《chap6-1 嵌入式Linux驱动程序开发课件.ppt》由会员分享,可在线阅读,更多相关《chap6-1 嵌入式Linux驱动程序开发课件.ppt(67页珍藏版)》请在金锄头文库上搜索。
1、第6章 嵌入式Linux驱动程序开发倪福川6.1 嵌入式Linux的设备管理Linux设备概述:块设备:类似磁盘以记录块或扇区为单位,成块进行输入/输出的设备;字符设备:类似键盘以字符为单位,逐个进行输入/输出的设备。网路设备:介于块设备和字符设备之间的一种特殊设备。块设备/字符设备n块设备:仅支持面向块的I/O操作,所有I/O操作都通过在内核地址空间中的I/O缓冲区进行,可以支持随机存取的功能。文件系统通常都建立在块设备上。n字符设备:支持面向字符的I/O操作,不经过系统的快速缓存,要负责管理自己的缓冲区结构。字符设备接口只支持顺序存取的功能,一般不能进行任意长度的I/O请求,而是限制I/O
2、请求的长度必须是设备要求的基本块长的倍数。n处理器与设备间数据交换方式查询方式、中断方式直接内存存取(DMA)方式。1查询方式设备驱动程序通过设备的I/O端口空间,以及存储器空间完成数据的交换。n利用这些地址空间,驱动程序可以向外设发送指定的操作指令。n驱动程序在提交命令之后,开始查询设备的状态寄存器,当状态寄存器表明操作完成时,驱动程序可继续后续处理。n优点:硬件开销小,使用起来比较简单。缺点:CPU要不断地查询外设的状态,当外设未准备好时,就只能循环等待,不能执行其他程序,这样就浪费了CPU的大量时间,降低了处理器的利用率。中断方式的原理当CPU进行主程序操作时,外设的数据已存入端口的数据
3、输入寄存器,或端口的数据输出寄存器已空,此时由外设通过接口电路向CPU发出中断请求信号。CPU在满足一定条件下,暂停执行当前正在执行的主程序,转入执行相应能够进行输入/输出操作的子程序,待输入/输出操作执行完毕之后,CPU再返回并继续执行原来被中断的主程序。CPU就避免了把大量时间耗费在等待、查询外设状态的操作上,使其工作效率得以大大提高。2中断方式中断响应n能够向CPU发出中断请求的设备或事件称为中断源。中断源向CPU发出中断请求,若优先级别最高,则CPU在满足一定的条件时,可中断当前程序的运行,保护好被中断的主程序的断点及现场信息,然后根据中断源提供的信息,找到中断服务子程序的入口地址,转
4、去执行新的程序段,这就是中断响应。nCPU响应中断是有条件的,如内部允许中断、中断未被屏蔽、当前指令执行完等。nCPU响应中断以后,就会中止当前的程序,转去执行一个中断服务子程序,以完成为相应设备的服务。DMA可允许设备和系统内存间在没有处理器参与的情况下传输大量数据。设备驱动程序在利用DMA之前,需要选择DMA通道并定义相关寄存器,以及数据的传输方向,即读取或写入,然后将设备设定为利用该DMA通道传输数据。设备完成设置之后,可以立即利用该DMA通道在设备和系统的内存之间传输数据,传输完毕后产生中断以便通知驱动程序进行后续处理。在利用DMA进行数据传输的同时,处理器仍然可以继续执行指令。3直接
5、访问内存(DMA)方式设备驱动程序的概念 设备驱动程序:处理和操作硬件控制器的软件,本质上,是内核中具有最高特权级的、驻留内存的、可共享的底层硬件处理例程。驱动程序是内核的一部分,是操作系统内核与硬件设备的直接接口。驱动程序屏蔽了硬件的细节,完成以下功能:对设备初始化和释放;对设备进行管理,包括实时参数设置,以及提供对设备的操作接口;读取应用程序传送给设备文件的数据或者回送应用程序请求的数据;检测和处理设备出现的错误。Linux设备驱动程序Linux操作系统将所有的设备全部看成文件,并通过文件的操作界面进行操作。对用户程序而言,设备驱动程序隐藏了设备的具体细节,对各种不同设备提供了一致的接口,
6、一般来说,是把设备映射为一个特殊的设备文件。设备文件的属性由三部分信息组成:第一部分是文件的类型,第二部分是一个主设备号,第三部分是一个次设备号。其中类型和主设备号结合在一起惟一地确定了设备文件驱动程序及其界面,而次设备号则说明目标设备是同类设备中的第几个。Linux设备驱动程序Linux中将设备当做文件处理,对设备进行操作的系统调用格式与对文件的操作类似,主要包括open()、read()、write()、ioctl()、close()等。应用程序发出系统调用命令后,从用户态转到核心态,通过内核将系统调用转换成对物理设备的操作。这意味着:由于每一个设备至少由文件系统的一个文件代表,因而都有一
7、个“文件名”。应用程序通常可以通过系统调用open()打开设备文件,建立起与目标设备的连接。打开了代表着目标设备的文件,即建立起与设备的连接后,可以通过read()、write()、ioctl()等常规的文件操作对目标设备进行操作。 驱动程序结构 1自动配置和初始化子程序,用来检测所需驱动的硬件设备是否工作正常、对正常工作的设备及其相关驱动程序所需要的软件状态进行初始化。2服务于I/O请求的子程序,该子程序称为驱动程序的上半部。这部分程序在执行时,系统仍认为与进行调用的进程属于同一个进程,只是由用户态变成了核心态,可在其中调用sleep()等与进程运行环境有关的函数。3中断服务程序,又称为驱动
8、程序的下半部,由Linux系统来接收硬件中断,再由系统调用中断服务子程序。在系统内部,I/O设备的存取通过一组固定的入口点来进行,入口点也可以理解为设备的句柄,就是对设备进行操作的基本函数。 驱动程序结构 字符型设备驱动程序入口点:open入口点。打开设备准备I/O操作。open子程序必须对将要进行的I/O操作做好必要的准备工作,如清除缓冲区等。如果设备是独占的,即同一时刻只能有一个程序访问此设备,则open子程序必须设置一些标志以表示设备处于忙状态。close入口点。关闭一个设备。当最后一次使用设备完成后,调用close子程序。独占设备必须标记设备方可再次使用。字符型设备驱动程序入口点:ni
9、octl入口点。执行读、写之外的操作。nselect入口点。检查设备,看数据是否可读或设备是否可用于写数据。select系统调用在检查与设备文件相关的文件描述符时使用select入口点。阻塞structfile_operationsstructmodule*owner;loff_t(*llseek)(structfile*,loff_t,int);ssize_t(*read)(structfile*,char*,size_t,loff_t*);ssize_t(*write)(structfile*,constchar*,size_t,loff_t*);int(*readdir)(structf
10、ile*,void*,filldir_t);unsignedint(*poll)(structfile*,structpoll_table_struct*);int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);int(*mmap)(structfile*,structvm_area_struct*);int(*open)(structinode*,structfile*);int(*flush)(structfile*);int(*release)(structinode*,structfile*);int(*fsync
11、)(structfile*,structdentry*,intdatasync);int(*fasync)(int,structfile*,int);int(*lock)(structfile*,int,structfile_lock*);ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*sendpage)(structfile*,structpage*,
12、int,size_t,loff_t*,int);unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);lseek,移动文件指针的位置,只能用于可以随机存取的设备。read,进行读操作,buf为存放读取结果的缓冲区,count为所要读取的数据长度。write,进行写操作,与read类似。select,进行选择操作。ioctl,进行读、写以外的其他操作。mmap,用于把设备的内容映射到地址空间,一般只有块设备驱动程序使用。open,打开设备进行I/O操作。返
13、回0表示成功,返回负数表示失败。release,即close操作。structinode称做索引节点数据结构,定义如下:structinodestructlist_head i_hash;structlist_head i_list;structlist_head i_dentry;structlist_head i_dirty_buffers;structlist_head i_dirty_data_buffers;unsignedlongi_ino;atomic_ti_count;kdev_ti_dev;umode_ti_mode;nlink_ti_nlink;uid_ti_uid;gid
14、_ti_gid;kdev_ti_rdev;loff_ti_size;time_ti_atime;time_ti_mtime;time_ti_ctime;unsignedinti_blkbits;unsignedlongi_blksize;unsignedlongi_blocks;unsignedlongi_version;structsemaphorei_sem;structsemaphorei_zombie;structinode_operations*i_op;structfile_operations*i_fop;structsuper_block*i_sb;wait_queue_hea
15、d_ti_wait;structfile_lock*i_flock;structaddress_space*i_mapping;structaddress_spacei_data;structdquot*i_dquotMAXQUOTAS;structlist_head i_devices;structpipe_inode_info*i_pipe;structblock_device*i_bdev;structchar_device*i_cdev;unsignedlongi_dnotify_mask;structdnotify_struct*i_dnotify;unsignedlongi_sta
16、te;unsignedinti_flags;unsignedchari_sock;atomic_ti_writecount;unsignedinti_attr_flags;_u32i_generation;unionstructminix_inode_infominix_i;structext2_inode_infoext2_i;structext3_inode_infoext3_i;structhpfs_inode_infohpfs_i;structntfs_inode_infontfs_i;structmsdos_inode_infomsdos_i;structumsdos_inode_i
17、nfoumsdos_i;structiso_inode_infoisofs_i;structnfs_inode_infonfs_i;structsysv_inode_infosysv_i;structaffs_inode_infoaffs_i;structufs_inode_infoufs_i;structefs_inode_infoefs_i;structromfs_inode_inforomfs_i;structshmem_inode_infoshmem_i;structcoda_inode_infocoda_i;structsmb_inode_infosmbfs_i;structhfs_
18、inode_infohfs_i;structadfs_inode_infoadfs_i;structqnx4_inode_infoqnx4_i;structreiserfs_inode_inforeiserfs_i;structbfs_inode_infobfs_i;structudf_inode_infoudf_i;structncp_inode_infoncpfs_i;structproc_inode_infoproc_i;structsocketsocket_i;structusbdev_inode_infousbdev_i;structjffs2_inode_infojffs2_i;v
19、oid*generic_ip;u;structfile主要用于与文件系统相关的设备驱动程序,可提供关于被打开的文件的信息,定义如下:structfilestructlist_headf_list;structdentry*f_dentry;structvfsmount*f_vfsmnt;structfile_operations*f_op;atomic_tf_count;unsignedintf_flags;mode_tf_mode;loff_tf_pos;unsignedlongf_reada,f_ramax,f_raend,f_ralen,f_rawin;structfown_struct
20、f_owner;unsignedintf_uid,f_gid;intf_error;unsignedlongf_version;/*neededforttydriver,andmaybeothers*/void*private_data;/*preallocatedhelperkiobuftospeedupO_DIRECT*/structkiobuf*f_iobuf;longf_iobuf_lock;structfilestructfile_operations*f_op;.;在用户自己的驱动程序中,首先要根据驱动程序的功能,完成file_operations结构中函数的实现。不需要的函数接口
21、可以直接在file_operations结构中初始化为NULL。file_operations中的变量会在驱动程序初始化时,注册到系统内部。每个进程对设备的操作,都会根据主次设备号,转换成对file_operations结构的访问。6.2 设备驱动程序的开发过程进行嵌入式Linux系统的开发,很大的工作量是为各种设备编写驱动程序。除非系统不使用操作系统,程序直接操纵硬件。嵌入式Linux系统驱动程序开发与普通Linux开发没有区别。可以在硬件生产厂家或者Internet上寻找驱动程序,也可以根据相近的硬件驱动程序来改写,可以加快开发速度。6.2嵌入式Linux设备驱动的开发流程:(1)查看原理
22、图,理解设备的工作原理。一般嵌入式处理器的生产商提供参考电路,也可以根据需要自行设计。(2)定义设备号。设备由一主设备号和一次设备号标识。主设备号惟一标识了设备类型,即设备驱动程序类型,它是块设备表或字符设备表中设备表项的索引。次设备号仅由设备驱动程序解释,区分被一设备驱动控制下的某个独立设备。(3)实现初始化函数。在驱动程序中实现驱动的注册和卸载。(4)设计所要实现的文件操作,定义file_operations结构。(5)实现所需的系统调用,如read、write等。(6)实现中断服务,并用request_irq向内核注册,并不是每个设备驱动所必需的。(7)编译该驱动程序到内核中,或者用in
23、smod命令加载模块。(8)测试该设备,编写应用程序,对驱动程序进行测试。模块化驱动程序设计 内核模块与应用程序之间的区别。一个应用从头到尾完成一个任务,而模块则是为以后处理某些请求而注册自己,完成这个任务后,它的“主”函数就立即中止了。内核源码仅能连接编译到内核模块中,不像应用那样有众多的支持库,内核能调用的仅是由内核开放出来的那些函数。由于没有库连接到模块中,所以源码文件不应该模块化任何常规头文件。与内核有关的所有内容都定义在目录/usr/include/linux和/usr/include/asm下的头文件中。模块化驱动程序设计 1内核空间和用户空间到软件称执行态分为“内核空间”和“用户
24、空间”。在Linux系统中,内核在最高级执行,也称为“管理员态”,在这一级任何操作都可以执行。而应用程序则执行在最低级,所谓的“用户态”,在这一级处理器禁止对硬件的直接访问和对内存的未授权访问。模块是在所谓的“内核空间”中运行的,而应用程序则是在“用户空间”中运行的。它们分别引用不同的内存映射,也就是程序代码使用不同的“地址空间”。Linux通过系统调用和硬件中断完成从用户空间到内核空间的控制转移。执行系统调用的内核代码在进程的上下文中执行,它执行调用进程的操作而且可以访问进程地址空间的数据。各个模块被分别编译并链接成一组目标文件,这些文件能被载入正在运行的内核,或从正在运行的内核中卸载。必要
25、时内核能请求内核守护进程Kerneld对模块进行加载或卸载。内核模块一部分保存在Kernel中,另一部分在Modules包中。内核提供一个插槽,它就像一个插件,在需要时,插入内核中使用,不需要时从内核中拔出。这一切都由一个称为Kerneld的守护进程自动处理。根据需要动态载入模块可以保证内核达到最小,并且具有很大的灵活性。2模块化的优缺点优点:将内核映像的尺寸保持在最小,并具有最大的灵活性。这便于检验新的内核代码,而不需要重新编译内核并重新引导。对系统性能和内存的利用有负面影响。装入的内核模块与其他内核部分一样,具有相同的访问权限,由此可见,差的内核模块会导致系统崩溃。为了使内核模块能访问所有
26、内核资源,内核必须维护符号表,并在加载和卸载模块时修改这些符号表。由于有些模块要求利用其他模块的功能,故内核要维护模块之间的依赖性。内核必须能够在卸载模块时通知模块,并且要释放分配给模块的内存和中断等资源。内核版本和模块版本的不兼容也可能导致系统崩溃,因此,严格的版本检查是必需的。模块机制确实是扩充内核功能的一种行之有效的方法,也是在内核级进行编程的有效途径。 设备注册和初始化 设备的驱动程序在加载的时候首先需要调用入口函数init_module(),该函数最重要的一个工作就是向内核注册该设备。对于字符设备调用register_chrdev()完成注册。intregister_chrdev(u
27、nsignedintmajor,constchar*name,structfile_operations*fops);major向系统申请的主设备号,为0,系统动态分配一主设备号。name是设备名,fops是对各个调用的入口点说明。函数返回值0时表示成功;-EINVAL,表示申请的主设备号非法,主设备号大于系统所允许的最大设备号;-EBUSY,所申请的主设备号正在被其他设备程序使用。如果动态分配主设备号成功,此函数将返回所分配的主设备号。如果register_chrdev()操作成功,设备名就会出现在/proc/dvices文件中。用mknod命令来把设备映射为一特别文件,其它程序使用这个设备
28、的时候,只要对此特别文件进行操作 设备注册和初始化 Linux在/dev目录中为每个设备建立一个文件,用lsl命令列出。注册以后,Linux将设备名与主、次设备号联系起来。当有对此设备名的访问时,Linux通过请求访问的设备名得到主、次设备号,然后把此访问分发到对应的设备驱动,设备驱动再根据次设备号调用不同的函数。当设备驱动模块从Linux内核中卸载,对应的主设备号必须被释放。字符设备在cleanup_module()函数中调用unregister_chrdev()来完成设备的注销。intunregister_chrdev(unsignedintmajor,constchar*name);Li
29、nux内核把name和major在内核注册的名称对比,如果不相等,卸载失败,并返回EINVAL;如果major大于最大的设备号,也返回EINVAL。设备驱动的初始化函数主要完成功能(1)对驱动程序管理的硬件进行必要的初始化。对硬件寄存器进行设置。比如,设置中断掩码,设置串口的工作方式、并口的数据方向等。(2)初始化设备驱动相关的参数。一般说来,每个设备都要定义一个设备变量,用以保存设备相关的参数。在这一步骤里对设备变量中的项进行初始化。(3)在内核注册设备。调用register_chrdev()函数来注册设备。(4)注册中断。如果设备需要IRQ支持,则要使用request_irq()函数注册中
30、断。设备驱动的初始化函数主要完成功能(5)其他初始化工作。初始化部分一般还负责给设备驱动程序申请包括内存、时钟、I/O端口等在内的系统资源,这些资源也可以在open子程序或者其他地方申请。这些资源不用时,应该释放,以利于资源的共享。若驱动程序是内核的一部分,初始化函数则要按如下方式声明:int_initchr_driver_init(void);其中_init是必不可少的,在系统启动时会由内核调用chr_driver_init,完成驱动程序的初始化。当驱动程序是以模块的形式编写时,则要按照如下方式声明:intinit_module(void)当运行后面介绍的insmod命令插入模块时,会调用i
31、nit_module函数完成初始化工作。中断管理 设备驱动程序通过调用request_irq函数来申请中断,通过free_irq来释放中断。它们在linux/sched.h中的定义如下:intrequest_irq(unsignedintirq,void(*handler)(intirq,voiddev_id,structpt_regs*regs),unsignedlongflags,constchar*device,void*dev_id);voidfree_irq(unsignedintirq,void*dev_id);通常从request_irq函数返回的值为0时,表示申请成功;负值表示
32、出现错误。irq表示所要申请的硬件中断号。handler为向系统登记的中断处理子程序,中断产生时由系统来调用,调用时所带参数irq为中断号,dev_id为申请时告诉系统的设备标识,regs为中断发生时寄存器内容。device为设备名,将会出现在/proc/interrupts文件里。flag是申请时的选项,它决定中断处理程序的一些特性,其中最重要的是决定中断处理程序是快速处理程序(flag里设置了SA_INTERRUPT)还是慢速处理程序(不设置SA_INTERRUPT)。下面的代码将在SBC-2410X的Linux中注册外部中断2。eint_irq=IRQ_EINT2;set_externa
33、l_irq(eint_irq,EXT_FALLING_EDGE,GPIO_PULLUP_DIS);ret_val=request_irq(eint_irq,eint2_handler,“S3C2410Xeint2”,0);if(ret_val0)returnret_val;用来打开和关闭中断的函数如下:#definecli()_asm_volatile_(cli:)#definesli()_asm_volatile_(sli:)中断管理 设备驱动程序通过调用request_irq函数来申请中断,通过free_irq来释放中断。在linux/sched.h中定义:intrequest_irq(u
34、nsignedintirq,/*所要申请的硬件中断号*/void(*handler)(intirq,voiddev_id,structpt_regs*regs),unsignedlongflags,/*申请时的选项SA_INTERRUPT快速处理程序*/constchar*device,/*设备名,出现在/proc/interrupts文件*/void*dev_id);voidfree_irq(unsignedintirq,void*dev_id);通常从request_irq函数返回的值为0时,表示申请成功;负值表示出现错误。handler为向系统登记的中断处理子程序,中断产生时由系统来调用
35、,调用时所带参数irq为中断号,dev_id为申请时告诉系统的设备标识,regs为中断发生时寄存器内容。 设备驱动开发的基本函数 1I/O口函数无论驱动程序多么复杂,归根结底,无非还是向某个端口或者某个寄存器位赋值,这个值只能是0或1。接收值的就是I/O口。与中断和内存不同,使用一个没有申请的I/O端口不会使处理器产生异常,也就不会导致诸如“segmentationfault”一类的错误发生。由于任何进程都可以访问任何一个I/O端口,此时系统无法保证对I/O端口的操作不会发生冲突,甚至因此而使系统崩溃。因此,在使用I/O端口前,也应该检查此I/O端口是否已有别的程序在使用,若没有,再把此端口标
36、记为正在使用,在使用完以后释放它。 设备驱动开发的基本函数 1I/O口函数需要用到如下几个函数:intcheck_region(unsignedintfrom,unsignedintextent);voidrequest_region(unsignedintfrom,unsignedintextent,constchar*name);voidrelease_region(unsignedintfrom,unsignedintextent);from表示所申请的I/O端口的起始地址;extent为所要申请的从from开始的端口数;name为设备名,将会出现在/proc/ioports文件里;ch
37、eck_region返回0表示I/O端口空闲,否则为正在被使用。在申请了I/O端口之后,可以借助asm/io.h中的如下几个函数来访问I/O端口:inlineunsignedintinb(unsignedshortport);inlineunsignedintinb_p(unsignedshortport);inlinevoidoutb(charvalue,unsignedshortport);inlinevoidoutb_p(charvalue,unsignedshortport);其中inb_p和outb_p插入一定延时适应低速的I/O端口。2时钟函数在设备驱动程序中,一般都需要用到计时机
38、制。在Linux系统中,时钟是由系统接管的,设备驱动程序可以向系统申请时钟。与时钟有关的系统调用有:#include#includevoidadd_timer(structtimer_list*timer);intdel_timer(structtimer_list*timer);inlinevoidinit_timer(structtimer_list*timer);structtimer_list的定义为:structtimer_liststructtimer_list*next;structtimer_list*prev;unsignedlongexpires;unsignedlongd
39、ata;void(*function)(unsignedlongd);其中,expires是要执行function的时间。系统计时到预定时间就调用function,并把此子程序从定时队列里删除,可见,如果想要每隔一定时间间隔执行一次的话,就必须在function里再一次调用add_timer。function的参数d即为timer里面的data项。n定时机制系统核心有一个全局变量jiffies表示当前时间,一般在调用add_timer时jiffies=JIFFIES+num,表示在num个系统最小时间间隔后执行function函数。系统最小时间间隔与所用的硬件平台有关,在核心里定义了常数HZ表
40、示一秒内最小时间间隔的数目,则num*HZ表示num秒。3内存操作函数作为系统核心的一部分,设备驱动程序在申请和释放内存时不是调用malloc和free,而代之以调用kmalloc和kfree,它们在linux/kernel.h中被定义为:void*kmalloc(unsignedintlen,intpriority);voidkfree(void*obj);参数len为希望申请的字节数,obj为要释放的内存指针。priority为分配内存操作的优先级,即在没有足够空闲内存时如何操作,一般由取值GFP_KERNEL解决即可。4复制函数在用户程序调用read、write时,因为进程的运行状态由用
41、户态变为核心态,地址空间也变为核心地址空间。由于read、write中参数buf是指向用户程序的私有地址空间的,所以不能直接访问,必须通过下面两个系统函数来访问用户程序的私有地址空间。#includevoidmemcpy_fromfs(void*to,constvoid*from,unsignedlongn);由用户程序地址空间往核心地址空间复制voidmemcpy_tofs(void*to,constvoid*from,unsignedlongn);由核心地址空间往用户程序地址空间复制参数to为复制的目的指针,from为源指针,n为要复制的字节数。在设备驱动程序里,可以调用printk来打印
42、一些调试信息,printk的用法与printf类似。printk打印的信息不仅出现在屏幕上,同时还记录在文件syslog里。加载和卸载驱动程序 1入口函数在编写模块程序时,必须提供两个函数,intinit_module(),在加载此模块时自动调用,负责进行设备驱动程序的初始化工作。init_module()返回0,表示初始化成功,返回负数表示失败,它在内核中注册一定的功能函数。在注册之后,如果有程序访问内核模块的某个功能,内核将查表获得该功能的位置,然后调用功能函数。init_module()的任务就是为以后调用模块的函数做准备。voidcleanup_module(),在模块被卸载时调用,负
43、责进行设备驱动程序的清除工作。这个函数的功能是取消init_module()所做的事情,把init_module()函数在内核中注册的功能函数完全卸载,如果没有完全卸载,在此模块下次调用时,将会因为有重名的函数而导致调入失败。加载和卸载驱动程序 1入口函数在2.3版本以上的Linux内核中,提供了一种新的方法来命名这两个函数。例如,可以定义init_my()代替init_module()函数,定义exit_my()代替cleanup_module()函数,然后在源代码文件末尾使用下面的语句:module_init(init_my);module_exit(exit_my);这样做的好处是,每个
44、模块都可以有自己的初始化和卸载函数的函数名,多个模块在调试时不会有重名的问题。2模块加载与卸载虽然模块作为内核的一部分,但并未被编译到内核中,它们被分别编译和链接成目标文件。Linux中模块可以用C语言编写,用gcc命令编译成模块*.o,在命令行里加上-c的参数和“-D_KERNEL_-DMODULE”参数。然后用depmod-a使此模块成为可加载模块。模块用insmod命令加载,用rmmod命令来卸载,这两个命令分别调用init_module()和cleanup_module()函数,还可以用lsmod命令来查看所有已加载的模块的状态。insmod命令可将编译好的模块调入内存。内核模块与系统
45、中其他程序一样是已链接的目标文件,但不同的是它们被链接成可重定位映像。insmod将执行一个特权级系统调用get_kernel_sysms()函数以找到内核的输出内容,insmod修改模块对内核符号的引用后,将再次使用特权级系统调用create_module()函数来申请足够的物理内存空间,以保存新的模块。内核将为其分配一个新的module结构,以及足够的内核内存,并将新模块添加在内核模块链表的尾部,然后将新模块标记为uninitialized。利用rmmod命令可以卸载模块。如果内核中还在使用此模块,这个模块就不能被卸载。原因是如果设备文件正被一个进程打开就卸载还在使用的内核模块,并导致对内
46、核模块的读/写函数所在内存区域的调用。如果幸运,没有其他代码被加载到那个内存区域,将得到一个错误提示;否则,另一个内核模块被加载到同一区域,这就意味着程序跳到内核中另一个函数的中间,结果是不可预见的。2模块加载与卸载6.3 LED驱动程序设计LED接口设计 S3C2410X提供了多达117个可编程的通用I/O端口,可以方便地输入输出各种信号。SBC-2410X目标板选用S3C2410X微处理器,带有4个用户可编程I/O方式的LED,硬件原理图如图6.2所示,表6.1为LED对应的I/O口。LED控制采用低电平有效方式,当端口电平为低时点亮LED指示灯,输出高电平时LED熄灭。与LED相连的通用
47、I/O端口由表6.3所示的控制寄存器配置。.#defineGPIO_CTL_BASE0x56000000/*IO口控制寄存器及地址*/#definebGPIO(p)_REG(GPIO_CTL_BASE+(p)/*寄存器地址0X50000000p*/#defineGPBCONbGPIO(0x10)/*寄存器地址0X56000010*/#defineGPBDATbGPIO(0x14)/*寄存器地址0X56000014*/#defineGPBUPbGPIO(0x18)/*寄存器地址0X56000018*/.#defineMAKE_GPIO_NUM(p,o)(pGPIO_PORT_SHIFTT)|(o
48、GPIO_OFS_SHIFT)#definePORTB_OFS1#defineGPIO_B7MAKE_GPIO_NUM(PORTB_OFS,7)/*端口GPB7*/#defineGPIO_B8MAKE_GPIO_NUM(PORTB_OFS,8)/*端口GPB8*/#defineGPIO_B9MAKE_GPIO_NUM(PORTB_OFS,9)/*端口GPB9*/#defineGPIO_B10MAKE_GPIO_NUM(PORTB_OFS,10)/*端口GBP10*/.#defineGPCON(x)_REG2(0x56000000,(x)*0x10)/*功能选择寄存器地址*/#defineGPD
49、AT(x)_REG2(0x56000004,(x)*0x10)/*数据寄存器地址*/#defineGPUP(x)_REG2(0x56000008,(x)*0x10)/*上拉电阻设置寄存器地址*/.#defineGPIO_OFS_SHIFT0#defineGPIO_PORT_SHIFTT8#defineGPIO_PULLUP_SHIFT16#defineGPIO_MODE_SHIFT24#defineGPIO_OFS_MASK0x000000ff#defineGPIO_PORT_MASK0x0000ff00#defineGPIO_PULLUP_MASK0x00ff0000#defineGPIO_
50、MODE_MASK0xff000000#defineGPIO_MODE_IN(0GPIO_MODE_SHIFT)#defineGPIO_MODE_OUT(1GPIO_MODE_SHIFT)#defineGPIO_MODE_ALT0(2GPIO_MODE_SHIFT)#defineGPIO_MODE_ALT1(3GPIO_MODE_SHIFT)#defineGPIO_PULLUP_EN(0GPIO_PULLUP_SHIFT)#defineGPIO_PULLUP_DIS(1GPIO_MODE_SHIFT)#defineGRAB_PULLUP(x)(x)&GPIO_PULLUP_MASK)GPIO_
51、PULLUP_SHIFT)#defineGRAB_PORT(x)(x)&GPIO_PORT_MASK)GPIO_PORT_SHIFTT)#defineGRAB_OFS(x)(x)&GPIO_OFS_MASK)GPIO_OFS_SHIFT)./*端口参数设置宏*/#defineset_gpio_ctrl(x)(GPCON(GRAB_PORT(x)&=(0x3(GRAB_OFS(x)*2);GPCON(GRAB_PORT(x)|=(GRAB_MODE(x)(GRAB_OFS(x)*2);GPUP(GRAB_PORT(x)&=(1GRAB_OFS(x);GPUP(GRAB_PORT(x)|=(GRA
52、B_PULLUP(x)GRAB_OFS(x);)#defineset_gpio_pullup(x)(GPUP(GRAB_PORT(x)&=(1GRAB_OFS(x);GPUP(GRAB_PORT(x)|=(GRAB_PULLUP(x)GRAB_OFS(x);)#defineset_gpio_pullup_user(x,v)(GPUP(GRAB_PORT(x)&=(1GRAB_OFS(x);GPUP(GRAB_PORT(x)|=(v)GRAB_OFS(x);)#defineset_gpio_mode(x)(GPCON(GRAB_PORT(x)&=(0x3(GRAB_OFS(x)*2);GPCON
53、(GRAB_PORT(x)|=(GRAB_MODE(x)(GRAB_OFS(x)*2);)#defineset_gpio_mode_user(x,v)(GPCON(GRAB_PORT(x)&=(0x3(GRAB_OFS(x)*2);GPCON(GRAB_PORT(x)|=(v)(GRAB_OFS(x)*2);)#defineset_gpioA_mode(x)(GPCON(GRAB_PORT(x)&=(0x1GRAB_OFS(x);GPCON(GRAB_PORT(x)|=(GRAB_MODE(x)GRAB_OFS(x);)#defineread_gpio_bit(x)(GPDAT(GRAB_PO
54、RT(x)&(1GRAB_OFS(x)#defineread_gpio_reg(x)(GPDAT(GRAB_PORT(x)#definewrite_gpio_bit(x,v)(GPDAT(GRAB_PORT(x)&=(0x1GRAB_OFS(x);GPDAT(GRAB_PORT(x)|=(v)GRAB_OFS(x);)#definewrite_gpio_reg(x,v)(GPDAT(GRAB_PORT(x)=(v)LED驱动程序代码分析 1系统资源和宏定义#defineDEVICE_NAMEleds/*定义led设备的名字*/#defineLED_MAJOR231/*定义led设备的主设备号*
55、/staticunsignedlongled_table=/*I/O方式led设备对应的硬件资源*/GPIO_B7,GPIO_B8,GPIO_B9,GPIO_B10,;2入口函数模块的入口函数leds_init(),所做的工作是点亮I/O端口对应的LED,这些二极管可以作为状态指示之用。register_chrdev()完成字符设备在系统中的注册,并建立与文件系统的并联。staticint_initleds_init(void)intret;inti;/*在内核中注册设备*/ret=register_chrdev(LED_MAJOR,DEVICE_NAME,&leds_fops);if(ret
56、0)printk(DEVICE_NAMEcantregistermajornumbern);returnret;devfs_handle=devfs_register(NULL,DEVICE_NAME,DEVFS_FL_DEFAULT,LED_MAJOR,0,S_IFCHR|S_IRUSR|S_IWUSR,&matrix4_leds_fops,NULL);/*使用宏进行端口初始化,set_gpio_ctrl和write_gpio_bit均为宏定义*/for(i=0;i4)return-EINVAL;write_gpio_bit(led_tablearg,!cmd);default:return
57、-EINVAL;5文件系统接口定义staticstructfile_operationsleds_fops=owner:THIS_MODULE,ioctl:leds_ioctl,;6模块化module_init(leds_init);module_exit(leds_exit);用insmod命令加载模块时,调用module_init();用rmmod命令来卸载模块时,调用module_exit()函数。加载运行LED驱动程序 1应用程序设计#include#include#include#includeintmain(intargc,char*argv)inton;intled_no;int
58、fd;if(argc!=3|sscanf(argv1,%d,&led_no)!=1|sscanf(argv2,%d,&on)!=1|on1|led_no3)fprintf(stderr,Usage:ledtestled_no0|1n);exit(1);if(fd0)perror(opendeviceleds);exit(1);ioctl(fd,on,led_no);close(fd);return0;该程序首先读取命令行的参数输入,其中参数argv1赋值给led_no,表示发光二极管的序号;argv2赋值给on。led_no的取值范围是13,on取值为0或1,0表示熄灭LED,1表示点亮LED
59、。参数输入后通过fd=open(/dev/leds,0)打开设备文件,在保证参数输入正确和设备文件正确打开后,通过语句ioctl(fd,on,led_no)实现系统调用ioctl,并通过输入的参数控制LED。在程序的最后关闭设备句柄。2加载驱动首先编写Makefile文件,如下所示:INCLUDE=/usr/linux/includeEXTRA_CFLAGS=-D_KERNEL_-DMODULEI$(INCLUDE)02WallOall:leds.oledtestleds.o:leds.carm-linux-gcc$(CFLAGS)$(EXTRA_CFLAGS)cleds.coleds.ole
60、dtest:ledtest.carm-linux-gccgled.coledtestclean:rmrfleds.ormrfledtest对Makefile文件执行make命令后,可以生成驱动模块leds和测试程序ledtest了。如果不想编写Makefile文件,也可以使用手动输入命令的方式编译驱动模块:$arm-linux-gcc-D_KERNEL_-DMODULEI$(INCLUDE)02WallOcleds.coleds.o以上命令将生成leds.o文件,将该文件复制到目标板的/lib目录下,使用以下命令安装leds模块:$insmod/lib/leds.o删除该模块的命令是:$rmm
61、odleds.o应用程序编译正确后如输入:$ledtest则提示:Usage:ledtestled_no0|1若输入:$ledtest21则点亮LED3。6.4 按键驱动程序设计S3C2410X中断控制器S3C2410X中断接口函数 include/*InterruptController*/#defineIRQ_EINT00/*Externalinterrupt0*/#defineIRQ_EINT11/*Externalinterrupt1*/#defineIRQ_EINT22/*Externalinterrupt2*/#defineIRQ_EINT33/*Externalinterrupt
62、3*/#defineIRQ_EINT4_74/*Externalinterrupt47*/#defineIRQ_EINT8_235/*Externalinterrupt823*/#defineIRQ_RESERVED66/*Reservedforfutureuse*/#defineIRQ_BAT_FLT7#defineIRQ_TICK8/*RTCtimetickinterrupt*/#defineIRQ_WDT9/*Watch-Dogtimerinterrupt*/#defineIRQ_TIMER010/*Timer0interrupt*/#defineIRQ_TIMER111/*Timer1
63、interrupt*/#defineIRQ_TIMER212/*Timer2interrupt*/#defineIRQ_TIMER313/*Timer3interrupt*/#defineIRQ_TIMER414/*Timer4interrupt*/#defineIRQ_UART215/*UART2interrupt*/#defineIRQ_LCD16/*reservedforfutureuse*/#defineIRQ_DMA017/*DMAchannel0interrupt*/#defineIRQ_DMA118/*DMAchannel1interrupt*/#defineIRQ_DMA219
64、/*DMAchannel2interrupt*/#defineIRQ_DMA320/*DMAchannel3interrupt*/#defineIRQ_SDI21/*SDInterfaceinterrupt*/#defineIRQ_SPI022/*SPIinterrupt*/#defineIRQ_UART123/*UART1receiveinterrupt*/#defineIRQ_RESERVED2424#defineIRQ_USBD25/*USBdeviceinterrupt*/#defineIRQ_USBH26/*USBhostinterrupt*/#defineIRQ_IIC27/*II
65、Cinterrupt*/#defineIRQ_UART028/*UART0transmitinterrupt*/#defineIRQ_SPI129/*UART1transmitinterrupt*/#defineIRQ_RTC30/*RTCalarminterrupt*/#defineIRQ_ADCTC31/*ADCEOCinterrupt*/#defineNORMAL_IRQ_OFFSET32#/*ExternalInterrupt*/#defineIRQ_EINT4(0+NORMAL_IRQ_OFFSET)#defineIRQ_EINT5(1+NORMAL_IRQ_OFFSET)#defi
66、neIRQ_EINT6(2+NORMAL_IRQ_OFFSET)#defineIRQ_EINT7(3+NORMAL_IRQ_OFFSET)#defineIRQ_EINT8(4+NORMAL_IRQ_OFFSET)#defineIRQ_EINT9(5+NORMAL_IRQ_OFFSET)#defineIRQ_EINT10(6+NORMAL_IRQ_OFFSET)#defineIRQ_EINT11(7+NORMAL_IRQ_OFFSET)#defineIRQ_EINT12(8+NORMAL_IRQ_OFFSET)#defineIRQ_EINT13(9+NORMAL_IRQ_OFFSET)#defi
67、neIRQ_EINT14(10+NORMAL_IRQ_OFFSET)#defineIRQ_EINT15(11+NORMAL_IRQ_OFFSET)#defineIRQ_EINT16(12+NORMAL_IRQ_OFFSET)#defineIRQ_EINT17(13+NORMAL_IRQ_OFFSET)#defineIRQ_EINT18(14+NORMAL_IRQ_OFFSET)#defineIRQ_EINT19(15+NORMAL_IRQ_OFFSET)#defineIRQ_EINT20(16+NORMAL_IRQ_OFFSET)#defineIRQ_EINT21(17+NORMAL_IRQ_
68、OFFSET)#defineIRQ_EINT22(18+NORMAL_IRQ_OFFSET)#defineIRQ_EINT23(19+NORMAL_IRQ_OFFSET) /*51*/#defineSHIFT_EINT4_7IRQ_EINT4_7#defineSHIFT_EINT8_23IRQ_EINT8_23#defineEXT_IRQ_OFFSET(20+NORMAL_IRQ_OFFSET)/*subInterrupt*/#defineIRQ_RXD0(0+EXT_IRQ_OFFSET)#defineIRQ_TXD0(1+EXT_IRQ_OFFSET)#defineIRQ_ERR0(2+E
69、XT_IRQ_OFFSET)#defineIRQ_RXD1(3+EXT_IRQ_OFFSET)#defineIRQ_TXD1(4+EXT_IRQ_OFFSET)#defineIRQ_ERR1(5+EXT_IRQ_OFFSET)#defineIRQ_RXD2(6+EXT_IRQ_OFFSET)#defineIRQ_TXD2(7+EXT_IRQ_OFFSET)#defineIRQ_ERR2(8+EXT_IRQ_OFFSET)#defineIRQ_TC(9+EXT_IRQ_OFFSET)#defineIRQ_ADC_DONE(10+EXT_IRQ_OFFSET)/*62*/#defineSHIFT_
70、UART0IRQ_UART0#defineSHIFT_UART1IRQ_UART1#defineSHIFT_UART2IRQ_UART2#defineSHIFT_ADCTCIRQ_ADCTC#defineSUB_IRQ_OFFSET(11+EXT_IRQ_OFFSET)#defineIRQ_UNKNOWNSUB_IRQ_OFFSET#defineNR_IRQSSUB_IRQ_OFFSET#defineOS_TIMERIRQ_TIMER41宏定义直接使用写1的方式进行清中断操作。#define ClearPending(x)SRCPND=(1(x);INTPND=(1(x);#defineEIN
71、T_OFFSET(x)(x)-NORMAL_IRQ_OFFSET+4)#defineSUBIRQ_OFFSET(x)(x)-EXT_IRQ_OFFSET)#defineEXTINT_MASK0x7#if0#defineEXTINT_OFFSET0x4#defineEXTINT_MASK0x7intset_EXT_IRQ_mode(intirq,intedge)EXPORT_SYMBOL(set_EXT_IRQ_mode);#endif其中set_EXT_IRQ_mode(intirq,intedge)函数的irq用来设定中断号,edge用来设定采用的边沿触发方式。2禁用和使能中断常规中断禁用并
72、清中断源:staticvoids3c2410_mask_ack_irq(unsignedintirq)常规禁用中断:staticvoids3c2410_mask_irq(unsignedintirq)常规使能中断:staticvoids3c2410_unmask_irq(unsignedintirq)外部中断禁用并清中断源:staticvoidEINT4_23mask_ack_irq(unsignedintirq)外部禁用中断:staticvoidEINT4_23mask_irq(unsignedintirq)外部使能中断:staticvoidEINT4_23unmask_irq(unsign
73、edintirq)子中断禁用并清中断源:staticvoidSUB_mask_ack_irq(unsignedintirq)子中断禁用:staticvoidSUB_mask_irq(unsignedintirq)子中断使能:staticvoidSUB_unmask_irq(unsignedintirq)带子中断处理的函数,需要清除子mask、pend、INTMASK和INTPND中的对应位。3获取中断号操作获得可以访问中断处理函数的irq号码(0-62)。获得子中断号码:inlineunsignedintget_subIRQ(intirq,intbegin,intend,intfail_irq
74、)其中begin和end为起止中断号,fail_irq为查询失误后返回值。获得外部中断号码:inlineunsignedintget_extIRQ(intirq,intbegin,intend,intfail_irq)获得irq号:unsignedintfixup_irq(intirq)获得irq号码,在extint中使用:staticintinlinefixup_irq_num(intirq)4中断初始化函数在使用S3C2410X微处理器时,可以采用初始化函数:void_inits3c2410_init_irq(void)在完成对处理器中断的初始化后,s3c2410_init_irq中没有将
75、外部中断置为有效,实际上也不可能置为有效,这需要在用户驱动程序中设置。设置时,可以使用外部中断初始化函数:intset_external_irq(intirq,intedge,intpullup)该函数中引用了端口设置函数:staticvoidinlineset_gpios(intirq,intpullup)该函数通过对GPF和GPG相关寄存器的操作,实现对EINT023的端口模式的设置。由于使用gpio,所以需要制定指为irq,且上拉电阻无效。上拉电阻一般在中断设置时无效,端口使用时有效。键盘驱动代码设计在键盘接口电路中,使用GPIO端口,复用为中断和数据方式,键盘按下或者抬起都将导致中断,
76、并在中断处理过程中转换为数据端口方式,读入数据,然后再改为中断方式,等待中断。部分程序代码如下。1系统资源和宏定义#defineDEVICE_NAMEbuttons/*定义按键设备名*/#defineBUTTON_MAJOR232/*定义按键主设备号*/*定义按键所使用的CPU资源*/staticstructkey_infointirq_no;unsignedintgpio_port;intkey_no;key_info_tab4=IRQ_EINT1,GPIO_F1,1,IRQ_EINT2,GPIO_F2,2,IRQ_EINT3,GPIO_F3,3,IRQ_EINT7,GPIO_F7,4,;2
77、中断服务程序staticvoidirq(intirq,void*dev_id,structpt_regs*reg)structkey_info*k;inti;intfound=0;intup;intflags;/*使用irq号码判断是否是某个键盘,因为中断程序被4个按键中断共用*/for(i=0;iirq_no=irq)found=1;break;if(!found)printk(badirq%dinbuttonn,irq);return;save_flags(flags);/*保存当前中断状态*/cli();/置位cpsr,禁用中断*/*复用中断端口,从端口读取电平高低值*/set_gpio
78、_mode_user(k-gpio_port,GPIO_MODE_IN);up=read_gpio_bit(k-gpio_port);/*复用中断端口,从端口读取电平高低值*/set_external_irq(k-irq_no,EXT_BOTH_EDGES,GPIO_PULLUP_DIS);restore_flags(flags);/*恢复中断状态*/*保存从中断读出的值*/if(up)key_value=k-key_no+0x80;elsekey_value=k-key_no;ready=1;/*置位全局状态*/wake_up_interruptible(&buttons_wait);/*等
79、待任务唤醒,select调用的进程*/申请系统中断,中断方式为双边触发,即在上升沿和下降沿均发生中断:staticintrequest_irqs(void)structkey_info*k;inti;for(i=0;iirq_no,EXT_BOTH_EDGES,GPIO_PULLUP_DIS);/*申请中断资源*/if(request_irq(k-irq_no,&buttons_irq,SA_INTERRUPT,DEVICE_NAME,&buttons_irq)return-1;return0;释放中断:staticvoidfree_irqs(void)structkey_info*k;int
80、i;for(i=0;iirq_no,buttons_irq);3文件系统的读函数实现staticintbuttons_read(structfile*file,char*buffer,size_tcount,loff_t*ppos)staticintkey;intflags;intrepeat;if(!ready)return-EAGAIN;if(count!=sizeofkey_value)return-EINVAL;save_flags(flags);if(key!=key_value)key=key_value;repeat=0;elserepeat=1;restore_flags(fl
81、ags);if(repeat)return-EAGAIN;/*使用copy_to_user把键值送到用户空间*/copy_to_user(buffer,&key,sizeofkey);ready=0;returnsizeofkey_value;4ioctl函数staticintbuttons_ioctl(structinode*inode,structfile*file,unsignedintcmd,unsignedlongarg)switch(cmd)default:return-EINVAL;5文件系统接口和字符设备定义staticstructfile_operationsbuttons_
82、fops=owner:THIS_MODULE,ioctl:buttons_ioctl,poll:buttons_select,read:buttons_read,;staticdevfs_handle_tdevfs_handle;6系统入口函数/*按键初始化*/staticint_initbuttons_init(void)intret;ready=0;/*注册按键设备*/ret=register_chrdev(BUTTON_MAJOR,DEVICE_NAME,&buttons_fops);if(ret0)printk(DEVICE_NAMEcantregistermajornumbern);
83、returnret;ret=request_irqs();/*申请中断资源*/if(ret)unregister_chrdev(BUTTON_MAJOR,DEVICE_NAME);printk(DEVICE_NAMEcantrequestirqsn);returnret;/*注册devfs,从而可以使用devfs文件系统*/devfs_handle=devfs_register(NULL,DEVICE_NAME,DEVFS_FL_DEFAULT,BUTTON_MAJOR,0,S_IFCHR|S_IRUSR|S_IWUSR,&buttons_fops,NULL);return0;staticvo
84、id_exitbuttons_exit(void)devfs_unregister(devfs_handle);free_irqs();unregister_chrdev(BUTTON_MAJOR,DEVICE_NAME);完整的程序位于buttons/buttons.c中,该程序参考了友善之臂公司提供的按键测试程序matrix5-buttons.c。使用手工输入命令的方式编译buttons驱动模块:$arm-linux-gcc-D_KERNEL_-I/kernel/include-DKBUILD_BASENAME=buttonsDMODULEcobuttons.obuttons.c以上命令将
85、生成buttons.o文件,把该文件复制到目标板的/lib目录下,使用以下命令安装buttons模块:$insmod/lib/buttons.o卸载该模块的命令是:$rmmodbuttons.o按键测试程序的实现 #include#include#include#include#include#include#include#include#include#includeintmain(void)intbuttons_fd;intkey_value;buttons_fd=open(/dev/buttons,0);if(buttons_fd0)perror(opendevicebuttons);
86、exit(1);for(;)fd_setrds;intret;FD_ZERO(&rds);FD_SET(buttons_fd,&rds);ret=select(buttons_fd+1,&rds,NULL,NULL,NULL);if(ret0)perror(select);exit(1);if(ret=0)printf(Timeout.n);elseif(FD_ISSET(buttons_fd,&rds)intret=read(buttons_fd,&key_value,sizeofkey_value);if(ret!=sizeofkey_value)if(errno!=EAGAIN)perr
87、or(readbuttonsn);continue;elseprintf(buttons_value:%dn,key_value);close(buttons_fd);return0;有了按键驱动程序,就可以在应用程序中编写驱动调用函数,然后利用文件操作实现对按键的各种操作了。完整的应用程序源代码清单如下:1文件描述符集fd_set在linux/types.h中存在对文件描述符集fd_set的定义:typedef_kernel_fd_set fd_set;而在linux/posix_types中存在结构的声明:typedefstructunsignedlongfds_bits_FDSET_LO
88、NGS;_kernel_fd_setfd_set实际上是long类型的数组,每一个数组元素都能与一个打开的文件句柄建立联系,不管是Socket句柄,还是其他文件或设备句柄。调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一个文件可读。2select()系统调用函数select()系统调用可以使进程检测同时等待的多个I/O设备,当没有设备准备好时,select()阻塞,其中任一设备准备好时,select()就返回。select()的调用形式为:intselect(intmaxfd,fd_set*readfds,fd_set*writefd
89、s,fe_set*exceptfds,conststructtimeval*timeout);参数maxfd指定文件描述符集中要被检测的比特数,至少要比待检测的最大文件描述符大1。参数readfds指定被读监控的文件描述符集。参数writefds指定被写监控的文件描述符集。参数exceptfds指定被例外条件监控的文件描述符集。参数timeout起了定时器的作用,到了指定的时间,无论是否有设备准备好,都返回调用。timeout取不同的值,该调用就表现不同的性质。timeout为0,调用立即返回;timeout为NULL,select()一被调用就阻塞,直到知道有文件描述符就绪;timeout为
90、正整数,就是一般的定时器。select调用返回时,除了那些已经就绪的描述符外,select将清除readfds、writefds和exceptfds中的所有没有就绪的描述符。select的返回值有如下情况:正常情况下返回就绪的文件描述符个数;经过timeout时长后仍无设备准备好,返回值为0;如果select被某个信号中断,它将返回1并设置errno为EINTR;如果出错,返回1并设置相应的errno。3系统提供了4个宏对描述符集进行操作:voidFD_SET(intfd,fd_set*fdset);voidFD_CLR(intfd,fd_set*fdset);voidFD_ISSET(int
91、fd,fd_set*fdset);voidFD_ZERO(fd_set*fdset);宏FD_SET设置文件描述符集fdset中对应于文件描述符fd的位(设置为1);宏FD_CLR清除文件描述符集fdset中对应于文件描述符fd的位(设置为0);宏FD_ZERO清除文件描述符集fdset中的所有位(即把所有位都设置为0)。使用这3个宏在调用select前设置描述符屏蔽位,在调用select后使用FD_ISSET来检测文件描述符集fdset中对应于文件描述符fd的位是否被设置。过去,描述符集用一个整数位屏蔽码来实现,但是这种实现对于多于32个的文件描述符将无法工作。描述符集现在通常用整数数组中的
92、位域表示,数组元素的每一位对应一个文件描述符。宏FD_SET设置整数数组中对应于fd文件描述符的位为1;宏FD_CLR设置整数数组中对应于fd文件描述符的位为0;宏FD_ZERO设置整数数组中的所有位都为0。4read/write函数定义:ssize_tread(intfd,void*buffer,size_tcount);ssize_twrite(intfd,constvoid*buffer,size_tcount);fd是我们要进行读写操作的文件描述符,buffer是我们要写入文件内容或读出文件内容的内存存放地址,count是我们要读写的字节数。对于普通的文件read从指定的文件(fd)中读取count个字节到buffer缓冲区中(注意:必须提供一个足够大的缓冲区),同时返回count。如果read读到了文件的结尾或者被一个信号中断,返回值会小于count。如果是由信号中断引起返回,而且没有返回数据,read会返回1,且设置errno为EINTR。当程序读到文件结尾的时候,read会返回0。write从buffer中写count个字节到文件fd中,成功时返回实际所写的字节数。