字符设备的驱动程序开发步骤大致都是差不多的,这里绘制了一张图来形象的反应字符设备驱动程序的关键步骤:

linux内核驱动开发(Linux内核与驱动学习记录-字符设备驱动程序框架)(1)

我们创建一个字符设备的时候,首先要得到一个设备号,分配设备号的途径有静态分配和动态分配;拿到设备的唯一 ID,我们需要实现 file_operation 并保存到 cdev 中,实现 cdev 的初始化;然后我们需要将我们所做的工作告诉内核,使用 cdev_add() 注册 cdev;最后我们还需要创建设备节点,以便我们后面调用 file_operation接口。 注销设备时我们需释放内核中的 cdev,归还申请的设备号,删除创建的设备节点。

1.字符设备的定义

Linux 内核提供了两种方式来定义字符设备:

//第一种方式 static struct cdev chrdev; //第二种方式 struct cdev *cdev_alloc(void); struct cdev *p_cdev = cdev_alloc();

第一种方式,就是我们常见的变量定义;第二种方式,是内核提供的动态分配方式,调用该函数之后,会返回一个 struct cdev 类型的指针,用于描述字符设备。

第二种方式定义的字符设备,可以通过cdev_del函数来释放占用的内存。

2.设备号的申请和归还2.1.设备号的静态申请

register_chrdev_region 函数用于静态地为一个字符设备申请一个或多个设备编号。

int register_chrdev_region(dev_t from, unsigned count, const char *name);

参数:

返回值:返回 0 表示申请成功,失败则返回错误码。

2.2.设备号的动态申请

使用 register_chrdev_region 函数时,都需要去查阅内核源码的 Documentation/devices.txt 文件,这就十分不方便。因此,内核又为我们提供了一种能够动态分配设备编号的方式: alloc_chrdev_region。

调用 alloc_chrdev_region 函数,内核会自动给我们分配一个尚未使用的主设备号。我们可以通过命令“cat /proc/devices”查询内核分配的主设备号。

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

参数:

返回值:返回 0 表示申请成功,失败则返回错误码。

2.3.设备号的申请(静态和动态都支持)

除了register_chrdev_region函数能够静态申请设备号,alloc_chrdev_region函数能够动态申请设备号之外,内核还提供了register_chrdev 函数用于分配设备号。该函数是一个内联函数,它不仅支持静态申请设备号,也支持动态申请设备号,并将主设备号返回。register_chrdev 函数原型如下:

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) { return __register_chrdev(major, 0, 256, name, fops); }

参数:

返回值:主设备号。

我们从以上代码中可以看到,使用 register_chrdev 函数向内核申请设备号,同一类字符设备(即主设备号相同),会在内核中申请了256 个,通常情况下,我们不需要用到这么多个设备,这就造成了极大的资源浪费。因此通常情况下,并不使用该函数。

2.4.设备号的归还

当我们删除字符设备时候,我们需要把分配的设备编号交还给内核,对于使用 register_chrdev_region 函数以及 alloc_chrdev_region 函数分配得到的设备编号,可以使用 unregister_chrdev_region 函数将分配得到的设备号归还给内核。

void unregister_chrdev_region(dev_t from, unsigned count);

参数:

3.字符设备的初始化

cdev_init()函数主要将file_operations结构体和我们的字符设备结构体相关联。

void cdev_init(struct cdev *cdev, const struct file_operations *fops);

参数:

4.字符设备的注册和移除4.1.字符设备的注册

cdev_add 函数用于向内核的 cdev_map 散列表添加一个新的字符设备。

int cdev_add(struct cdev *p, dev_t dev, unsigned count);

参数:

返回值:0或者错误码。

4.2.字符设备的移除

从内核中移除某个字符设备,则需要调用 cdev_del 函数。从系统中删除 cdev, cdev 设备将无法再打开,但任何已经打开的 cdev 将保持不变,即使在 cdev_del 返回后,它们的 fops 仍然可以调用。

void cdev_del(struct cdev *p);

参数:

5.设备节点的创建和销毁5.1.设备节点的创建

可以在代码中使用device_create函数创建设备节点。

struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);

参数:

返回值:成功时返回 struct device 结构体指针, 错误时返回 ERR_PTR()。

5.2.设备节点的销毁

可以使用device_destroy函数来删除 device_create 函数创建的设备节点。

void device_destroy(struct class *class, dev_t devt);

参数:

5.3.使用mknod命令创建设备节点

除了使用代码创建设备节点,还可以使用 mknod 命令创建设备节点。

用法: mknod 设备名 设备类型 主设备号 次设备号

当类型为”p”时可不指定主设备号和次设备号,否则它们是必须指定的。如果主设备号和次设备号以”0x”或”0X”开头,它们会被视作十六进制数来解析;如果以”0”开头,则被视作八进制数;其余情况下被视作十进制数。可用的设备类型包括:

  • b:创建 (有缓冲的) 区块特殊文件;
  • c,u:创建 (没有缓冲的) 字符特殊文件;
  • p:创建先进先出 (FIFO) 特殊文件。

如: mkmod /dev/test c 2 0 创建一个字符设备/dev/test,其主设备号为 2,次设备号为 0。

当我们使用上述命令,创建了一个字符设备文件时,实际上就是创建了一个设备节点 inode 结构体,并且将该设备的设备编号记录在成员i_rdev,将成员 f_op 指针指向了 def_chr_fops 结构体。这就是 mknod 负责的工作内容。

inode 上的 file_operation 并不是自己构造的 file_operation,而是字符设备通用的 def_chr_fops,那么自己构建的 file_operation 等在应用程序调用 open 函数之后,才会绑定在文件上。

,