ImageVerifierCode 换一换
格式:DOCX , 页数:64 ,大小:259.70KB ,
资源ID:22703984      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/22703984.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(深入浅出驱动开发Word格式.docx)为本站会员(b****8)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

深入浅出驱动开发Word格式.docx

1、二项(如果本文的例程要在特定的嵌入式系统中运行,还应安装相应的交叉编译器,并包含相应的Linux源代码),如下图:深入浅出Linux设备驱动编程之内核模块Linux设备驱动属于内核的一部分,Linux内核的一个模块可以以两种方式被编译和加载:(1)直接编译进Linux内核,随同Linux启动时加载;(2)编译成一个可加载和删除的模块,使用insmod加载(modprobe和insmod命令类似,但依赖于相关的配置文件),rmmod删除。这种方式控制了内核的大小,而模块一旦被插入内核,它就和内核其他部分一样。下面我们给出一个内核模块的例子:#include /所有模块都需要的头文件linux/i

2、nit.h / init&exit相关宏MODULE_LICENSE(GPL);static int _init hello_init (void)printk(Hello module initnreturn 0;static void _exit hello_exit (void)Hello module exitnmodule_init(hello_init);module_exit(hello_exit);分析上述程序,发现一个Linux内核模块需包含模块初始化和模块卸载函数,前者在insmod的时候运行,后者在rmmod的时候运行。初始化与卸载函数必须在宏module_init和mo

3、dule_exit使用前定义,否则会出现编译错误。程序中的MODULE_LICENSE()用于声明模块的许可证。 如果要把上述程序编译为一个运行时加载和删除的模块,则编译命令为:gcc -D_KERNEL_ -DMODULE -DLINUX -I /usr/local/src/linux2.4/include -c -o hello.o hello.c由此可见,Linux内核模块的编译需要给gcc指示-D_KERNEL_ -DMODULE -DLINUX参数。-I选项跟着Linux内核源代码中Include目录的路径。下列命令将可加载hello模块:insmod ./hello.o下列命令完成

4、相反过程:rmmod hello如果要将其直接编译入Linux内核,则需要将源代码文件拷贝入Linux内核源代码的相应路径里,并修改Makefile。我们有必要补充一下Linux内核编程的一些基本知识:内存在Linux内核模式下,我们不能使用用户态的malloc()和free()函数申请和释放内存。进行内核编程时,最常用的内存申请和释放函数为在include/linux/kernel.h文件中声明的kmalloc()和kfree(),其原型为:void *kmalloc(unsigned int len, int priority);void kfree(void *_ptr);kmalloc

5、的priority参数通常设置为GFP_KERNEL,如果在中断服务程序里申请内存则要用GFP_ATOMIC参数,因为使用GFP_KERNEL参数可能会引起睡眠,不能用于非进程上下文中(在中断中是不允许睡眠的)。由于内核态和用户态使用不同的内存定义,所以二者之间不能直接访问对方的内存。而应该使用Linux中的用户和内核态内存交互函数(这些函数在include/asm/uaccess.h中被声明):unsigned long copy_from_user(void *to, const void *from, unsigned long n);unsigned long copy_to_user

6、 (void * to, void * from, unsigned long len);copy_from_user、copy_to_user函数返回不能被复制的字节数,因此,如果完全复制成功,返回值为0。include/asm/uaccess.h中定义的put_user和get_user用于内核空间和用户空间的单值交互(如char、int、long)。这里给出的仅仅是关于内核中内存管理的皮毛,关于Linux内存管理的更多细节知识,我们会在本文第9节内存与I/O操作进行更加深入地介绍。输出在内核编程中,我们不能使用用户态C库函数中的printf()函数输出信息,而只能使用printk()。但

7、是,内核中printk()函数的设计目的并不是为了和用户交流,它实际上是内核的一种日志机制,用来记录下日志信息或者给出警告提示。每个printk都会有个优先级,内核一共有8个优先级,它们都有对应的宏定义。如果未指定优先级,内核会选择默认的优先级DEFAULT_MESSAGE_LOGLEVEL。如果优先级数字比int console_loglevel变量小的话,消息就会打印到控制台上。如果syslogd和klogd守护进程在运行的话,则不管是否向控制台输出,消息都会被追加进/var/log/messages文件。klogd 只处理内核消息,syslogd 处理其他系统消息,比如应用程序。模块参数

8、2.4内核下,include/linux/module.h中定义的宏MODULE_PARM(var,type) 用于向模块传递命令行参数。var为接受参数值的变量名,type为采取如下格式的字符串min-maxb,h,i,l,s。min及max用于表示当参数为数组类型时,允许输入的数组元素的个数范围;b:byte;h:short;i:int;l:long;s:string。在装载内核模块时,用户可以向模块传递一些参数:insmod modname var=value如果用户未指定参数,var将使用模块内定义的缺省值。深入浅出Linux设备驱动之字符设备驱动程序Linux下的设备驱动程序被组织为

9、一组完成不同任务的函数的集合,通过这些函数使得Windows的设备操作犹如文件一般。在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作,如open ()、close ()、read ()、write () 等。Linux主要将设备分为二类:字符设备和块设备。字符设备是指设备发送和接收数据以字符的形式进行;而块设备则以整个数据缓冲区的形式进行。字符设备的驱动相对比较简单。下面我们来假设一个非常简单的虚拟字符设备:这个设备中只有一个4个字节的全局变量int global_var,而这个设备的名字叫做gobalvar。对设备的读写等操作即是对其中全局变量glo

10、bal_var的操作。驱动程序是内核的一部分,因此我们需要给其添加模块初始化函数,该函数用来完成对所控设备的初始化工作,并调用register_chrdev() 函数注册字符设备:static int _init gobalvar_init(void)if (register_chrdev(MAJOR_NUM, gobalvar , &gobalvar_fops)/注册失败else/注册成功其中,register_chrdev函数中的参数MAJOR_NUM为主设备号,为设备名,gobalvar_fops为包含基本函数入口点的结构体,类型为file_operations。当gobalvar模块被

11、加载时,gobalvar_init被执行,它将调用内核函数register_chrdev,把驱动程序的基本入口点指针存放在内核的字符设备地址表中,在用户进程对该设备执行系统调用时提供入口地址。与模块初始化函数对应的就是模块卸载函数,需要调用register_chrdev()的反函数 unregister_chrdev():static void _exit gobalvar_exit(void)if (unregister_chrdev(MAJOR_NUM, )/卸载失败/卸载成功随着内核不断增加新的功能,file_operations结构体已逐渐变得越来越大,但是大多数的驱动程序只是利用了其

12、中的一部分。对于字符设备来说,要提供的主要入口有:open ()、release ()、read ()、write ()、ioctl ()、llseek()、poll()等。open()函数对设备特殊文件进行open()系统调用时,将调用驱动程序的open () 函数:int (*open)(struct inode * ,struct file *);其中参数inode为设备特殊文件的inode (索引结点) 结构的指针,参数file是指向这一设备的文件结构的指针。open()的主要任务是确定硬件处在就绪状态、验证次设备号的合法性(次设备号可以用MINOR(inode- i - rdev)

13、取得)、控制使用设备的进程数、根据执行情况返回状态码(0表示成功,负数表示存在错误) 等;release()函数当最后一个打开设备的用户进程执行close ()系统调用时,内核将调用驱动程序的release () 函数:void (*release) (struct inode * ,struct file *) ;release 函数的主要任务是清理未结束的输入/输出操作、释放资源、用户自定义排他标志的复位等。read()函数当对设备特殊文件进行read() 系统调用时,将调用驱动程序read() 函数:ssize_t (*read) (struct file *, char *, size

14、_t, loff_t *);用来从设备中读取数据。当该函数指针被赋为NULL 值时,将导致read 系统调用出错并返回-EINVAL(Invalid argument,非法参数)。函数返回非负值表示成功读取的字节数(返回值为signed size数据类型,通常就是目标平台上的固有整数类型)。globalvar_read函数中内核空间与用户空间的内存交互需要借助第2节所介绍的函数:static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)copy_to_user(buf, &global

15、_var, sizeof(int);write( ) 函数当设备特殊文件进行write () 系统调用时,将调用驱动程序的write () 函数:ssize_t (*write) (struct file *, const char *, size_t, loff_t *);向设备发送数据。如果没有这个函数,write 系统调用会向调用程序返回一个-EINVAL。如果返回值非负,则表示成功写入的字节数。globalvar_write函数中内核空间与用户空间的内存交互需要借助第2节所介绍的函数:static ssize_t globalvar_write(struct file *filp, c

16、onst char *buf, size_t len, loff_t*off)copy_from_user(&global_var, buf, sizeof(int);ioctl() 函数该函数是特殊的控制函数,可以通过它向设备传递控制信息或从设备取得状态信息,函数原型为:int (*ioctl) (struct inode * ,struct file * ,unsigned int ,unsigned long);unsigned int参数为设备驱动程序要执行的命令的代码,由用户自定义,unsigned long参数为相应的命令提供参数,类型可以是整型、指针等。如果设备不提供ioctl

17、入口点,则对于任何内核未预先定义的请求,ioctl 系统调用将返回错误(-ENOTTY,No such ioctl fordevice,该设备无此ioctl 命令如果该设备方法返回一个非负值,那么该值会被返回给调用程序以表示调用成功。llseek()函数 该函数用来修改文件的当前读写位置,并将新位置作为(正的)返回值返回,原型为:loff_t (*llseek) (struct file *, loff_t, int);poll()函数 poll 方法是poll 和select 这两个系统调用的后端实现,用来查询设备是否可读或可写,或是否处于某种特殊状态,原型为:unsigned int (*

18、poll) (struct file *, struct poll_table_struct *);我们将在设备的阻塞与非阻塞操作一节对该函数进行更深入的介绍。设备的驱动程序的这些函数应分别命名为gobalvar_open、gobalvar_ release、gobalvar_read、gobalvar_write、gobalvar_ioctl,因此设备的基本入口点结构变量gobalvar_fops 赋值如下:struct file_operations gobalvar_fops = read: gobalvar_read,write: gobalvar_write,;上述代码中对gobal

19、var_fops的初始化方法并不是标准C所支持的,属于GNU扩展语法。完整的globalvar.c文件源代码如下:linux/fs.hasm/uaccess.h#define MAJOR_NUM 254 /主设备号static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*);static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*);/初始化字符设备驱动的file_operations结构体struct file_opera

20、tions globalvar_fops = globalvar_read, write: globalvar_write,static int global_var = 0; /globalvar设备的全局变量static int _init globalvar_init(void)int ret;/注册设备驱动ret = register_chrdev(MAJOR_NUM, globalvar_fops);if (ret)printk(globalvar register failureglobalvar register successreturn ret;static void _ex

21、it globalvar_exit(void)/注销设备驱动ret = unregister_chrdev(MAJOR_NUM, globalvar unregister failureglobalvar unregister success/将global_var从内核空间复制到用户空间if (copy_to_user(buf, &global_var, sizeof(int)return - EFAULT; return sizeof(int);/将用户空间的数据复制到内核空间的global_varif (copy_from_user(&global_var, buf, sizeof(in

22、t)module_init(globalvar_init);module_exit(globalvar_exit);运行:gcc -D_KERNEL_ -DMODULE -DLINUX -I /usr/local/src/linux2.4/include -c -o globalvar.o globalvar.c编译代码,运行:inmod globalvar.o加载globalvar模块,再运行:cat /proc/devices发现其中多出了254 globalvar一行,如下图:接着我们可以运行:mknod /dev/globalvar c 254 0创建设备节点,用户进程通过/dev/g

23、lobalvar这个路径就可以访问到这个全局变量虚拟设备了。我们写一个用户态的程序globalvartest.c来验证上述设备:sys/types.h /访问_LP64和_ILP32的定义,还包含适当时使用的多个基本派生类型:clock_t,dev_t,off_t,ptrdiff,size_t,ssize_t,time_tsys/stat.h /获得文件的属性,它可以返回一个结构,里面包含文件全部属性stdio.hfcntl.h /设备驱动程序接口是由结构说明,它定义在fcntl.h中main()int fd, num;/打开/dev/globalvarfd = open(, O_RDWR, S_IRUSR | S_IWUSR); /这个函数就是告诉系统,应用程 序要以读写的方式打开/dev/globalvar这个设备, 然后系统再查找相应的驱动程序内的open函数打开设备if (fd != -1 )/初次读globalvarread(fd, &num, sizeof(int);printf(The globalvar is %dn, num);/写globalvar

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1