Linux字符设备驱动程序

韦东山<嵌入式Linux开发完全手册>第19章读书笔记
一般一个软件系统可分为:应用程序,库,操作系统(内核),驱动程序;开发人员可以专注自己熟悉的部分,对于相邻层,只需要了解他的接口,无需关注它的实现细节;

以点亮一个LED为例,4层软件的协作关系如下:
1、应用程序使用库提供的open函数打开LED的设备文件;
2、库根据open函数传入的参数执行“swi”指令,这条指令会引起CPU异常,进入内核;
3、内核的异常处理函数根据这些参数找到相应的驱动程序,返回一个文件句柄(fd)给库,进而返回给应用程序;
4、应用程序得到文件句柄后,使用库提供的write或ioclt函数发出控制命令;
5、库根据write或ioclt函数传入的参数执行”swi”指令,这条指令会引起CPU异常,进入内核;
6、内核的异常处理函数根据这些参数调用驱动程序的相关函数,点亮LED;
由以上可知,驱动程序不主动运行,它是被动的:根据应用程序的需求进行初始化,根据应用程序的要求进行读写。驱动程序加载进内核时,只是告诉内核”我在这里,我能做这些工作”,至于工作何时开始,取决于应用程序。

拥有MMU的系统能够限制应用程序的权限(比如将它限制于某个内存块中),这可以避免部分应用程序的错误而使得整个系统崩溃。而驱动程序运行于“内核空间”,他是系统“信任”的一部分;

字符设备是能够像字节流(比如文件)一样被访问的设备,就是说对它的读写是以字节为单位的。比如串口收发数据就是一个字节一个字节进行的。

Linux内核就是由各种驱动组成的,内核源码中85%是各种驱动的代码。内核中驱动种类齐全,可以在同类型驱动的基础上进行修改以符合具体单板。

编写驱动程序的难点并不是硬件的具体操作,而是弄清楚现有驱动程序的框架,在这个框架中加入这个硬件。

编写驱动程序还有很多需要注意的地方,比如:驱动程序可能同时被多个进程调用,这就要考虑并发的问题;尽可能发挥硬件的作用以提高性能,如使用DMA等。

编写Linux设备驱动程序的大致流程:
一、查看原理图,数据手册,了解设备的操作方法;
二、在内核中找到相近的驱动程序,以它为模板进行开发,有时候需要从零开始。
三、实现驱动程序的初始化:比如向内核注册这个驱动程序,这样应用程序传入文件名时,内核才能找到相应的驱动程序;
四、设计所要实现的操作,比如open、close、read、write等函数。
五、实现中断服务(中断并不是每个设备驱动所必需的)。
六、编译该驱动程序到内核中,或者用insmod命令加载。
七、测试驱动程序。

Linux 操作系统将所有设备都看成文件,以操作文件的方式访问设备。应用程序不能直接操作硬件,而是使用统一的接口函数调用硬件驱动程序。这组接口被称作系统调用,在库函数中定义。可在glibc中的fcntl.h、unistd.h、sys/ioctl.h等文件中看到如下定义;
extern ssize_t read (int __fd, void *__buf, size_t __nbytes);
extern ssize_t write (int __fd, __const void *__buf, size_t __n);

对于上述每个系统调用,驱动程序都有一个与之对应的函数。对于字符设备驱动程序,这些函数集合在一个file operations类型的数据结构中。file operations结构在Linux内核的include/linux/fs.h文件中定义。

struct file_operations {
struct module *owner;
loff_t (llseek) (struct file , loff_t, int);
ssize_t (read) (struct file , char __user , size_t, loff_t );
ssize_t (write) (struct file , const char __user , size_t, loff_t );
ssize_t (aio_read) (struct kiocb , const struct iovec *, unsigned long, loff_t);
ssize_t (aio_write) (struct kiocb , const struct iovec *, unsigned long, loff_t);
int (readdir) (struct file , void *, filldir_t);
unsigned int (poll) (struct file , struct poll_table_struct *);
int (ioctl) (struct inode , struct file *, unsigned int, unsigned long);
long (unlocked_ioctl) (struct file , unsigned int, unsigned long);
long (compat_ioctl) (struct file , unsigned int, unsigned long);
int (mmap) (struct file , struct vm_area_struct *);
int (open) (struct inode , struct file *);
int (flush) (struct file , fl_owner_t id);
int (release) (struct inode , struct file *);
int (fsync) (struct file , struct dentry *, int datasync);
int (aio_fsync) (struct kiocb , int datasync);
int (fasync) (int, struct file , int);
int (lock) (struct file , int, struct file_lock *);
ssize_t (sendfile) (struct file , loff_t , size_t, read_actor_t, void );
ssize_t (sendpage) (struct file , struct page , int, size_t, loff_t , int);
unsigned long (get_unmapped_area)(struct file , unsigned long, unsigned long, unsigned long, unsigned
long);
int (*check_flags)(int);
int (*dir_notify)(struct file *filp, unsigned long arg);
int (flock) (struct file , int, struct file_lock *);
ssize_t (splice_write)(struct pipe_inode_info , struct file , loff_t , size_t, unsigned int);
ssize_t (splice_read)(struct file , loff_t , struct pipe_inode_info , size_t, unsigned int);
};
换个角度来说,编写字符设备驱动程序就是为具体硬件的file_operation结构编写各个函数(并不需要全部实现file operations结构的成员);

那么,当应用程序通过open,read,write等系统调用访问某个设备文件时,Linux系统怎么知道去调用哪个驱动程序的file operations结构中的open,read,write等文件呢;
1、设备文件有主/次设备号。
2、模块初始化时,将主设备号与file operations结构一起向内核注册。
驱动程序有一个初始化函数,在安装驱动程序时会调用它。在初始化函数中,会将驱动程序的file_operations结构连同其主设备号一起向内核进行注册:
int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops);

编写字符驱动程序的过程大致如下:
1、编写驱动程序初始化函数;进行必要的初始化,包括硬件初始化(也可放其他地方),向内核注册驱动程序等;
2、构造file_operations结构中要用到的各个成员函数。

你可能感兴趣的:(Linux驱动)