Char drivers [LDD3 03]

本章开始讲解char device drivers.

在linux中设备可通过文件进行访问,在/dev目录下,有很多device,通过ls -l可以看到这些device有major/minor number。majoy number用来表明该device和哪个driver关联,minor number被kernel用来确定当前被使用的是哪个device,因为同一个driver可以驱动多个device,每个device share同样的major number,但是具有不同的minor number。

major number可以在driver中指定,也可以在runtime动态的分配。如果希望静态获取,可以调用register_chrdev_region,参数中指定major numbr和range,kernel来决定是否满足driver的请求;也可以通过alloc_chrdev_region的方式动态申请。需要释放的时候,调用unregister_chrdev_region。因为静态分配的方式有可能失败,有很多major number是被占用的,最好通过动态分配的方式来获取major number。

下面是一些重要的数据结构:

1. File Operations

file operations用来把device number和device driver关联起来。file operations里面是各种各样的函数指针,包含了可以对device进行的各种操作,比如open/close/mmap等,都会在file operations里有对应的实现。下面是file operations里具体的member:

a) struct module *owner

指向当前的module,一般被初始化成THIS_MODULE,用来防止driver正在被使用的时候被unload。

b) loff_t (*llseek) (struct file *, lofft_t, int);

用来改变文件中当前的读/写位置,新的位置作为返回值。lofft_t即便在32位系统中也是64bits的值。如果出错,返回负值。如果这个函数为NULL,seek将会以难以预测的方式运行。

c) ssize_t (*read) (struct file *, char __user *, size_t, loff_t *)

扫描二维码关注公众号,回复: 10609807 查看本文章

用来读取设备中的数据。如果为NULL,read将会返回-EINVAL。如果读取成功,返回成功读取数据的长度。

d) ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t *)

异步读数据,函数返回时,数据可能还没读取完成。如果为NULL,kernel调用read实现。

e) ssize_t (*write) (struct file *, char __user *, size_t, loff_t *)

向设备写数据,如果为NULL,write调用直接返回-EINVAL。如果成功,返回写入的数据长度。

f) ssize_t (*aio_write) (struct kiocb *, char __user *, size_t, loff_t *)

异步写数据,函数返回时,数据可能还没写完。

g) int (*readdir) (struct file *, void *, filldir_t)

设备文件为NULL,文件系统才需要使用readdir。

h) unsigned int (*poll) (struct file *, struct poll_table_struct *)

设备驱动中的poll对应用户态的三个系统调用:poll, epoll 和 select,都是用来查询针对一个或者多个file descriptor的读写操作是否能以non-block的方式完成,返回值是bit mask,这个function在读写的资源不可用时,可以把当前的process设成睡眠,直到请求的资源可用。如果poll为NULL,表示读写都是block的方式。

i) int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

ioctl用于实现device driver特定的一些操作,如果为NULL,不能被kernel识别的ioctl就会返回-ENOTTY.

j) int (*mmap) (struct file *, struct vm_area_struct *);

mmap可以把device的memory映射到当前的进程地址空间中,并在用户态进行直接访问。如果为NULL,调用返回-ENODEV.

k) int (*open) (struct inode *, struct file *);

当device被打开时,open被调用,可以在open里初始化device driver相关的内容。也可以不实现,如果为NULL,相当于device被打开时,driver没有被通知。

l) int (*flush) (struct file *);

当进程关闭了device的fd时,flush被调用,用于将未完成或者pending的操作做完,device driver用的很少了。

m) int (*release) (struct inode *, struct file *);

当file被释放时,release函数会被调用,也可以为NULL。

n)int (*fsync) (struct file *, struct dentry *, int);

承接user mode的fsync,用于flush data。

o)int (*aio_fsync)(struct kiocb *, int);

异步的fsync。

p)int (*fasync) (int, struct file *, int);

如果device的FASYNC flag发生变化,这个函数被调用。可以为NULL。

剩下的一些function这里不再列,因为device driver已经很少会用到。

2. File Structure

struct file,定义在<linux/fs.h>,代表一个被打开的文件,系统里的任何文件被打开,都会有一个对应的file struct。当kernel的open被调用时,file struct就会创建,直到file被引用的instance变为0,file struct就会被释放,通常kernel里看到的filp就是指向file structure的指针。

3. inode structure

inode是kernel内部使用,用以表示file。一个file可以被多次打开,因此可能有多个file structure,但是都对应同一个inode。

a)dev_t i_rdev;

包含了真正的device number。但是不推荐直接读取,应该使用kernel提供的macro来获取device major/minor number:

unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);

b)struct cdev *i_cdev;

如果一个inode对应一个char device file,i_cdev就指向那个char device。

重要的数据结构介绍完毕,后面就是device driver和kernel交互的一些流程。

Char Device Registration

cdev用来代表一个char device,在使用它之前,需要driver自己创建并注册device。首先cdev结构体的创建,有两种方式:1, 由kerne动态帮我们分配一个,通过cdev_alloc,driver提供ops;2, 我们自己创建cdev,然后调用cdev_init让kernel帮我们初始化。现在大部分的device driver都是采用第二种方式,自己创建一个结构体,里面包含cdev,然后通过cdev_init把cdev初始化,方式很灵活。在cdev创建完成,并且ops等初始化做完以后,调用cdev_add,将cdev告诉kernel,同时告诉kernel与此关联的device number,这样就能把我们的cdev结构体,与filesystem里的device关联起来。需要注意的时,一旦调用了cdev_add,就代表device对应的功能已经ready,kernel会随时调用进来,所以必须能准备工作都做完以后,再调用cdev_add。如果需要移除设备,调用cdev_del。

Open and Release

driver里的open,是在对应的device设备文件被打开的时候,此时通常做一些初始化设备的操作。在open被调用进来的时候,首先得知道是哪个device。在open传进来的inode里,记录着i_cdev,这个就是driver之间创建并初始化过的cdev,直接container_of来获取对应的scull_dev即可。然后可以把scull_dev记录在filp->private_data里。因为后续的操作,比如read/write/ioctl都是只有filp参数,通过filp->private_data可以直接拿到scull_dev,从而拿到driver的所有东西。另外,查看哪个设备被打开,需要查看inode里的minor number,这里不再赘述。

driver里的release,简单来说就是把open获取 资源对应的释放掉。具体的:1, 释放open中分配的资源,尤其是记录在filp->private_data里的东西;2, 如果device被彻底close,就关闭设备。这里提到,dup和fork不会调用到device的open,这些都被OS处理掉了,并且自己维护了一个计数,每次dup和fork都是增加了计数而已。同样的,上层调用close的时候,OS只是减少了计数,等到计数为0,才会调用device的release。因此,driver的open和release会是一一对应的,一次open必会只对应一次的release。当user mode app退出,包括异常退出,process在被终止的时候,kernel都是针对open的file调用close,这样保证不会发生leak。

后面开始将scull的实现细节,这里不再讨论。

这里注意,read/write调用进来的时候,buffer都是user mode的,也就说这些指针都是用户态指针,kernel不能直接使用,需要通过copy_from_user/copy_to_user来转换。这两个函数中:1,会对user mode地址进行检查,如果无效,函数会返回未被copy的字节数;2, 如果对应的user page不存在,会调用page fault,所以使用这两个函数的地方要支持可重入。

发布了32 篇原创文章 · 获赞 6 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/scutth/article/details/105197042