基本字符设备驱动

问:系统是如何通过文件找到对应驱动的?


答:每一个文件都有一个 inode 号,可以通过 ls -i 查看。字符设备都有一个唯一的32位设备号。主设备号:12位,用于识别设备类别。次设备号:20位,用于识别是同类设备的中的第几个。

问:C语言中inline的作用?


答:在一个函数中去调用另一个函数时(PC寄存器 程序计数器)会发送跳转,而加了inline之后编译器会自动将另外一个函数代码放入这个函数当中,从而减少跳转发生提高程序的运行速度。缺点是会让程序的代码量增加。

注册字符设备驱动函数

注册字符设备驱动设备号


static inline int register_chrdev(
unsigned int major, /* 主设备号。[major > 0]:用户指定,[major = 0]:系统分配 失败返回错误码*/
const char *name, /* 驱动的名字。 可以通过 $cat /proc/devices 查看 */
const struct file_operations *fops) { /* 操作方法结构体 */
  return __register_chrdev(major, 0, 256, name, fops);
  }

【注意】使用register_chrdev()注册的设备次设备号的范围是 0~255

注销字符设备驱动设备号


static inline void unregister_chrdev(
   unsigned int major, /* 主设备号 */
   const char *name) { /* 驱动的名字 */
  __unregister_chrdev(major, 0, 256, name);
}

内核返回的错误类型

#ifndef _ASM_GENERIC_ERRNO_BASE_H
#define _ASM_GENERIC_ERRNO_BASE_H

#define EPERM 1 /* Operation not permitted         操作不允许 */
#define ENOENT 2 /* No such file or directory       没有此类文件或目录 */
#define ESRCH 3 /* No such process                 没有这种程序 */
#define EINTR 4 /* Interrupted system call         系统呼叫中断 */
#define EIO     5 /* I/O error                       I/O错误 */
#define ENXIO 6 /* No such device or address       没有这种设备或地址 */
#define E2BIG 7 /* Argument list too long           参数列表太长 */
#define ENOEXEC 8 /* Exec format error               执行格式错误 */
#define EBADF 9 /* Bad file number                 坏的文件号 */
#define ECHILD 10 /* No child processes               没有子程序 */
#define EAGAIN 11 /* Try again                       再试一次 */
#define ENOMEM 12 /* Out of memory                   内存溢出 */
#define EACCES 13 /* Permission denied               操作不允许 */
#define EFAULT 14 /* Bad address                     错误地址 */
#define ENOTBLK 15 /* Block device required           需要块设备 */
#define EBUSY 16 /* Device or resource busy         设备或资源繁忙 */
#define EEXIST 17 /* File exists                     文件已存在 */
#define EXDEV 18 /* Cross-device link               跨设备的链接 */
#define ENODEV 19 /* No such device                   没有这种设备 */
#define ENOTDIR 20 /* Not a directory                 不是一个目录 */
#define EISDIR 21 /* Is a directory                   是一个目录 */
#define EINVAL 22 /* Invalid argument                 无效参数 */
#define ENFILE 23 /* File table overflow             文件表溢出 */
#define EMFILE 24 /* Too many open files             打开的文件太多了 */
#define ENOTTY 25 /* Not a typewriter                 不是打字机 */
#define ETXTBSY 26 /* Text file busy                   文本文件繁忙 */
#define EFBIG 27 /* File too large                   文件太大 */
#define ENOSPC 28 /* No space left on device         设备上没有剩余空间 */
#define ESPIPE 29 /* Illegal seek                     非法寻找 */
#define EROFS 30 /* Read-only file system           只读文件系统 */
#define EMLINK 31 /* Too many links                   太多的链接 */
#define EPIPE 32 /* Broken pipe                     管道破裂 */
#define EDOM 33 /* Math argument out of domain of func 数学参数超出函数域 */
#define ERANGE 34 /* Math result not representable   数学结果不能表示 */

#endif

最简单的字符设备驱动

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/types.h>

#define cdevname "test_cdev"
int major = 0;

/* 编写文件操作函数 */
ssize_t cdev_read(struct file *file, char __user *ubuffp, size_t size, loff_t *offs) {
   printk(KERN_EMERG "%s, %s, %d\n", __FILE__, __func__, __LINE__);
   return 0;
}
ssize_t cdev_write(struct file *file, const char __user *ubuffp, size_t size, loff_t *offs) {
   printk(KERN_EMERG "%s, %s, %d\n", __FILE__, __func__, __LINE__);
   return 0;
}
int cdv_open(struct inode *inode, struct file *file) {
   printk(KERN_EMERG "%s, %s, %d\n", __FILE__, __func__, __LINE__);
   return 0;
}
int cdev_close(struct inode *inode, struct file *file) {
   printk(KERN_EMERG "%s, %s, %d\n", __FILE__, __func__, __LINE__);
   return 0;
}

const struct file_operations fops = {
  .open   = cdv_open, /* open操作函数指针 */
  .read   = cdev_read, /* read操作函数指针 */
  .write  = cdev_write, /* write操作函数指针 */
  .release = cdev_close, /* close操作函数指针 */
};

static int __init chrdev_init(void) {
   
   //注册字符设备驱动
   major = register_chrdev(major, cdevname, &fops);
   
   if(major < 0) {
       printk(KERN_ERR "register char device error");
       return -EAGAIN;
  }
   
   printk(KERN_EMERG "%s, %s, %d\n", __FILE__, __func__, __LINE__);
   return 0;
}

static void __exit chrdev_exit(void) {
   unregister_chrdev(major, cdevname);
   printk(KERN_EMERG "%s, %s, %d\n",__FILE__, __func__, __LINE__);
}

module_init(chrdev_init);
module_exit(chrdev_exit);
MODULE_LICENSE("GPL");

创建设备文件

编译好以上驱动并安装后,使用 $cat /proc/devices 查看主设备号

此时由于驱动中还没有创建设备节点 $ls /dev -la 查看不到该设备的设备文件

手动创建设备文件

sudo mknod /dev/test_cdev c 240 0
#设备类型(c:字符设备/b:块设备) 主设备号 次设备号(范围0~255)
sudo chmod 777 /dev/test_cdev
#修改文件权限,如果没有修改权限,应用程序调用的时候需要管理员权限

【注意】网卡没有设备文件,设备文件的路径是任意的

编写应用程序

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

char buff[128] = {0,};

int main(int argc, char const *argv[])
{
   int fd;
   fd = open("/dev/test_cdev", O_RDWR);
   if(fd == -1) {
       perror("open /dev/test_cdev error");
       return -1;
  }

   read(fd, buff, sizeof(buff));
   write(fd, buff, sizeof(buff));
   close(fd);
   return 0;
}

编译、运行测试程序

$gcc app.c -o app
$sudo ./app

$sudo dmesg #查看运行结果
#分别执行了驱动模块当中的打印语句

用户空间和内核空间数据传递

static  unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
/* 用户空间数据拷贝到内核空间
* @to: 内核空间数据首地址指针
* @from: 用户空间数据首地址指针
* @n: 拷贝数据的大小(字节byte)
* @返回值: 成功返回0,失败返回未拷贝的数据大小(字节byte)*/

static  unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
/* 内核空间数据拷贝到用户空间
* @to: 用户空间数据首地址指针
* @from: 内核空间数据首地址指针
* @n: 拷贝数据的大小(字节byte)
* @返回值: 成功返回0,失败返回未拷贝的数据大小(字节byte)*/

示例程序

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/uaccess.h>

#define cdevname "test_cdev"
int major = 240; //手动指定主设备号
char copybuff[128] = {0,};

ssize_t cdev_read(struct file *file, char __user *ubuffp, size_t size, loff_t *offs) {
   int ret;
   if(size > sizeof(copybuff))
       size = sizeof(copybuff);
   
   ret = copy_to_user(ubuffp, copybuff, size); //成功返回0, 失败返回未成功拷贝字节个数
   if(ret == 0) {
       printk(KERN_ERR "copy to user error\n");
       return -EINVAL; //返回错误码,注意是负数
  }

   printk(KERN_EMERG "%s, %s, %d\n", __FILE__, __func__, __LINE__);
   return size;    //read()成功返回读取的字节个数
}
ssize_t cdev_write(struct file *file, const char __user *ubuffp, size_t size, loff_t *offs) {
   int ret;
   if(size > sizeof(copybuff))
       size = sizeof(copybuff);
   
   ret = copy_from_user(copybuff, ubuffp, size); //成功返回0, 失败返回未成功拷贝字节个数
   if(ret == 0) {
       printk(KERN_ERR "copy from user error\n");
       return -EINVAL; //返回错误码,注意是负数
  }
   printk(KERN_EMERG "%s, %s, %d\n", __FILE__, __func__, __LINE__);
   return size;
}
int cdv_open(struct inode *inode, struct file *file) {
   printk(KERN_EMERG "%s, %s, %d\n", __FILE__, __func__, __LINE__);
   return 0;
}
int cdev_close(struct inode *inode, struct file *file) {
   printk(KERN_EMERG "%s, %s, %d\n", __FILE__, __func__, __LINE__);
   return 0;
}

const struct file_operations fops = {
  .open   = cdv_open,
  .read   = cdev_read,
  .write  = cdev_write,
  .release = cdev_close,
};

static int __init chrdev_init(void) {
   int ret;
   //注册字符设备驱动
   ret = register_chrdev(major, cdevname, &fops); //成功注册返回主设备号,失败返回负数的错误码
   if(ret < 0) {
       printk(KERN_ERR "register char device error");
       return -EAGAIN;
  }
   printk(KERN_EMERG "%s, %s, %d\n", __FILE__, __func__, __LINE__);
   return 0;
}

static void __exit chrdev_exit(void) {
   unregister_chrdev(major, cdevname);
   printk(KERN_EMERG "%s, %s, %d\n",__FILE__, __func__, __LINE__);
}

module_init(chrdev_init);
module_exit(chrdev_exit);
MODULE_LICENSE("GPL");

示例应用程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

char buff[128] = "hello linux!\n";

int main(int argc, char const *argv[])
{
   int fd;
   fd = open("/dev/test_cdev", O_RDWR);
   if(fd == -1) {
       perror("open /dev/test_cdev error");
       return -1;
  }
   printf("%s", buff);
   write(fd, buff, strlen(buff));

   bzero(buff, sizeof(buff));
   read(fd, buff, sizeof(buff));
   printf("%s", buff);

   close(fd);
   return 0;
}

物理地址映射

void  *ioremap(phys_addr_t offset, size_t size);
/* 功能:将物理地址映射成虚拟地址的接口
* 参数:
* @offset:物理起始地址
* @size: 映射空间的大小(字节)
* @返回值: 成功返回起始虚拟地址,失败返回NULL*/

void iounmap(void __iomem *addr);
/* 功能:取消映射*/

发表评论

您的电子邮箱地址不会被公开。