嵌入式学院第期Linux字符设备驱动.ppt
嵌入式学院第期Linux字符设备驱动 Still waters run deep.流静水深流静水深,人静心深人静心深 Where there is life,there is hope。有生命必有希望。有生命必有希望2版权华华清清远见远见嵌入式培嵌入式培训训中心版中心版权权所有;所有;未未经华经华清清远见远见明确明确许许可,不能可,不能为为任何目的以任何形式复制任何目的以任何形式复制或或传传播此文档的任何部分;播此文档的任何部分;本文档包含的信息如有更改,恕不另行通知;本文档包含的信息如有更改,恕不另行通知;保留所有保留所有权权利。利。www.embedu.org Linux驱动程序基础知识 LinuxLinux字符设备驱动基础字符设备驱动基础 三种重要数据结构及关系三种重要数据结构及关系 file_oprationsfile_oprations结构分析结构分析 用户空间与内核空间数据传输用户空间与内核空间数据传输基本字符设备驱动函数模板www.embedu.org设备驱动程序基础Linux设备驱动概念驱动程序为操作硬件提供良好内部接口驱动程序为应用程序提供了访问设备的机制Linux设备驱动分类字符设备:键盘、鼠标、串口块设备:硬盘、Flash 网络接口:以太网特定类型设备:audio设备www.embedu.orgLinux内核功能的划分www.embedu.org系统调用和设备I/O www.embedu.org驱动程序的两大任务作为系统调用的一部分而执行,运行在进程上下文。负责中断处理,运行在中断上下文www.embedu.org设备驱动健壮性和安全性机制和策略的折衷驱动程序是内核的一部分驱动程序的漏洞和缺陷直接危及内核留心未初始化的指针,恶意用户程序,缓冲区溢出www.embedu.org驱动程序与内核版本号Linux内核版本号简述 2.0.x 2.2.x 2.4.x 2.6.x版本号在内核编译过程中的影响模块加载时的版本号检查www.embedu.orgLinux下构建和运行模块为什么用模块?模块和应用程序有什么不同#ifdef _KERNEL_#ifdef MODULEwww.embedu.orgLinux驱动程序模块加载 2.4内核:www.embedu.orgLinux驱动程序模块加载2.6内核:www.embedu.org模块的版本依赖版本号定义可以区分不同版本内核的接口函数可以在linux/version.h找到版本定义KERNEL_VERSIONwww.embedu.org模块初始化和关闭模块加载调用的第一个函数init_module模块所使用资源的分配与释放使用计数模块卸载和cleanup_module显式指定初始化和清除函数www.embedu.org一个简单的Linux内核模块 1#include 2#include 3 MODULE_LICENSE(Dual BSD/GPL);4 static int hello_init(void)5 6 printk(KERN_ALERT Hello World entern);7 return 0;8 9 static void hello_exit(void)10 11 printk(KERN_ALERT Hello World exitn);12 13 module_init(hello_init);14 module_exit(hello_exit);15 16 MODULE_AUTHOR(Song Baohua);17 MODULE_DESCRIPTION(A simple Hello World Module);18 MODULE_ALIAS(a simplest module);www.embedu.orgLinux内核模块的程序结构 模块加载函数(必须)模块卸载函数(必须)模块许可证声明(必须)大多数情况下,内核模块应遵循GPL兼容许可权。Linux 2.6内核模块最常见的是以MODULE_LICENSE(Dual BSD/GPL)语句声明模块采用BSD/GPL双LICENSE。模块参数(可选)。模块导出符号(可选)模块作者等信息声明(可选)www.embedu.org模块参数 module_param(参数名,参数类型,参数读/写权限)module_param(myshort,short,0000);MODULE_PARM_DESC(myshort,A short integer);module_param(myint,int,0000);MODULE_PARM_DESC(myint,An integer);#insmod hello.ko myshort=55 myint=456www.embedu.org导出符号 Linux 2.6的“/proc/kallsyms”文件对应着内核符号表,它记录了符号以及符号所在的内存地址。模块可以使用如下宏导出符号到内核符号表:EXPORT_SYMBOL(符号名);EXPORT_SYMBOL_GPL(符号名);www.embedu.org模块的使用计数 Linux 2.4内核:MOD_INC_USE_COUNT、MOD_DEC_USE_COUNT Linux 2.6内核:try_module_get(&module)该函数用于增加模块使用计数 module_put(&module)该函数用于减少模块使用计数 www.embedu.orgLinux内核与模块中的并发可重入简介共享资源带来的问题并发与竞态抢占式内核对并发的影响www.embedu.org产生并发的三种情况对称多处理器(SMP)多个CPUwww.embedu.org产生并发的三种情况单CPU内进程与抢占它的进程中断与进程之间www.embedu.org解决并发的途径互斥访问临界区(critical section)www.embedu.org模块的编译和装载编译模块都需要什么编译模块相关的宏模块工具insmod,rmmod,lsmod,modprobe,modinfowww.embedu.org Linux驱动程序基础知识 LinuxLinux字符设备驱动基础字符设备驱动基础 三种重要数据结构及关系三种重要数据结构及关系 file_oprationsfile_oprations结构分析结构分析 用户空间与内核空间数据传输用户空间与内核空间数据传输基本字符设备驱动函数模板www.embedu.orgLinux字符设备驱动基础驱动注册与初始化主设备号和次设备号设备名设备文件节点操作与file operation结构用户系统调用与驱动函数集合www.embedu.org设备名与主次设备号字符设备文件例子crw-rw-1 root uucp 4,64 2005-03-20 03:36/dev/ttyS0主设备号区分设备驱动程序次设备号区分同一个驱动程序创建的多个设备常见于多个串口,硬盘分区等mknod 创建设备文件:mknod /dev/mydevice c 254 0open、close等操作/dev/下设备文件,内核根据文件的主设备号找到对应驱动程序主设备号可以分为动态、静态申请www.embedu.org动态分配主设备号alloc_chrdev_region注册动态主设备号(2.6)动态分配主设备号的优缺点/proc/devices和lsmod动态生成设备文件系统节点dev_t和kdev_twww.embedu.orgfile_opration结构分析file 在内核中定义linux/fs.hfile struct Mode_t f_modeLoff_t f_posUnsigned int f_flagsStruct file_operations*f_opvoid*private_dataStruct dentry*f_dentrywww.embedu.org Linux驱动程序基础知识 LinuxLinux字符设备驱动基础字符设备驱动基础 三种重要数据结构及关系三种重要数据结构及关系 file_oprationsfile_oprations结构分析结构分析 用户空间与内核空间数据传输用户空间与内核空间数据传输基本字符设备驱动函数模板www.embedu.org三种重要数据结构及关系include/linux/fs.hstruct 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*);unsigned int(*poll)(struct file*,struct poll_table_struct*);int(*ioctl)(struct inode*,struct file*,unsigned int,unsigned long);int(*mmap)(struct file*,struct vm_area_struct*);int(*open)(struct inode*,struct file*);int(*release)(struct inode*,struct file*);定义了针对文件的一系列操作方法定义了针对文件的一系列操作方法www.embedu.org三种重要数据结构及关系include/linux/fsstruct file mode_t f_mode;loff_t f_ops;unsigned int f_flags;struct file_operations *f_op;void *private_data;struct dentry *f_dentry;系统中每个打开的文件在内核空间都有一个对应的系统中每个打开的文件在内核空间都有一个对应的file结构结构www.embedu.org三种重要数据结构及关系include/linux/fs.hstruct inode dev_t i_rdev;/包含真正的设备编号struct cdev *i_cdev;/指向cdev结构的指针;inode结构体用于描述文件的静态属性结构体用于描述文件的静态属性,每个文件对应一个每个文件对应一个唯一的唯一的inode结构结构www.embedu.orgfile_opration结构分析file_operations 在内核中定义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*);unsigned int(*poll)(struct file*,struct poll_table_struct*);int(*ioctl)(struct inode*,struct file*,unsigned int,unsigned long);int(*mmap)(struct file*,struct vm_area_struct*);int(*open)(struct inode*,struct file*);int(*release)(struct inode*,struct file*);int(*fsync)(struct file*,struct dentry*,int datasync);www.embedu.orgfile_operationsint(*open)(struct inode*,struct file*)增加使用计数,检查错误如果未初始化,则调用初始化识别次设备号,如果必要,更新f_op指针分配并填写被置于filp-private_data的数据结构int(*realse)(struct inode*,struct file*)open逆操作www.embedu.orgfile_operationsssize_t(*read)(struct file*,char*,size_t,loff_t*)用户空间和内核空间数据交互用户空间指针和内核指针驱动调用copy_to_user()将数据返回给用户ssize_t(*write)(struct file*,const char*,size_t,loff_t*)驱动调用copy_from_user()将用户数据读到本地bufferwww.embedu.orgfile_operationsint(*mmap)(struct file*,struct vm_area_struct*)驱动中提供该方法用于支持用户mmap操作用户将设备内存区映射到进程的地址空间,直接操作该物理内存提高效率典型例子:framebuffer,sound,capture等驱动www.embedu.orgfile_operationsint(*ioctl)(struct inode*,struct file*,unsigned int cmd,unsigned long arg)驱动程序一般需支持通过Ioctl实现各种控制与参数设置,如串口可设置波特率等多参数cmd变量存放命令,驱动代码根据cmd里面的值进行switch-case处理分支arg存放参数,如为整数,可直接使用。如为指针,驱动程序首先要检查指针的合法性int access_ok(int type,const void*addr,unsigned long size);检查通过后可以使用驱动程序还可通过int capable(int capability)函数来确定调用进程是否有权执行操作www.embedu.org怎样使用ioctl用户系统调用:int ioctl(int fd,int cmd,.);cmd命令码格式用户空间与Linux内核中定义需一致:_|设备类型|序列号|方向|数据尺寸|-|-|-|-|8 bit|8 bit|2 bit|814 bit|www.embedu.orgIoctl权能和受限操作驱动程序的访问控制采用linux文件系统的权限机制驱动程序采用权能机制来控制特殊的操作权限驱动程序通过int capable(int capability);函数来确定调用进程是否有权执行操作www.embedu.org用ioctl控制驱动程序实例分析int scull_ioctl(struct inode*inode,struct file*filp,unsigned int cmd,unsigned long arg)int err=0,tmp;int retval=0;if(_IOC_TYPE(cmd)!=SCULL_IOC_MAGIC)return-ENOTTY;if(_IOC_NR(cmd)SCULL_IOC_MAXNR)return-ENOTTY;if(_IOC_DIR(cmd)&_IOC_READ)err=!access_ok(VERIFY_WRITE,(void _user*)arg,_IOC_SIZE(cmd);else if(_IOC_DIR(cmd)&_IOC_WRITE)err=!access_ok(VERIFY_READ,(void _user*)arg,_IOC_SIZE(cmd);if(err)return-EFAULT;www.embedu.org用ioctl控制驱动程序实例分析switch(cmd)case SCULL_IOCRESET:scull_quantum=SCULL_QUANTUM;scull_qset=SCULL_QSET;break;case SCULL_IOCSQUANTUM:/*Set:arg points to the value*/if(!capable(CAP_SYS_ADMIN)return-EPERM;retval=_get_user(scull_quantum,(int _user*)arg);break;case SCULL_IOCTQUANTUM:/*Tell:arg is the value*/if(!capable(CAP_SYS_ADMIN)return-EPERM;scull_quantum=arg;break;case SCULL_IOCGQUANTUM:/*Get:arg is pointer to result*/retval=_put_user(scull_quantum,(int _user*)arg);break;www.embedu.org Linux驱动程序基础知识 LinuxLinux字符设备驱动基础字符设备驱动基础 三种重要数据结构及关系三种重要数据结构及关系 file_oprationsfile_oprations结构分析结构分析 用户空间与内核空间数据传输用户空间与内核空间数据传输基本字符设备驱动函数模板www.embedu.org用户空间与内核空间数据传输用户(进程)空间受保护的空间,执行于“用户模式”所有地址都是“逻辑”的,不能访问不属于自己的内存需要转换成物理的(真实的)地址执行指令时由硬件辅助完成转换发生页面错误时间接地由操作系统完成内核(系统)空间不受保护的空间,执行于“超级模式”能访问任何内存,所有地址都是“逻辑”的需要转换成物理的(真实的)地址执行指令时由硬件辅助完成转换发生页面错误时,间接地由操作系统完成www.embedu.org驱动程序使用的内存获取内存区(kmalloc分配物理内存)BufferDMA bufferioport和iomem映射及使用用户静态映射(iodesc,iomap)动态映射(ioremap)在用户和内核之间传递数据unsigned long copy_from_user(void*to,const void _ _user*from,unsigned long count);unsigned long copy_to_user(void _ _user*to,const void*from,unsigned long count);www.embedu.org典型的嵌入式设备存储器映射 www.embedu.org Linux驱动程序基础知识 LinuxLinux字符设备驱动基础字符设备驱动基础 三种重要数据结构及关系三种重要数据结构及关系 file_oprationsfile_oprations结构分析结构分析 用户空间与内核空间数据传输用户空间与内核空间数据传输 基本字符设备驱动函数模板基本字符设备驱动函数模板www.embedu.org模块加载与卸载函数模板 struct xxx_dev_t/设备结构体 struct cdev cdev;.xxx_dev;static int _ _init xxx_init(void)/设备驱动模块加载函数 .cdev_init(&xxx_dev.cdev,&xxx_fops);/初始化cdev xxx_dev.cdev.owner=THIS_MODULE;if(xxx_major)/获取字符设备号 register_chrdev_region(xxx_dev_no,1,DEV_NAME);else alloc_chrdev_region(&xxx_dev_no,0,1,DEV_NAME);ret=cdev_add(&xxx_dev.cdev,xxx_dev_no,1);/注册设备 .static void _ _exit xxx_exit(void)/*设备驱动模块卸载函数*/unregister_chrdev_region(xxx_dev_no,1);/释放占用的设备号 cdev_del(&xxx_dev.cdev);/注销设备 .www.embedu.org读、写、I/O控制函数模板/*读设备*/ssize_t xxx_read(struct file*filp,char _ _user*buf,size_t count,loff_t*f_pos)copy_to_user(buf,.,.);/*写设备*/ssize_t xxx_write(struct file*filp,const char _ _user*buf,size_t count,loff_t*f_pos).copy_from_user(.,buf,.);./*ioctl函数*/int xxx_ioctl(struct inode*inode,struct file*filp,unsigned int cmd,unsigned long arg).switch(cmd)case XXX_CMD1:.break;case XXX_CMD2:.break;default:/*不能支持的命令*/return -ENOTTY;return 0;www.embedu.org字符设备驱动(字符设备驱动(2 2)www.embedu.org51版权华华清清远见远见嵌入式培嵌入式培训训中心版中心版权权所有;所有;未未经华经华清清远见远见明确明确许许可,不能可,不能为为任何目的以任何形式复制任何目的以任何形式复制或或传传播此文档的任何部分;播此文档的任何部分;本文档包含的信息如有更改,恕不另行通知;本文档包含的信息如有更改,恕不另行通知;保留所有保留所有权权利。利。www.embedu.org 字符设备访问控制字符设备访问控制同步、互斥、阻塞、睡眠Poll和select操作异步通知机制www.embedu.org字符设备访问控制访问控制可以防止未授权用户使用设备,这种控制通过设置文件系统权限来实现最简单实现访问控制是一次只允许一个进程打开设备用一个标志变量来指示当前设备是否已经被打开若已经打开则拒绝新的打开操作www.embedu.org全局标志的竞争问题在上例中,如果有两个进程试图同时打开设备,则他们有可能同时测试全局标志,并同时成功打开。为了避免这种情况发生,我们可以以原子操作的形式修改标志使用自旋锁比较适合此处使用www.embedu.org限制每次只有一个用户访问此种实现允许一个用户多次打开设备维护数据结构的完整性由用户来完成用户第一次打开设备时授权,并记录下设备的属主。该用户可以对此打开设备其他用户试图再打开设备时,UID检查结果会拒绝打开,并返回-EBUSY,指示设备忙www.embedu.org用阻塞open代替EBUSY有些情况下,设备忙时最好让用户稍延迟一会儿而不是返回失败可以用阻塞型的open来实现当用户试图打开设备时,如果设备忙,则把当前进程置入等待队列当前一个用户使用完设备,使用计数减到0时,下一个用户睡眠的进程将被唤醒www.embedu.org 字符设备访问控制 同步、互斥、阻塞、睡眠同步、互斥、阻塞、睡眠poll和select操作异步通知机制www.embedu.org竟态产生的原因对称多处理器(SMP)的多个CPU单CPU内进程与抢占它的进程中断(硬中断、软中断、Tasklet、底半部)与进程之间www.embedu.org原子操作原子操作指的是在执行过程中不会被别的代码路径所中断的操作。常用原子操作函数举例:atomic_t v=ATOMIC_INIT(0);/定义原子变量v并初始化为0atomic_read(atomic_t*v);/返回原子变量的值void atomic_inc(atomic_t*v);/原子变量增加1void atomic_dec(atomic_t*v);/原子变量减少1int atomic_dec_and_test(atomic_t*v);/自减操作后测试其是否为0,为0则返回true,否则返回false。www.embedu.org原子变量的使用实例原子变量的使用实例:最多只能被一个进程打开 static atomic_t xxx_available=ATOMIC_INIT(1);/*定义原子变量*/static int xxx_open(struct inode*inode,struct file*filp).if(!atomic_dec_and_test(&xxx_available)atomic_inc(&xxx_available);return -EBUSY;/*已经打开*/.return 0;/*成功*/static int xxx_release(struct inode*inode,struct file*filp)atomic_inc(&xxx_available);/*释放设备*/return 0;www.embedu.org自旋锁自旋锁(spin lock)是一种对临界资源进行互斥手访问的典型手段Linux系统中与自旋锁相关的操作主要有如下4种。1定义自旋锁spinlock_t spin;2初始化自旋锁spin_lock_init(lock)3获得自旋锁spin_lock(lock)/该宏用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,否则,它将自旋 在那里,直到该自旋锁的保持者释放;spin_trylock(lock)/该宏尝试获得自旋锁lock,如果能立即获得锁,它获得锁并返回真,否则立即返回假,实际上不再“在原地打转”;4释放自旋锁spin_unlock(lock)/该宏释放自旋锁lock,它与spin_trylock或spin_lock配对使用。www.embedu.org自旋锁使用举例使用自旋锁使设备只能被一个进程打开 int xxx_count=0;/*定义文件打开次数计数*/static int xxx_open(struct inode*inode,struct file*filp).spinlock(&xxx_lock);if(xxx_count)/*已经打开*/spin_unlock(&xxx_lock);return -EBUSY;xxx_count+;/*增加使用计数*/spin_unlock(&xxx_lock);.return 0;/*成功*/static int xxx_release(struct inode*inode,struct file*filp).spinlock(&xxx_lock);xxx_count-;/*减少使用计数*/spin_unlock(&xxx_lock);return 0;www.embedu.org信号量信号量(semaphore)是用于保护临界区的一种常用方法,它的使用方式和自旋锁类似。与自旋锁相同,只有得到信号量的进程才能执行临界区代码。但是,与自旋锁不同的是,当获取不到信号量时,进程不会原地打转而是进入休眠等待状态www.embedu.org信号量相关操作定义信号量struct semaphore sem;初始化信号量void sema_init(struct semaphore*sem,int val);void init_MUTEX(struct semaphore*sem);/初始化为0获得信号量void down(struct semaphore*sem);int down_interruptible(struct semaphore*sem);int down_trylock(struct semaphore*sem);释放信号量void up(struct semaphore*sem);www.embedu.org信号量使用实例使用信号量实现设备只能被一个进程打开 static DECLARE_MUTEX(xxx_lock);/定义互斥锁static int xxx_open(struct inode*inode,struct file*filp).if(down_trylock(&xxx_lock)/获得打开锁 return -EBUSY;/设备忙 .return 0;/*成功*/static int xxx_release(struct inode*inode,struct file*filp)up(&xxx_lock);/释放打开锁 return 0;www.embedu.org自旋锁vs信号量自旋锁和信号量选用的3项原则1、判断进程切换时间Tsw,和等待获取自旋锁(由临界区执行时间决定)Tcs。如果若Tcs比较小,应使用自旋锁,若Tcs很大,应使用信号量。2、信号量所保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界区。因为阻塞意味着要进行进程的切换,如果进程被切换出去后,另一个进程企图获取本自旋锁,死锁就会发生3、信号量存在于进程上下文,因此,如果被保护的共享资源需要在中断或软中断情况下使用,则在信号量和自旋锁之间只能选择自旋锁。当然,如果一定要使用信号量,则只能通过down_trylock()方式进行,不能获取就立即返回以避免阻塞。www.embedu.org阻塞与非阻塞I/O阻塞操作阻塞操作 是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。非阻塞操作非阻塞操作 的进程在不能进行设备操作时并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。www.embedu.org阻塞地读取串口一个字符char buf;fd=open(/dev/ttyS1,O_RDWR);.res=read(fd,&buf,1);/当串口上有输入时才返回if(res=1)printf(%cn,buf);www.embedu.org非阻塞地读取串口一个字符char buf;fd=open(/dev/ttyS1,O_RDWR|O_NONBLOCK);.while(read(fd,&buf,1)!=1);/串口上无输入也返回,所以要循环尝试读取串口printf(%cn,buf);www.embedu.org等待队列等待队列等待队列 以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制,也可以用来同步对系统资源的访问(如信号量)www.embedu.org等待队列的操作(1)定义“等待队列头”wait_queue_head_t my_queue;初始化“等待队列头”init_waitqueue_head(&my_queue);定义等待队列DECLARE_WAITQUEUE(name,tsk)添加/移除等待队列void fastcall add_wait_queue(wait_queue_head_t*q,wait_queue_t*wait);void fastcall remove_wait_queue(wait_queue_head_t*q,wait_queue_t*wait);www.embedu.org等待队列的操作(2)等待事件wait_event(queue,condition)wait_event_interruptible(queue,condition)wait_event_timeout(queue,condition,timeout)wait_event_interruptible_timeout(queue,condition,timeout)唤醒队列void wake_up(wait_queue_head_t*queue);void wake_up_interruptible(wait_queue_head_t*queue);www.embedu.org等待队列的操作(3)在等待队列上睡眠sleep_on(wait_queue_head_t*q);Interruptible_sleep_on(wait_queue_head_t*q);www.embedu.org 字符设备访问控制同步、互斥、阻塞、睡眠 PollPoll和和selectselect操作操作异步通知机制www.embedu.orgpoll和select操作进程中调用poll和select操来查询打开的I/O设备文件是否可做非阻塞读写驱动程序中unsigned int(*poll)(struct file*,poll_table*)来实现poll和select操作通过poll_wait可以向驱动向poll_table结构添加一个等待队列驱动的poll函数应该实现返回那个操作可以立即完成而无需休眠POLLIN,POLLOUT,POLLDNORM,POLLERRLinux/poll.hwww.embedu.org应用程序中的轮询编程int select(int numfds,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,struct timeval*timeout);其中readfds、writefds、exceptfds分别是被select()监视的读、写和异常处理的文件描述符集合,numfds的值是需要检查的号码最高的文件描述符加1。timeout参数是一个指向struct timeval类型的指针,它可以使select()在等待timeout时间后若没有文件描述符准备好则返回。struct timeval数据结构的定义如代码清单8.10所示。www.embedu.orgpoll()函数典型模板 static unsigned int xxx_poll(struct file*filp,poll_table*wait)unsigned int mask=0;struct xxx_dev*dev=filp-private_data;/*获得设备结构体指针*/.poll_wait(filp,&dev-r_wait,wait);/加读等待队列头poll_wait(filp,&dev-w_wait,wait);/加写等待队列头 if(.)/可读 mask|=POLLIN|POLLRDNORM;/*标示数据可获得*/if(.)/可写 mask|=POLLOUT|POLLWRNORM;/*标示数据可写入*/.return mask;www.embedu.org 字符设备访问控制同步、互斥、阻塞、睡眠Poll和select操作 异步通知机制异步通知机制www.embedu.org异步通知的概念与作用异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”的概念,比较准确的称谓是“信号驱动的异步I/O”。www.embedu.orgLinux信号 使用信号进行进程间通信(IPC)是UNIX系统中的一种传统机制,Linux系统也支持这种机制。在Linux系统中,异步通知使用信号来实现。除了SIGSTOP和SIGKILL两个信号外,进程能够忽略或捕获其他的全部信号。例如:在进程执行时,按下Ctrl+c组合键将向其发出SIGINT信号,kill正在运行的进程将向其发出SIGTERM信号SIGHUP 1挂起 SIGINT 2终端中断 SIGQUIT 3终端退出 www.embedu.org信号的接收在用户程序中,为了捕获信号,可以使用signal()函数来设置对应信号的处理函数,如下所示:void(*signal(int signum,void(*handler)(int)(int);www.embedu.orgsignal()捕获信号范例 void sigterm_handler(int signo)printf(Have caught sig N.O.%dn,signo);exit(0);int main(void)signal(SIGINT,sigterm_handler);signal(SIGTERM,sigterm_handler);while(1);return 0;www.embedu.org异步