logo资料库

Linux字符设备驱动程序编写基本流程.doc

第1页 / 共7页
第2页 / 共7页
第3页 / 共7页
第4页 / 共7页
第5页 / 共7页
第6页 / 共7页
第7页 / 共7页
资料共7页,全文预览结束
Linux 字符设备驱动程序编写基本流程 Linux device driver 的概念 系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器 硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件 设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动 程序是内核的一部分,它完成以下的功能: 1、对设备初始化和释放; 2、把数据从内核传送到硬件和从硬件读取数据; 3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据; 4、检测和处理设备出现的错误。 在 Linux 操作系统下有三类主要的设备文件类型,一是字符设备,二是块设备,三是网 络设备。字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件 I/O 一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请 求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的 I/O 操 作。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的 CPU 时间来等待。 已经提到,用户进程是通过设备文件来与实际的硬件打交道。每个设备文件都都有其文 件属性(c/b),表示是字符设备还是块设备?另外每个文件都有两个设备号,第一个是主设备 号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备, 比如有两个软盘,就可以用从设备号来区分他们。设备文件的的主设备号必须与设备驱动程 序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序。 1.Linux 驱动程序编写基本流程: 1.首先是一些版本信息,没什么用,但是不能少 #define __NO_VERSION__ #include #include char kernel_version[] = UTS_RELEASE; 2. 为 了 把 系 统 调 用 和 驱 动 程 序 关 联 起 来 , 需 要 一 个 非 常 关 键 的 数 据 结 构 : struct file_operations。file_operations 结构的每一个成员的名字都对应着一个系统调用。用户进程 利用系统调用在对设备文件进行诸如 read/write 操作时,系统调用通过设备文件的主设备号 找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函 数。这是 linux 的设备驱动程序工作的基本原理。编写设备驱动程序的主要工作就是编写子 函数,并填充 file_operations 的各个域 3.简单驱动程序的编写(test.c): a.包含一些基本的头文件。 b.编写一些功能函数,比如 read(),write()等。这些函数被调用时系统进入和心态。 c.定义 struct file_operations 结构的对象,填充结构体。结构体中功能的顺序不能改变,若一
些功能没有实现就用 NULL 填充,已经实现的功能如 read()、write()分别添加到对应的位置。 这步实现的是函数的注册。到这里驱动程序的主体可以说是写好了。现在需要把驱动程序嵌 入内核。 d.注册设备驱动程序,使用 register_chrdev 注册字符型设备。函数原型为: int register_chrdev(0, “test_name”, &test_file_operations) 函数返回主设备号,若注册成功返回值大于 0。 第一个参数:主设备号。第二个参数:注册的设备名。第三个参数:结构体名(设备相关操 作方式,驱动程序实际执行操作的函数的指针)。 这个函数由 int init_module(void)函数调用,这个函数在系统启动时注册到内核时调用。 e.在用 rmmod 卸载模块时,cleanup_module 函数被调用,它释放字符设备 test 在系统字符设 备表中占有的表项。 void cleanup_module(void) { unregister_chrdev(test_major, “test”); } 到这里 test.c 基本就编写完成了。一个简单的字符设备可以说写好了。 4.编译 $ gcc -O2 -DMODULE -D__KERNEL__ -c test.o test.c 得到文件 test.o 就是一个设备驱动程序。 如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后 ld -r file1.o file2.o -o modulename 驱动程序已经编译好了,现在把它安装到系统中去。 $ insmod -f test.o 安装成功在/proc/devices 文件中就可以看到设备 test,并可以看到主设备号。要卸载运行: $ rmmod test 5.创建设备节点 mkmod /dev/test c major minor c 是指字符设备,major 是主设备号,minor 是从设备号,一般可以设置为 0 以上就是 Linux 驱动编写的基本过程了,可能有遗漏的地方,这个我只是按我理解的整理的。 具体问题还要在实践中再进行研究。 1.Linux 驱动程序编写基本流程: 1.首先是一些版本信息,没什么用,但是不能少 #define __NO_VERSION__ #include #include 2.为了把系统调用和驱动程序关联起来,需要一 char kernel_version[] = UTS_RELEASE; 个非常关键的数据结构:struct file_operations。file_operations 结构的每一个成员的名字都对 应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如 read/write 操作时,系统 调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数 指针,接着把控制权交给该函数。这是 linux 的设备驱动程序工作的基本原理。编写设备驱 动程序的主要工作就是编写子函数,并填充 file_operations 的各个域 3.简单驱动程序的编写(test.c): a.包含一些基本的头文件。 b.编写一些功能函数,比如 read(),write()等。这些函数被调用时系统进入和心态。
c.定义 struct file_operations 结构的对象,填充结构体。结构体中功能的顺序不能改变,若一 些功能没有实现就用 NULL 填充,已经实现的功能如 read()、write()分别添加到对应的位置。 这步实现的是函数的注册。到这里驱动程序的主体可以说是写好了。现在需要把驱动程序嵌 入内核。 d.注册设备驱动程序,使用 register_chrdev 注册字符型设备。函数原型为: int register_chrdev(0, “test_name”, &test_file_operations) 函数返回主设备号,若注册成功返回值大于 0。 第一个参数:主设备号。第二个参数:注册的设备名。第三个参数:结构体名(设备相关操 作方式,驱动程序实际执行操作的函数的指针)。 这个函数由 int init_module(void)函数调用,这个函数在系统启动时注册到内核时调用。e.在 用 rmmod 卸载模块时,cleanup_module 函数被调用,它释放字符设备 test 在系统字符设备 表中占有的表项。 void cleanup_module(void) { unregister_chrdev(test_major, “test”); } 到这里 test.c 基本就编写完成了。一个简单的字符设备可以说写好了。 $ gcc -O2 -DMODULE -D__KERNEL__ -c test.o test.c 得到文件 test.o 就是一个设备驱动程序。 如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后 ld -r file1.o file2.o -o modulename 驱动程序已经编译好了,现在把它安装到系统中去。 $ insmod -f test.o 安装成功在/proc/devices 文件中就可以看到设备 test,并可以看到主设备号。要卸载运行: $ rmmod test 5.创建设备节点 mkmod /dev/test c major minor c 是指字符设备,major 是主设备号,minor 是从设备号,一般可以设置为 0 以上就是 Linux 驱动编写的基本过程了,可能有遗漏的地方,这个我只是按我理解的整理的。 具体问题还要在实践中再进行研究。 4.编译 二、实例剖析 我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解 Linux 的设备驱动程序的工作原理。把下面的 C 代码输入机器,你就会获得一个真正的设备 驱动程序。 由于用户进程是通过设备文件同硬件打交道,对设备文件的操作方式不外乎就是一些系 统调用,如 open,read,write,close…, 注意,不是 fopen, fread,但是如何把系统调用 和驱动程序关联起来呢?这需要了解一个非常关键的数据结构: struct file_operations { int (*seek) (struct inode * ,struct file *, off_t ,int); int (*read) (struct inode * ,struct file *, char ,int); int (*write) (struct inode * ,struct file *, off_t ,int);
int (*readdir) (struct inode * ,struct file *, struct dirent * ,int); int (*select) (struct inode * ,struct file *, int ,select_table *); int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long); int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *); int (*open) (struct inode * ,struct file *); int (*release) (struct inode * ,struct file *); int (*fsync) (struct inode * ,struct file *); int (*fasync) (struct inode * ,struct file *,int); int (*check_media_change) (struct inode * ,struct file *); int (*revalidate) (dev_t dev); } 这个结构的每一个成员的名字都对应着一个系统调用。用户进程利用系统调用在对设备 文件进行诸如 read/write 操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程 序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。这是 linux 的设备 驱动程序工作的基本原理。既然是这样,则编写设备驱动程序的主要工作就是编写子函数, 并填充 file_operations 的各个域。 下面就开始写子程序。 #include 基本的类型定义 #include 文件系统使用相关的头文件 #include #include #include unsigned int test_major = 0; static int read_test(struct inode *inode,struct file *file,char *buf,int count) { int left; 用户空间和内核空间 if (verify_area(VERIFY_WRITE,buf,count) == -EFAULT ) return -EFAULT; for(left = count ; left > 0 ; left--) { __put_user(1,buf,1); buf++; } return count; } 这个函数是为 read 调用准备的。当调用 read 时,read_test()被调用,它把用户的缓冲区 全部写 1。buf 是 read 调用的一个参数。它是用户进程空间的一个地址。但是在 read_test 被调用时,系统进入核心态。所以不能使用 buf 这个地址,必须用__put_user(),这是 kernel 提供的一个函数,用于向用户传送数据。另外还有很多类似功能的函数。请参考,在向用户 空间拷贝数据之前,必须验证 buf 是否可用。这就用到函数 verify_area。为了验证 BUF 是 否可以用。
static int write_test(struct inode *inode,struct file *file,const char *buf,int count) { return count; } static int open_test(struct inode *inode,struct file *file ) { MOD_INC_USE_COUNT; 模块计数加以,表示当前内核有个设备加载内核当中去 return 0; } static void release_test(struct inode *inode,struct file *file ) { MOD_DEC_USE_COUNT; } 这几个函数都是空操作。实际调用发生时什么也不做,他们仅仅为下面的结构提供函数 指针。 struct file_operations test_fops = {? read_test, write_test, open_test, release_test, }; 设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序可以按照 两种方式编译。一种是编译进 kernel,另一种是编译成模块(modules),如果编译进内核的话, 会增加内核的大小,还要改动内核的源文件,而且不能动态的卸载,不利于调试,所以推荐 使用模块方式。 int init_module(void) { int result; result = register_chrdev(0, "test", &test_fops); 对设备操作的整个接口 if (result < 0) { printk(KERN_INFO "test: can't get major number\n"); return result; } if (test_major == 0) test_major = result; /* dynamic */ return 0; } 在用 insmod 命令将编译好的模块调入内存时,init_module 函数被调用。在这里, init_module 只做了一件事,就是向系统的字符设备表登记了一个字符设备。register_chrdev 需要三个参数,参数一是希望获得的设备号,如果是零的话,系统将选择一个没有被占用的
设备号返回。参数二是设备文件名,参数三用来登记驱动程序实际执行操作的函数的指针。 如果登记成功,返回设备的主设备号,不成功,返回一个负值。 void cleanup_module(void) { unregister_chrdev(test_major,"test"); } 在用 rmmod 卸载模块时,cleanup_module 函数被调用,它释放字符设备 test 在系统字 符设备表中占有的表项。 一个极其简单的字符设备可以说写好了,文件名就叫 test.c 吧。 下面编译 : $ gcc -O2 -DMODULE -D__KERNEL__ -c test.c –c 表示输出制定名,自动生成.o 文件 得到文件 test.o 就是一个设备驱动程序。 如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后 ld ?-r ?file1.o ?file2.o ?-o ?modulename。 驱动程序已经编译好了,现在把它安装到系统中去。 $ insmod ?–f ?test.o 如果安装成功,在/proc/devices 文件中就可以看到设备 test,并可以看到它的主设备号。要 卸载的话,运行 : $ rmmod test 下一步要创建设备文件。 mknod /dev/test c major minor c 是指字符设备,major 是主设备号,就是在/proc/devices 里看到的。 用 shell 命令 $ cat /proc/devices 就可以获得主设备号,可以把上面的命令行加入你的 shell script 中去。
minor 是从设备号,设置成 0 就可以了。 我们现在可以通过设备文件来访问我们的驱动程序。写一个小小的测试程序。 #include #include #include #include main() { int testdev; int i; char buf[10]; testdev = open("/dev/test",O_RDWR); if ( testdev == -1 ) { printf("Cann't open file \n"); exit(0); } read(testdev,buf,10); for (i = 0; i < 10;i++) printf("%d\n",buf[i]); close(testdev); } 编译运行,看看是不是打印出全 1 ? 以上只是一个简单的演示。真正实用的驱动程序要复杂的多,要处理如中断,DMA, I/O port 等问 本 文 来 自 CSDN 博 客 , 转 载 请 标 明 出 处 : http://blog.csdn.net/zhenwenxian/archive/2010/03/28/5424375.aspx
分享到:
收藏