Linux设备驱动之字符设备驱动

article/2025/11/10 1:14:22

一、linux系统将设备分为3类:字符设备、块设备、网络设备。

应用程序调用的流程框图:



三种设备的定义分别如下,

字符设备:只能一个字节一个字节的读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后顺序进行。字符设备是面向流的设备,常见的字符设备如鼠标、键盘、串口、控制台、LED等。

块设备:是指可以从设备的任意位置读取一定长度的数据设备。块设备如硬盘、磁盘、U盘和SD卡等存储设备。

网络设备:网络设备比较特殊,不在是对文件进行操作,而是由专门的网络接口来实现。应用程序不能直接访问网络设备驱动程序。在/dev目录下也没有文件来表示网络设备。



对于字符设备和块设备来说,在/dev目录下都有对应的设备文件。linux用户程序通过设备文件或叫做设备节点来使用驱动程序操作字符设备和块设备。


二、字符设备和快设备启动与用户控件访问该设备的程序 ,三者之间的关系。



如上图,在linux内核中使用cdev结构体来描述字符设备,通过其成员dev_t来定义设备号,以确定字符设备的唯一性。通过其成员file_operations来定义字符设备驱动提供给VFS的接口函数,如常见的open()、read()、write()等。

在Linux字符设备驱动中,模块加载函数通过register_chrdev_region( ) 或alloc_chrdev_region( )来静态或者动态获取设备号,通过cdev_init( )建立cdev与file_operations之间的连接,通过cdev_add( )向系统添加一个cdev以完成注册。模块卸载函数通过cdev_del( )来注销cdev,通过unregister_chrdev_region( )来释放设备号。

用户空间访问该设备的程序通过Linux系统调用,如open( )、read( )、write( ),来“调用”file_operations来定义字符设备驱动提供给VFS的接口函数。


三、字符设备驱动模板



这张图基本表示了字符驱动所需要的模板,只是缺少class的相关内容,class主要是用来自动创建设备节点的,还有就是一个比较常用的ioctl()函数没有列在上边。

下面具体说一下每个步骤的详细内容。

1.驱动初始化

1.1分配cdev

在2.6的内核中使用cdev结构体来描述字符设备,在驱动中分配cdev,主要是分配一个cdev结构体与申请设备号。

例如:

/*……*/
/* 分配cdev*/
struct cdev btn_cdev;
/*……*/
/* 1.1 申请设备号*/
if(major){//静态dev_id = MKDEV(major, 0);register_chrdev_region(dev_id, 1, "button");
} else {//动态alloc_chardev_region(&dev_id, 0, 1, "button");major = MAJOR(dev_id);
}
/*……*/
从上面的代码可以看出,申请设备号有动静之分,其实设备号还有主次之分。


在Linux中以主设备号用来标识与设备文件相连的驱动程序。次编号被驱动程序用来辨别操作的是哪个设备。cdev 结构体的 dev_t 成员定义了设备号,为 32 位,其中高 12 位为主设备号,低20 位为次设备号。

设备号的获得与生成:
获得:主设备号:MAJOR(dev_t dev);
次设备号:MINOR(dev_t dev);
生成:MKDEV(int major,int minor);

设备号申请的动静之分:
静态:

int register_chrdev_region(dev_t from, unsigned count, const char *name);
/*功能:申请使用从from开始的count 个设备号(主设备号不变,次设备号增加)*/
静态申请相对较简单,但是一旦驱动被广泛使用,这个随机选定的主设备号可能会导致设备号冲突,而使驱动程序无法注册。

动态:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
/*功能:请求内核动态分配count个设备号,且次设备号从baseminor开始。*/
动态申请简单,易于驱动推广,但是无法在安装驱动前创建设备文件(因为安装前还没有分配到主设备号)。


1.2.初始化cdev

1 void cdev_init(struct cdev *, struct file_operations *); 
2 cdev_init()函数用于初始化 cdev 的成员,并建立 cdev 和 file_operations 之间的连接。


1.3.注册cdev

1 int cdev_add(struct cdev *, dev_t, unsigned);
2      cdev_add()函数向系统添加一个 cdev,完成字符设备的注册。

1.4.硬件初始化

硬件初始化主要是硬件资源的申请与配置,主要涉及地址映射,寄存器读写等相关操作,每一个开发板不同都有不同的实现方式,这里以FS4412为例:

	/* 地址映射 */gpx2con = ioremap(GPX2CON, 4);if (gpx2con == NULL){printk("gpx2con ioremap err\n");goto err3;}gpx2dat = ioremap(GPX2DAT, 4);if (gpx2dat == NULL){printk("gpx2dat ioremap err\n");goto err4;}gpx1con = ioremap(GPX1CON, 4);gpx1dat = ioremap(GPX1DAT, 4);gpf3con = ioremap(GPF3CON, 4);gpf3dat = ioremap(GPF3DAT, 4);writel(readl(gpx2con)&(~(0xf<<28))|(0x1<<28), gpx2con);writel((0x1<<7), gpx2dat);writel(readl(gpx1con)&(~(0xf<<0))|(0x1<<0), gpx1con);writel((0x1<<0), gpx1dat);writel(readl(gpf3con)&(~(0xf<<16))|(0x1<<16), gpf3con);writel(readl(gpf3dat)|(0x1<<4), gpf3dat);writel(readl(gpf3con)&(~(0xf<<20))|(0x1<<20), gpf3con);writel(readl(gpf3dat)|(0x1<<5), gpf3dat);

2.实现设备操作

用户空间的程序以访问文件的形式访问字符设备,通常进行open、read、write、close等系统调用。而这些系统调用的最终落实则是file_operations结构体中成员函数,它们是字符设备驱动与内核的接口。以FS4412的LED驱动为例:

struct file_operations hello_fops = {.owner = THIS_MODULE,.open = hello_open,.release = hello_release,.read = hello_read,.write = hello_write,
};

上面代码中的hello_open、hello_write、hello_read是要在驱动中自己实现的。file_operations结构体成员函数有很多个,下面就选几个常见的来展示:

2.1. open()函数

原型:

1 int(*open)(struct inode *, struct file*); 
2 /*打开*/

2.2. read( )函数

原型:

ssize_t(*read)(struct file *, char __user*, size_t, loff_t*); 
/*用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一个负值*/

2.3. write( )函数

原型:

1 ssize_t(*write)(struct file *, const char__user *, size_t, loff_t*);
2 /*向设备发送数据,成功时该函数返回写入的字节数。如果此函数未被实现,
3 当用户进行write()系统调用时,将得到-EINVAL返回值*/
2.4. close( )函数

原型:

1 int(*release)(struct inode *, struct file*); 
2 /*关闭*/

2.5 ioctl( )函数

原型:

int ioctl(int fd, ind cmd, …);

这里主要说一下ioctl函数。ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。
其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。
ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道。

如果不用ioctl的话,也可以实现对设备I/O通道的控制,但那就是蛮拧了。例如,我们可以在驱动程序中实现write的时候检查一下是否有特殊约定的数据流通过,如果有的话,那么后面就跟着控制命令(一般在socket编程中常常这样做)。但是如果这样做的话,会导致代码分工不明,程序结构混乱,程序员自己也会头昏眼花的。

所以,我们就使用ioctl来实现控制的功能。要记住,用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。

在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情,因为设备都是特定的,这里也没法说。关键在于怎么样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径。

命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,这样才不会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或者是把错误的命令发给错误的设备。这些错误都会导致不可预料的事情发生,而当程序员发现了这些奇怪的事情的时候,再来调试程序查找错误,那将是非常困难的事情。

所以在Linux核心中是这样定义一个命令码的:

      MAGIC: 幻数  8位的数  0 - 255    'L' 
      nr: 序数,用来区分同一类设备的不同命令
      #define   CMD     _IO(MAGIC, nr)

 hello.h
      #define   LED_MAGIC  'L'
      #define   LED_ON    _IO(LED_MAGIC, 1)
      #define   LED_OFF   _IO(LED_MAGIC, 2)

通过这种方式来定义cmd,并在ioctl程序中区分cmd。


2.5. 补充说明

1. 在Linux字符设备驱动程序设计中,有3种非常重要的数据结构:struct file、struct inode、struct file_operations。

struct file 代表一个打开的文件。系统中每个打开的文件在内核空间都有一个关联的struct file。它由内核在打开文件时创建, 在文件关闭后释放。其成员loff_t f_pos 表示文件读写位置。

struct inode 用来记录文件的物理上的信息。因此,它和代表打开文件的file结构是不同的。一个文件可以对应多个file结构,但只有一个inode结构。其成员dev_t i_rdev表示设备号。

struct file_operations 一个函数指针的集合,定义能在设备上进行的操作。结构中的成员指向驱动中的函数,这些函数实现一个特别的操作, 对于不支持的操作保留为NULL。

2. 在read( )和write( )中的buff 参数是用户空间指针。因此,它不能被内核代码直接引用,因为用户空间指针在内核空间时可能根本是无效的——没有那个地址的映射。因此,内核提供了专门的函数用于访问用户空间的指针:

1 unsigned long copy_from_user(void *to, const void __user *from, unsigned long count);
2 unsigned long copy_to_user(void __user *to, const void *from, unsigned long count);

3. 驱动注销

3.1. 删除cdev

在字符设备驱动模块卸载函数中通过cdev_del()函数向系统删除一个cdev,完成字符设备的注销。
/*原型:*/
void cdev_del(struct cdev *);
/*例:*/
cdev_del(&btn_cdev);

3.2. 释放设备号

在调用cdev_del()函数从系统注销字符设备之后,unregister_chrdev_region()应该被调用以释放原先申请的设备号。
/*原型:*/
void unregister_chrdev_region(dev_t from, unsigned count);
/*例:*/
unregister_chrdev_region(MKDEV(major, 0), 1);

四、字符设备驱动程序基础:

4.1 cdev结构体

前面写到如何向系统申请一个设备号,设备号就像我们的身份证号一样,号本身并没有什么特殊的意义,只有把这个号和人对应才有意义,通用设备号也需要和一个特殊的东西对于,这就是cdev, cdev是linux下抽象出来的一个用来描述一个字符设备的结构体,在linux下定义如下:

struct cdev {struct kobject kobj;struct module *owner;const struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;};

结构体中有几个成员事我们写驱动的时候必须关心的:
dev 类型是dev_t,也就是我们的设备号
ops是一个同样也是一个结构体并且是一个字符驱动实现的主体,字符驱动通常需要和应用程序交互,在学linux系统编程的时候,都会讲到linux 应用程序通过系统调用陷入到内核空间,从而执行内核代码,而驱动作为内核的一部分同样也是需要在内核空间执行的,ops也就是file_operations这个结构体就是我们的驱动为应用程序调用驱动而实现的一个操作的集合。后面会详细讲解。

cdev 结构体的dev_t 成员定义了设备号,为32位,其中12位是主设备号,20位是次设备号,我们只需使用二个简单的宏就可以从dev_t 中获取主设备号和次设备号:

MAJOR(dev_t dev)
MINOR(dev_t dev)

相反地,可以通过主次设备号来生成dev_t:

MKDEV(int major,int minor)


4.2 Linux 2.6内核提供一组函数用于操作cdev 结构体

1:void cdev_init(struct cdev*,struct file_operations *);
2:struct cdev *cdev_alloc(void);
3:int cdev_add(struct cdev *,dev_t,unsigned);
4:void cdev_del(struct cdev *);

其中(1)用于初始化cdev结构体,并建立cdev与file_operations 之间的连接。(2)用于动态分配一个cdev结构,(3)向内核注册一个cdev结构,(4)向内核注销一个cdev结构。

cdev_add实现cdev的注册,linux内核里维护了一个cdev_map的表,所谓cdev的注册就是把我们的cdev注册到cdev_map表上,cdev_map表结构如图:



4.3 Linux 2.6内核分配和释放设备号

在调用cdev_add()函数向系统注册字符设备之前,首先应向系统申请设备号,有二种方法申请设备号,一种是静态申请设备号:

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

另一种是动态申请设备号:

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

其中,静态申请是已知起始设备号的情况,如先使用cat /proc/devices 命令查得哪个设备号未事先使用(不推荐使用静态申请);动态申请是由系统自动分配,只需设置major = 0即可。

相反地,在调用cdev_del()函数从系统中注销字符设备之后,应该向系统申请释放原先申请的设备号,使用:

7:void unregister_chrdev_region(dev_t from,unsigned count);

4.4 cdev结构的file_operations结构体

这个结构体是字符设备当中最重要的结构体之一,file_operations 结构体中的成员函数指针是字符设备驱动程序设计的主体内容,这些函数实际上在应用程序进行Linux 的 open()、read()、write()、close()、seek()、ioctl()等系统调用时最终被调用。

1 struct file_operations {2 3 /*拥有该结构的模块计数,一般为THIS_MODULE*/4 struct module *owner;5 6 /*用于修改文件当前的读写位置*/7 loff_t (*llseek) (struct file *, loff_t, int);8 9 /*从设备中同步读取数据*/
10 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
11 
12 /*向设备中写数据*/
13 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
14 
15 
16 ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
17 ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
18 int (*readdir) (struct file *, void *, filldir_t);
19 
20 /*轮询函数,判断目前是否可以进行非阻塞的读取或写入*/
21 unsigned int (*poll) (struct file *, struct poll_table_struct *);
22 
23 /*执行设备的I/O命令*/
24 int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
25 
26 
27 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
28 long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
29 
30 /*用于请求将设备内存映射到进程地址空间*/
31 int (*mmap) (struct file *, struct vm_area_struct *);
32 
33 /*打开设备文件*/
34 int (*open) (struct inode *, struct file *);
35 int (*flush) (struct file *, fl_owner_t id);
36 
37 /*关闭设备文件*/
38 int (*release) (struct inode *, struct file *);
39 
40 
41 int (*fsync) (struct file *, struct dentry *, int datasync);
42 int (*aio_fsync) (struct kiocb *, int datasync);
43 int (*fasync) (int, struct file *, int);
44 int (*lock) (struct file *, int, struct file_lock *);
45 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
46 unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
47 int (*check_flags)(int);
48 int (*flock) (struct file *, int, struct file_lock *);
49 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
50 ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
51 int (*setlease)(struct file *, long, struct file_lock **);
52 };

4.5 file结构

file 结构代表一个打开的文件,它的特点是一个文件可以对应多个file结构。它由内核再open时创建,并传递给在该文件上操作的所有函数,直到最后close函数,在文件的所有实例都被关闭之后,内核才释放这个数据结构。

在内核源代码中,指向 struct file 的指针通常比称为filp,file结构有以下几个重要的成员:

1 struct file{2 3 mode_t fmode; /*文件模式,如FMODE_READ,FMODE_WRITE*/4 5 ......6 7 loff_t f_pos; /*loff_t 是一个64位的数,需要时,须强制转换为32位*/8 9 unsigned int f_flags; /*文件标志,如:O_NONBLOCK*/
10 
11 struct file_operations *f_op;
12 
13 void *private_data; /*非常重要,用于存放转换后的设备描述结构指针*/
14 
15 .......
16 
17 };

4.6 inode 结构

内核用inode 结构在内部表示文件,它是实实在在的表示物理硬件上的某一个文件,且一个文件仅有一个inode与之对应,同样它有二个比较重要的成员:

1 struct inode{2 3 dev_t i_rdev; /*设备编号*/4 5 struct cdev *i_cdev; /*cdev 是表示字符设备的内核的内部结构*/6 7 };8 9 可以从inode中获取主次设备号,使用下面二个宏:
10 
11 /*驱动工程师一般不关心这二个宏*/
12 
13 unsigned int imajor(struct inode *inode);
14 
15 unsigned int iminor(struct inode *inode);

4.7字符设备驱动模块加载与卸载函数

在字符设备驱动模块加载函数中应该实现设备号的申请和cdev 结构的注册,而在卸载函数中应该实现设备号的释放与cdev结构的注销。

我们一般习惯将cdev内嵌到另外一个设备相关的结构体里面,该设备包含所涉及的cdev、私有数据及信号量等等信息。常见的设备结构体、模块加载函数、模块卸载函数形式如下:

1/*设备结构体*/2 3 struct xxx_dev{4 5 struct cdev cdev;6 7 char *data;8 9 struct semaphore sem;
10 
11 ......
12 
13 };
14 
15 
16 
17 /*模块加载函数*/
18 
19 static int __init xxx_init(void)
20 
21 {
22 
23 .......
24 
25 初始化cdev结构;
26 
27 申请设备号;
28 
29 注册设备号;
30 
31 
32 
33 申请分配设备结构体的内存; /*非必须*/
34 
35 }
36 
37 
38 
39 /*模块卸载函数*/
40 
41 static void __exit xxx_exit(void)
42 
43 {
44 
45 .......
46 
47 释放原先申请的设备号;
48 
49 释放原先申请的内存;
50 
51 注销cdev设备;
52 
53 }
54 
55

4.8字符设备驱动的 file_operations 结构体重成员函数

1 /*读设备*/2 3 ssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)4 5 {6 7 ......8 9 使用filp->private_data获取设备结构体指针;
10 
11 分析和获取有效的长度;
12 
13 /*内核空间到用户空间的数据传递*/
14 
15 copy_to_user(void __user *to, const void *from, unsigned long count);
16 
17 ......
18 
19 }
20 
21 /*写设备*/
22 
23 ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
24 
25 {
26 
27 ......
28 
29 使用filp->private_data获取设备结构体指针;
30 
31 分析和获取有效的长度;
32 
33 /*用户空间到内核空间的数据传递*/
34 
35 copy_from_user(void *to, const void __user *from, unsigned long count);
36 
37 ......
38 
39 }
40 
41 /*ioctl函数*/
42 
43 static int xxx_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)
44 
45 {
46 
47 ......
48 
49 switch(cmd){
50 
51 case xxx_CMD1:
52 
53 ......
54 
55 break;
56 
57 case xxx_CMD2:
58 
59 .......
60 
61 break;
62 
63 default:
64 
65 return -ENOTTY; /*不能支持的命令*/
66 
67 }
68 
69 return 0;
70 
71 }

4.9、字符设备驱动文件操作结构体模板

1 struct file_operations xxx_fops = {2 3 .owner = THIS_MODULE,4 5 .open = xxx_open,6 7 .read = xxx_read,8 9 .write = xxx_write,
10 
11 .close = xxx_release,
12 
13 .ioctl = xxx_ioctl,
14 
15 .lseek = xxx_llseek,
16 
17 };
18 
19 上面的写法需要注意二点,一:结构体成员之间是以逗号分开的而不是分号,结构体字段结束时最后应加上分号。

5.自动创建设备节点

在刚开始写Linux设备驱动程序的时候,很多时候都是利用mknod命令手动创建设备节点,实际上Linux内核为我们提供了一组函数,可以用来在模块加载的时候自动在/dev目录下创建相应设备节点,并在卸载模块时删除该节点。

在2.6.17以前,在/dev目录下生成设备文件很容易,
devfs_mk_bdev
devfs_mk_cdev
devfs_mk_symlink
devfs_mk_dir
devfs_remove
这几个是纯devfs的api,2.6.17以前可用,但后来devfs被sysfs+udev的形式取代,同时期sysfs文件系统可以用的api:
class_device_create_file,在2.6.26以后也不行了,现在,使用的是device_create ,从2.6.18开始可用
struct device *device_create(struct class *class, struct device *parent, dev_t devt, const char *fmt, ...)
从2.6.26起又多了一个参数drvdata: the data to be added to the device for callbacks
不会用可以给此参数赋NULL
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)

在驱动用加入对udev的支持主要做的就是:在驱动初始化的代码里调用class_create(...)为该设备创建一个class,再为每个设备调用device_create(...)( 在2.6较早的内核中用class_device_create)创建对应的设备。
内核中定义的struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用 device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应 device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。
struct class和class_create(…) 以及device_create(…)都包含在在/include/linux/device.h中,使用的时候一定要包含这个头文件,否则编译器会报错。


struct class定义在头文件include/linux/device.h中

//device classesstruct class {const char       *name;struct module     *owner;nbsp;struct kset        subsys;struct list_head        devices;struct list_head        interfaces;struct kset             class_dirs;struct semaphore sem;    //lockschildren, devices, interfaces struct class_attribute   *class_attrs;struct device_attribute     *dev_attrs;int (*dev_uevent)(structdevice *dev, struct kobj_uevent_env *env);void (*class_release)(structclass *class);void (*dev_release)(struct device *dev);int (*suspend)(struct device*dev, pm_message_t state);int (*resume)(struct device *dev);
};
class_create(…)在/drivers/base/class.c中实现

// class_create - create a struct class structure
// @owner: pointer to the module that is to "own"this struct class
// @name: pointer to a string for the name of this class.// This is used to create a struct class pointer that canthen be used
// in calls to device_create().//Note, the pointer created here is to be destroyed whenfinished by
// making a call to class_destroy().struct class *class_create(struct module *owner, const char *name)
{struct class *cls;int retval;cls = kzalloc(sizeof(*cls), GFP_KERNEL);if (!cls) {retval = -ENOMEM;goto error;}cls->name = name;cls->owner = owner;cls->class_release =class_create_release;retval =class_register(cls);if (retval)goto error;return cls;
error:kfree(cls);return ERR_PTR(retval);
}

class_destroy(...)函数

// class_destroy - destroys a struct class structure
//@cs: pointer to the struct class that is to be destroyed//Note, the pointer to be destroyed must have been created with a call
//to class_create().void class_destroy(struct class *cls)
{if((cls == NULL) || (IS_ERR(cls)))return;class_unregister(cls);
}

device_create(…)函数在/drivers/base/core.c中实现

// device_create - creates a device and registersit with sysfs
// @class: pointer to the struct class that thisdevice should be registered to
// @parent: pointer to the parent struct device ofthis new device, if any
// @devt: the dev_t for the char device to beadded
// @fmt: string for the device's name// This function can be used by char deviceclasses. A struct device
// will be created in sysfs, registered to thespecified class.// A "dev" file will be created, showingthe dev_t for the device, if
// the dev_t is not 0,0.
// If a pointer to a parent struct device ispassed in, the newly created
// struct device will be a child of that device insysfs.
// The pointer to the struct device will bereturned from the call.
// Any further sysfs files that might be requiredcan be created using this
// pointer.// Note: the struct class passed to this functionmust have previously
// been created with a call to class_create().struct device *device_create(struct class *class,struct device *parent,dev_tdevt, const char *fmt, ...)
{va_list vargs;struct device *dev;va_start(vargs,fmt);dev =device_create_vargs(class, parent, devt, NULL, fmt, vargs);va_end(vargs);return dev;
}
第一个参数指定所要创建的设备所从属的类,
第二个参数是这个设备的父设备,如果没有就指定为NULL,
第三个参数是设备号,
第四个参数是设备名称,
第五个参数是从设备号。


class_destroy(...),device_destroy(...)也在/drivers/base/core.c中实现

// device_destroy - removes a device that was created with device_create()
// @class: pointer to the struct class that this device was registered with
// @devt: the dev_t of the device that was previously registered// This call unregisters and cleans up a device that was created with a
// call to device_create().void device_destroy(struct class *class,dev_t devt)
{structdevice *dev = NULL;structdevice *dev_tmp;down(&class->sem);list_for_each_entry(dev_tmp,&class->devices, node) {if(dev_tmp->devt == devt) {dev= dev_tmp;break;}}up(&class->sem);if(dev)device_unregister(dev);
}

相比devfs,udev有很多优势,在此就不罗嗦了,提醒一点,udev是应用层的东东,不要试图在内核的配置选项里找到它;加入对udev的支持很简单,以字符设备驱动为例,在驱动初始化的代码里调用class_create为该设备创建一个class,再为每个设备调用 class_device_create创建对应的设备。大致用法如下:

struct class *myclass = class_create(THIS_MODULE, “my_device_driver”);
class_device_create(myclass, NULL, MKDEV(major_num, 0), NULL, “my_device”);
这样的module被加载时,udev daemon就会自动在/dev下创建my_device设备文件。

下面以一个简单字符设备驱动来展示如何使用这几个函数:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>int HELLO_MAJOR = 0;
int HELLO_MINOR = 0;
int NUMBER_OF_DEVICES = 2;struct class *my_class;
//struct cdev cdev;
//dev_t devno;struct hello_dev {
struct device *dev;
dev_t chrdev;
struct cdev cdev;
};static struct hello_dev *my_hello_dev = NULL;struct file_operations hello_fops = {.owner = THIS_MODULE
};static int __init hello_init (void)
{
int err = 0;
struct device *dev;my_hello_dev = kzalloc(sizeof(struct hello_dev), GFP_KERNEL);
if (NULL == my_hello_dev) {
printk("%s kzalloc failed!\n",__func__);
return -ENOMEM;
}devno = MKDEV(HELLO_MAJOR, HELLO_MINOR);
if (HELLO_MAJOR)
err= register_chrdev_region(my_hello_dev->chrdev, 2, "memdev");
else
{
err = alloc_chrdev_region(&my_hello_dev->chrdev, 0, 2, "memdev");
HELLO_MAJOR = MAJOR(devno);
}  
if (err) {
printk("%s alloc_chrdev_region failed!\n",__func__);
goto alloc_chrdev_err;
}
printk("MAJOR IS %d\n",HELLO_MAJOR);cdev_init(&(my_hello_dev->cdev), &hello_fops);
my_hello_dev->cdev.owner = THIS_MODULE;
err = cdev_add(&(my_hello_dev->cdev), my_hello_dev->chrdev, 1);
if (err) {
printk("%s cdev_add failed!\n",__func__);
goto cdev_add_err;
}
printk (KERN_INFO "Character driver Registered\n");my_class =class_create(THIS_MODULE,"hello_char_class");  //类名为hello_char_class
if(IS_ERR(my_class)) 
{
err = PTR_ERR(my_class);
printk("%s class_create failed!\n",__func__);
goto class_err;
}dev = device_create(my_class,NULL,my_hello_dev->chrdev,NULL,"memdev%d",0);      //设备名为memdev
if (IS_ERR(dev)) {
err = PTR_ERR(dev);
gyro_err("%s device_create failed!\n",__func__);
goto device_err;
}printk("hello module initialization\n");
return 0;device_err:
device_destroy(my_class, my_hello_dev->chrdev);
class_err:
cdev_del(my_hello_dev->chrdev);
cdev_add_err:
unregister_chrdev_region(my_hello_dev->chrdev, 1);
alloc_chrdev_err:
kfree(my_hello_dev);
return err;
}static void __exit hello_exit (void)
{
cdev_del (&(my_hello_dev->cdev));
unregister_chrdev_region (my_hello_dev->chrdev,1);
device_destroy(my_class, devno);         //delete device node under /dev//必须先删除设备,再删除class类
class_destroy(my_class);                 //delete class created by us
printk (KERN_INFO "char driver cleaned up\n");
}module_init (hello_init);
module_exit (hello_exit);MODULE_LICENSE ("GPL");

加载结果:

[root@localhost node]# insmod node.ko  
[root@localhost node]# dmesg | tail -3  
[23503.365316] create node success:  
[23503.365319]   ls -l /dev/noddev*  
[23503.365321]   ls -l /sys/class/noddev  
[root@localhost node]# ls -l /dev/noddev*  
crw------- 1 root root 66,  0 11月 26 15:02 /dev/noddev0  
crw------- 1 root root 66, 20 11月 26 15:02 /dev/noddev20  
[root@localhost node]# ls -l /sys/class/noddev  
总用量 0  
lrwxrwxrwx 1 root root 0 11月 26 15:02 noddev0 -> ../../devices/virtual/noddev/noddev0  
lrwxrwxrwx 1 root root 0 11月 26 15:02 noddev20 -> ../../devices/virtual/noddev/noddev20  

















http://chatgpt.dhexx.cn/article/y1kn4e0q.shtml

相关文章

Linux设备驱动基础01:Linux设备驱动概述

目录 1. 设备驱动的作用 2. 有无操作系统时的设备驱动 2.1 无操作系统 2.1.1 硬件、驱动和应用程序的关系 2.1.2 单任务软件典型架构 2.2 有操作系统 2.2.1 硬件、驱动、操作系统和应用软件的关系 3. Linux设备分类 3.1 常规分类法 3.1.1 字符设备 3.1.2 块设备 3.…

Linux设备驱动模型

目录 一、linux设备驱动模型简介1、什么是设备驱动模型2、为什么需要设备驱动模型3、驱动开发的2个点 二、设备驱动模型的底层架构1、kobject2、kobj_type3、kset 三、总线式设备驱动组织方式1、总线2、设备3、驱动4、类5、总结 四、platform平台总线工作原理1、何为平台总线2、…

【linux内核分析与应用-陈莉君】字符设备驱动

目录 1.什么是字符设备 2.如何来描述字符设备 3 struct cdev与const struct file_operations之间的关系 4.struct file_operations源码 5.字符设备驱动框架 6.编写字符设备驱动的步骤 7.字符设备结构 8.字符设备驱动程序的注册 9.从系统调用到驱动程序 10.用户空间与内…

微信小程序云数据库使用讲解

第一步&#xff1a;注册开通 单击云开发 第二步&#xff1a;创建数据库 选择数据库&#xff0c;并点击号创建一个集合 输入名字 创建完毕后点击添加记录即可添加数据 数据库获取&#xff1a; 查询&#xff1a; 查询指令&#xff1a;

微信小程序云开发入门-数据库插入数据(包含批量)

一、前言 文章将介绍如何在微信小程序云开发中向云开发数据库插入数据&#xff08;单条或批量&#xff09;。 写法有好几种&#xff0c;文章将会一一进行对比&#xff0c;看看每种写法之间有何优缺点&#xff0c;如何让代码看起来更优雅。 为了更加贴合实际的开发逻辑&#xf…

【微信小程序】如何获取微信小程序云开发数据库的数据并渲染到页面?

前言 上一篇博客我把微信小程序云开发数据库操作&#xff08;增删改查&#xff09;的实现方法都已经分享出来啦&#xff0c;可以戳链接进去阅读哦 【微信小程序】小程序云开发实现数据库增删改查(小白速度Get起来&#xff01;&#xff01;一步步教你如何实现) 基于微信小程序…

开发一个可以查询并显示数据库内容的微信小程序

使用微信开发者工具可以创建云数据库&#xff0c;并通过代码可以查询并在客户端显示数据库的内容。 附&#xff1a;小程序一个功能页面有wxml(客户端呈现)&#xff0c;js(功能函数)&#xff0c;json&#xff0c;wxss(个性化处理)&#xff0c;这些是局部的文件。还有全局的文件如…

微信小程序开发---连接云开发数据库,实现数据获取

之前几篇博客里面都详细交代了如何配置云函数&#xff0c;现在就讲一下关于云函数中数据库的使用&#xff0c;主要是讲如何从云开发平台的数据库中调取数据 我们直接来到需要调用数据库数据的页面的js文件&#xff0c;直接设置全局变量&#xff0c;来便于后续调用数据库 const …

微信小程序查询数据库

微信小程序云开发的官方例子&#xff1a; const db wx.cloud.database() //获取数据库的引用 const _ db.command //获取数据库查询及更新指令 db.collection("china") //获取集合china的引用.where({ //查询的条件指令wheregdp: _.gt(3000) …

微信小程序云数据库操作

微信小程序云数据库操作 1、云数据库简介1.1 数据类型Date地理位置Null 1.2 权限控制 2、云数据库操作2.1 查询数据2.1.1 通过collection.doc获取一条记录2.1.2 通过collection.get获取所有记录的数据2.1.3 通过document.get获取某一条记录的数据2.1.4 通过collection.count获取…

微信小程序取本地数据库数据(实测有图)

测试效果如下&#xff1a; 本实验主要分为如下几个步骤&#xff1a; 一、安装数据库 二、安装PHPApache 三、编辑微信小程序代码 前两项的简单介绍在如下连接&#xff1a; PHPApache 四、本文主要介绍第三项的内容 需要用到的文件如下&#xff1a; 1、新建微信小程序工程 2、…

微信小程序云开发(数据库)

开发者可以使用云开发开发微信小程序、小游戏&#xff0c;无需搭建服务器&#xff0c;即可使用云端能力。 云开发为开发者提供完整的云端支持&#xff0c;弱化后端和运维概念&#xff0c;无需搭建服务器&#xff0c;使用平台提供的 API 进行核心业务开发&#xff0c;即可实现快…

微信小程序云开发(云数据库篇)

微信小程序云开发[云数据库篇] 云数据库关系型数据库和 JSON 数据库对比数据类型数据库操作联表查询事务处理 云数据库 云开发提供了一个 JSON 数据库&#xff0c;顾名思义&#xff0c;数据库中的每条记录都是一个 JSON 格式的对象。 一个数据库可以有多个集合&#xff08;相当…

微信小程序云开发 1 - 数据库

微信小程序云开发最重要的有两点&#xff1a; 1、云数据库&#xff1b; 2、云函数&#xff1b; 学会这两点基本就能够进行微信小程序的云开发&#xff1b; 首先&#xff0c;我们先看微信小程序云数据库的基本操作&#xff1a; 1&#xff09;打开微 信开发者工具&#xff0…

微信小程序笔记 -- 数据库

6.15 学习微信小程序 -- 数据库 数据库1. 初始化2. 数据库操作2.1 数据类型2.2 增删查改2.2.1 增加/插入 数据&#xff08;add方法&#xff09;2.2.2 删除数据&#xff08;remove方法&#xff09;2.2.3 查看数据&#xff08;get&#xff0c;where&#xff09;2.2.4 更新数据&am…

nohup忽略SIGHUP信号

今天遇到一个问题&#xff1a;开启终端启动gunicorn进程后台运行&#xff0c;终端不关闭时&#xff0c;可以导入excel&#xff0c;关闭终端后&#xff0c;不能导入excel。原因是&#xff0c;xlrd模块需要向控制台输出内容&#xff0c;终端关闭后&#xff0c;控制台消失&#xf…

sighup问题

2019独角兽企业重金招聘Python工程师标准>>> SIGHUP会在以下3种情况下被发送给相应的进程&#xff1a; 1、终端关闭时&#xff0c;该信号被发送到session首进程以及作为job提交的进程&#xff08;即用 & 符号提交的进程&#xff09; 2、session首进程退出时&…

Linux——1、守护进程、SIGHUP与nohup

一、守护进程 脱离于终端并且在后台运行的进程&#xff0c;用于长期运行&#xff0c;守护自己的职责&#xff08;如&#xff1a;监听端口、服务等&#xff09;。 1、特点&#xff1a; 不受用户登录、注销影响。大多数Linux下的服务器都是利用守护进程实现的&#xff0c;如My…

Failed to ignore SIGHUP: No error

memcache下载:https://www.runoob.com/memcached/window-install-memcached.html 下载下来需要install一下: c:\memcached\memcached.exe -d install c:\memcached\memcached.exe -d start c:\memcached\memcached.exe -d stop 然后报错: 我下载的版本是1.4.5按照网上说的: 以…

安装Memcached:Failed to ignore SIGHUP: No error 解决方式

Memcached安装1.4.5 版本 管理员运行cmd&#xff0c;出现如下错误&#xff1a; 解决方式&#xff1a; 命令行中输入schtasks /create /sc onstart /tn memcached /tr "c:\memcached\memcached.exe -m 512",回车 注意&#xff1a;c:\memcached\memcached.exe替换自己…