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

加入VIP,免费下载
 

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

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

下载须知

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

版权提示 | 免责声明

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

LED驱动.docx

1、LED驱动Linux系统下的LED驱动(X86平台)1.Linux设备驱动与整个软硬件系统的关系如图所示,除网络设备外,字符设备与块设备都被映射到Linux文件系统的文件和目录,通过文件系统的系统调用接口open()、write()、read()、close()等函数即可访问字符设备和块设备。所有的字符设备和块设备都被统一地呈现给用户。块设备比字符设备复杂,在它上面会首先建立一个磁盘/Flash文件系统,如FAT、Ext3、YAFFS、JFFS等。2. Linux系统下的LED驱动硬件环境:PC104软件环境:使用的系统为红旗Linux Notebook 5.0(内核版本Linux 2.6.1

2、6.9-9smp SMP PENTIUM gcc-3.4) 编译的内核为2.6.16(注意版本的匹配,前面三位要一致)Linux提供了这样一种机制模块。模块具有以下特点:1 模块本身不被编译进内核2 模块一旦被加载,它就和内核中的其他部分完全一样。先看一个最简单的内核模块“Hello World”,代码如下所示#include#includeMODULE_LICENSE(“Dual BSD/GPL”);static int hello_init(void)printk(KERN_ALERT “Hello World entern”);return 0;static void hello_exi

3、t(void)printk(KERN_ALERT “Hello World exitn”);module_init(hello_init);module_eixt(hello_exit);这个最简单的内核模块只包含内核加载函数、卸载函数和对GPL许可权限的声明以及一些描述信息。编译它会产生hello.ko目标文件,通过“insmod ./hello.ko”命令可以加载它,通过“rmmod hello”命令可以卸载它,加载时输出“Hello World enter”,卸载时输出“Hello World exit”。加载完成后可以用modinfo hello.ko命令查看模块的信息,包括作者、模块

4、的说明。模块所支持的参数以及编译这个模块用到的环境信息。从上面这个最简单的例子中可以看出一个Linux内核模块主要由以下几个部分组成。模块加载函数模块卸载函数模块许可证声明前面三个部分是必须的,还有一些可选的,由于这里没有用到,所以留做以后再做说明。模块加载函数必须以“module_init(函数名)”的形式被指定。模块卸载函数必须以“module_exit(函数名)”的形式来指定。2.1 Linux字符设备驱动结构2.1.1. cdev结构体在Linux2.6内核中使用cdev结构体来描述字符设备,cdev结构体的定义代码如下:struct cdevstruct kobject kobj;s

5、truct module *owner;struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;cdev结构体cdev结构体的dev_t成员定义了设备号,为32位,其中高12为主设备号,低20位为次设备号。使用下面宏可以从dev_t获得主设备号和次设备号。MAJOR(dev_t dev)MINOR(dev_t dev)而使用下列宏则可以通过主设备号和次设备号生成dev_tMKDEV(int major,int minor)cdev结构体的另外一个重要成员file_operations定义了字符

6、设备驱动提供给虚拟文件系统的接口函数。Linux2.6 内核提供了一组函数用于操作cdev结构体,就使用了的函数说明一下void cdev_init(struct cdev*,struct file_operations *); 初始化cdev的成员,并建立cdev和file_operations之间的连接int cdev_add(struct cdev*,dev_t,unsigned); 模块加载函数中void cdev_del(struct cdev*); 模块卸载函数中这两个函数是向系统添加和删除一个cdev,完成字符设备的注册和注销。2.1.2. 分配和释放设备号在调用cdev_add

7、()函数向系统注册字符设备之前,应首先调用register_chrdev_region()或者alloc_chrdev_region()函数向系统申请设备号,这两个函数原型如下:int register_chrdev_region(dev_t from,unsigned count,const char*name);用于已知设备的设备号int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char*name);向系统动态申请未被占用的设备号的情况。相反地,在调用dev_del()函数从系统注销字符设

8、备后,unregister_chrdev_region()应该被调用以释放原先申请的设备号,这个函数的原型如下:void unregister_chrdev_region(dev_t from,unsigned count);2.1.3. file_operations 结构体file_operations 结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行Linux的open(),write(),read(),close()等系统调用时 最终被调用。File_operations的数据结构如下:struct module *owner第一个 file_opera

9、tions 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针. 这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为 THIS_MODULE, 一个在 中定义的宏.loff_t (*llseek) (struct file *, loff_t, int);llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一个long offset, 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示. 如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中

10、的位置计数器( 在file 结构 一节中描述).ssize_t (*read) (struct file *, char _user *, size_t, loff_t *);用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL(Invalid argument) 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 signed size 类型, 常常是目标平台本地的整数类型).ssize_t (*aio_read)(struct kiocb *, char _user *, size_t, loff_t);初始化一个异步读 - 可能在函数返回前不

11、结束的读操作. 如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地).ssize_t (*write) (struct file *, const char _user *, size_t, loff_t *);发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.ssize_t (*aio_write)(struct kiocb *, const char _user *, size_t, loff_t *);初始化设备上的一个异步写.int (*readdir) (struct file *,

12、 void *, filldir_t);对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对文件系统有用.unsigned int (*poll) (struct file *, struct poll_table_struct *);poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞. poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到 I/O 变为可能. 如果一个驱动的 poll 方法为 NULL, 设备假定为不阻塞地可

13、读可写.int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表. 如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, 设备无这样的 ioctl), 系统调用返回一个错误.int (*mmap) (struct file *, struct vm_area_struct *);mmap 用来请求将设备内存映射到进程

14、的地址空间. 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.int (*open) (struct inode *, struct file *);尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.int (*flush) (struct file *);flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作. 这个必须不要和用户查询请求的 fsync 操作混淆了. 当前, flush 在很少驱动中使用; SCSI 磁带驱动使用

15、它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush 为 NULL, 内核简单地忽略用户应用程序的请求.int (*release) (struct inode *, struct file *);在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.int (*fsync) (struct file *, struct dentry *, int);这个方法是 fsync 系统调用的后端, 用户调用来刷新任何挂着的数据. 如果这个指针是 NULL, 系统调用返回 -EINVAL.int (*aio_fsync)(struct kiocb

16、*, int);这是 fsync 方法的异步版本.int (*fasync) (int, struct file *, int);这个操作用来通知设备它的 FASYNC 标志的改变. 异步通知是一个高级的主题, 在第 6 章中描述. 这个成员可以是NULL 如果驱动不支持异步通知.int (*lock) (struct file *, int, struct file_lock *);lock 方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实现它.ssize_t (*readv) (struct file *, const struct iovec *, uns

17、igned long, loff_t *);ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);这些方法实现发散/汇聚读和写操作. 应用程序偶尔需要做一个包含多个内存区的单个读或写操作; 这些系统调用允许它们这样做而不必对数据进行额外拷贝. 如果这些函数指针为 NULL, read 和 write 方法被调用( 可能多于一次 ).ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);这个方法

18、实现 sendfile 系统调用的读, 使用最少的拷贝从一个文件描述符搬移数据到另一个. 例如, 它被一个需要发送文件内容到一个网络连接的 web 服务器使用. 设备驱动常常使 sendfile 为 NULL.ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);sendpage 是 sendfile 的另一半; 它由内核调用来发送数据, 一次一页, 到对应的文件. 设备驱动实际上不实现 sendpage.unsigned long (*get_unmapped_area)(struct f

19、ile *, unsigned long, unsigned long, unsigned long, unsigned long);这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中. 这个任务通常由内存管理代码进行; 这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求. 大部分驱动可以置这个方法为 NULL.int (*check_flags)(int)这个方法允许模块检查传递给 fnctl(F_SETFL.) 调用的标志.int (*dir_notify)(struct file *, unsigned long);这个方法在应用程序使用 fcntl 来

20、请求目录改变通知时调用. 只对文件系统有用; 驱动不需要实现 dir_notify.2.2 Linux字符设备驱动的组成2.2.1 字符设备驱动模块加载与卸载函数在字符设备驱动模块加载函数中应该实现设备号的申请和cdev的注册,而在卸载函数中应实现设备号的释放和cdev的注册。习惯上将设备定义为一个设备相关的结构体,其中包括该设备所涉及的cdev、私有数据及信号量等信息。设备结构体struct led_dev_tstruct cdev cdev;.led_dev;设备驱动模块加载函数static int _init led_init(void)cdev_init(&led_dev.cdev,&

21、led_fops);/初始化cdevled_dev.cdev.owner = THIS_MODULE;获取字符设备号if(led_major)register_chrdev_region(led_dev_no,1,DEV_NAME);elsealloc_chrdev_region(&led_dev_no,0,1,DEV_NAME);ret=cdev_add(&led_dev.cdev,led_dev_no,1);设备驱动模块卸载函数static void _exit led_exit(void)unregister_chrdev_region(led_dev_no,1); /释放占用的设备号c

22、dev_del(&led_dev.cdev);2.2.2 字符设备驱动的file_operation结构体中成员函数大多数字符设备驱动会实现read()、write()、和ioctl()函数,常见的字符设备驱动的这三个函数的形式如代码清单所示。读设备ssize_t led_read(struct file *filp,char _user *buf,size_t count,loff_t *f_pos)copy_to_user(buf,);写设备ssize_t led_write(struct file*filp,const char _user*buf,size_t count,loff_t

23、*f_pos)copy_from_user(,buf,);ioctl函数int led_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg)switch(cmd)case LED_CMD1:break;case LED_CMD2:break;default:return ENOTTY;return 0;设备驱动的读函数中,filp是文件结构体指针,buf是用户空间内存的地址,该地址在内核空间不能直接读写,count是要读的字节数,f_pos是读的位置相对于文件开头的偏移。由于内核空间与用户

24、空间的内存不能直接互访,因此借助函数copy_from_user()完成用户空间到内核空间的复制,函数cop_to_user()完成内核空间到用户空间的复制。I/O控制函数的cmd参数为事先定义的I/O控制命令,而arg为对应于该命令的参数。ls -l在字符设备驱动中,需要定义一个file_operations的实例,并将具体设备驱动的函数赋值给file_operations的成员,代码如下struct file_operations led_fops=.owner = THIS_MODULE,.read = led_read,.write = led_write,.ioctl = led_i

25、octl,;上述led_fops 在语句cdev_init($led_dev.cdev,&led_fops)中建立与cdev的连接。2.3 LED设备驱动在LED字符驱动中,应包含它要使用的头文件,并定义LED设备结构体及相关宏#include#include#include#include#include#include#include#include#include#include#include#include#include#include#define LIGHT_MAJOR 250 设置led的主设备号#define LIGHT_ON 0x1#define LIGHT_OFF 0x

26、2led设备结构体struct light_devstruct cdev cdev; 字符设备cdev结构体unsigned char value; LED亮时为1 熄灭时为0,用户可读写此值;int port;void set_light(void) port=check_region(0x378, 1); if(port=0) printk(The ports are available in that range.n); request_region(0x378,1,LED); else printk(The port cannot reserve 0x378n);void light_

27、on(void)outb(0x1,0x378);void light_off(void)outb(0x0,0x378);struct light_dev *light_devp;int light_major = LIGHT_MAJOR;MODULE_LICENSE(Dual BSD/GPL);文件打开函数int light_open(struct inode *inode,struct file *filp)struct light_dev *dev;获得设备结构指针dev = container_of(inode-i_cdev,struct light_dev,cdev);让设备结构体作为

28、设备的私有信息filp-private_data = dev;return 0;文件释放函数int light_release(struct inode *inode,struct file *filp)return 0;读写函数ssize_t light_read(struct file *filp,char _user *buf,size_t count,loff_t*f_pos)struct light_dev *dev = filp-private_data;if(copy_to_user(buf,&(dev-value),1)return - EFAULT;return 1;ssiz

29、e_t light_write(struct file *filp,const char _user*buf,size_t count,loff_t *f_pos)struct light_dev *dev = filp-private_data; 获得设备结构体if(copy_from_user(&(dev-value),buf,1)return - EFAULT;根据写入的值点亮和熄灭LEDif(dev-value = 1)light_on();elselight_off();return 1;int light_ioctl(struct inode *inode,struct file

30、*filp,unsigned int cmd,unsigned long arg)struct light_dev *dev = filp-private_data;switch(cmd)case LIGHT_ON:dev-value = 1;light_on();break;case LIGHT_OFF:dev-value = 0;light_off();break;default:return - ENOTTY;return 0;led设备驱动文件操作结构体struct file_operations light_fops =.owner = THIS_MODULE,.read = light_read,

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

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