嵌入式操作系统(Linu进程及设备驱动程序).ppt
Linux进程间通信及驱动程序,软件学院 张亮,西安电子科技大学软件学院,2,通信目的,进程间的数据传递,A向B传送,进程间的数据共享,一个修改后,别的可以看到,通知事件:一个进程要向一个或者一组进程发送消息,表明某种事件的产生,资源共享:多个进程要共享同样的资源,需要加锁和同步,进程控制:需要控制另外一个进程的执行(知道他的状态信息),西安电子科技大学软件学院,3,通信方法,信号,管道,消息,信号量,共享内存,套接字,自定义,西安电子科技大学软件学院,4,通信方法 信号,最古老的进程间通信方式之一,内核为进程产生信号,以表示不同的事件,这些事件就是信号源,异常:进程运行过程产生的异常,其他进程:一个进程可以向另一一个或者一组进程发送信号,终端中断:Ctrl-C,报警:计时器到期,其他:通知。例如IO就绪,西安电子科技大学软件学院,5,通信方法 信号处理,内核对信号的5种缺省动作,异常终止,将进程的地址空间内容、寄存器内容保存到一个core文件中,终止进程,退出:不产生core文件,终止进程,忽略:不处理该信号,停止:挂起该进程,继续:如果该进程被挂起,则恢复进程运行,否则,忽略该信号,西安电子科技大学软件学院,6,通信方法 信号处理,信号的特定处理动作,忽略,但是SIGKILL、SIGSTOP不能被忽略,阻塞信号:进程可以选择阻塞某些信号,先将到来的信号记录下来,等到以后解除阻塞后再处理,进程处理该信号:注册处理程序,信号对照含义?,SIGHUP、SIGINT、SIGQUIT、SIGFPE、SIGKILL、SIGALRM、SIGTERM、SIGHLD、SIGSTOP,西安电子科技大学软件学院,7,通信方法 信号处理,系统调用,Signal、sigaction,定时器相关:alarm,settimer,西安电子科技大学软件学院,8,通信方法 管道,特点,单向的、先进先出、无结构、固定大小的字节流,一个读、一个写,管道大小的定义:PIPE_BUF,在include/linux/limits.h中,一般大小为4096,西安电子科技大学软件学院,9,通信方法 管道调用,系统调用,Pipe(),读、写操作,输入参数int filedes2返回值中filedes0为读句柄filedes1为写句柄,西安电子科技大学软件学院,10,通信方法 消息,使用消息队列标识符进行标识,Msgget创建一个新队列或者打开一个已经存在的队列,Msgsnd向队列末端添加一条新消息,Msgrcv从队列中取消息,注:取消息不一定是按照先进先出原则,也可以按照消息类型字段获取(msgrcv方式决定),西安电子科技大学软件学院,11,通信方法-信号量,用于保护临界资源,以及进程间同步,初始化函数sem_init(),注意参数,等待信号量可用sem_wait/sem_trywait,释放信号量sem_post,西安电子科技大学软件学院,12,通信方法-共享内存,进程间数据通信方法,直接读写内存,不需要任何数据拷贝,管道、消息机制都需要在内核、用户空间进行数据拷贝(系统调用过多),Shm_get用于创建或者获取标识符,Shm_at用于获取对应的地址,Shm_dt用于删除,西安电子科技大学软件学院,13,通信方法-套接字,进程间数据通信方法,事件形式,RAW_SOCKET,发送、接收都一致,西安电子科技大学软件学院,14,什么是设备驱动程序,设备驱动程序就是外部设备的软件抽象,或者说是软件表现,是系统看到的设备,是虚拟的设备。,一个外设的性能在很大程度上取决于驱动程序的性能。驱动程序之于外部设备,犹如操作系统之于硬件环境。,西安电子科技大学软件学院,15,驱动程序有什么用,Linux下的设备驱动程序都是一个个独立的“黑盒子”,使某个特定的硬件响应一个定义良好的内部编程接口,同时完全隐藏了设备的工作细节。,用户对设备的操作通过一组标准化的系统调用来完成。驱动程序就是将这些调用映射到作用于实际硬件的设备特定的操作上(写寄存器命令)。,西安电子科技大学软件学院,16,系统调用接口,设备1,设备2,设备3,驱动1,驱动2,驱动3,用户程序,用户空间,内核空间,内核子系统,其它模块,其它模块,硬件,驱动程序在内核中的位置,西安电子科技大学软件学院,17,模块化的驱动程序,为了使系统更有效的运行,Linux支持内核的动态扩展,即在系统运行时给内核增加新的功能(模块)。,驱动程序就是几个可以模块化的功能之一。这也是Linux下驱动程序与Windows下驱动程序的最大区别。,西安电子科技大学软件学院,18,什么是模块,模块是一段没有链接的目标代码(.o)可由insmod程序动态的链接到正在运行的内核链接后,它就成了内核的一部分,直到用rmmod程序解除链接。和用户函数库的动态链接有些相似;但函数的功能在链接前就已明确,而模块连接时才注册自己的功能。,西安电子科技大学软件学院,19,系统内核,西安电子科技大学软件学院,20,驱动程序,系统内核,西安电子科技大学软件学院,21,驱动程序,将驱动程序加入到内核中,系统内核,西安电子科技大学软件学院,22,西安电子科技大学软件学院,23,核心模块与应用程序的对比,西安电子科技大学软件学院,24,简单驱动程序举例子-2.4,#ifndef MODULE#define MODULE#endif#ifndef _KERNEL_#define _KERNEL_#endif#include#include int init_module(void)printk(KERN_ALERT“Hello World!n”);return 0;,西安电子科技大学软件学院,25,void cleanup_module(void)printk(KERN_ALERT“Goodbye!n”);MODULE_LICENSE(“GPL”)/为了避免”no license”警告-MakeFile文件 MODULECFLAGS:=-I/usr/src/linux-2.4/include DMODULE D_KERNEL_ hello.o:hello.c gcc$(MODULECFLAGS)c hello.c.PHONY:clean clean:rm f hello.o,简单驱动程序举例子-2.4,注意之处1:2.4内核中,MODULE和_KERNEL_宏必须定义,无论是在编译选项时还是在源文件代码中。保险期间:两边都写,同时使用ifdef宏加了保护,不会出现重复定义2:如果在printk打印时候不使用KERN_ALERT宏,则linux/kernel.h可以不包括3:头文件linux/module.h必须添加4:初始化必须是init_module,退出函数必须是cleanup_module,西安电子科技大学软件学院,26,简单驱动程序举例子-2.4,西安电子科技大学软件学院,27,简单驱动程序举例子-2.6,#include#include#include MODULE_LICENSE(Dual BSD/GPL);static char*whom=world;module_param(whom,charp,0);static int howmany=1;module_param(howmany,int,0);static int hello_init(void)int i;for(i=0;ihowmany;i+)printk(KERN_ALERT(%d)Hello,%s!n,i,whom);return 0;,西安电子科技大学软件学院,28,简单驱动程序举例子-2.6,static void hello_exit(void)printk(KERN_ALERT Goodbye!n);module_init(hello_init);module_exit(hello_exit);-Makefile文件:obj-m+=hello.oall:make-C/lib/modules/$(shell uname-r)/build M=$(PWD)modulesclean:make-C/lib/modules/$(shell uname-r)/build M=$(PWD)clean,2.6加载insmod hello.ko howmany=5 whom=islab注意事项初始化和退出函数可以写成任何名字,使用module_init以及module_exit宏调用一下即可,本质和2.4的init,cleanup一致参数传递的注意事项信息查看在cat/var/log/messages,西安电子科技大学软件学院,29,简单驱动程序举例子-2.6,2.6内核模块参数传递,内核提供了一个简单框架,允许驱动程序声明参数,并且用户在系统启动或者模块装载时候为参数指定相应的值。-必须包含linux/moduleparam.h文件,西安电子科技大学软件学院,30,模块参数定义方式module_param(int_name,type,perm);module_param_named(ext_name,int_name,type,perm);module_param_string(ext_name,string,len,perm);module_param_array(int_name,type,nump,perm);module_param_array_named(ext_name,int_name,type,nump,perm);使用宏MODULE_PARM_DESC()对定义的参数进行说明,各个参数含义int_name:当参数中没有ext_name的时候,此参数即是用户看到的参数名,又是模块内接受参数的变量,西安电子科技大学软件学院,31,2.6内核模块参数传递,len:当参数是string的时候,表示字符串string数组的大小,type:表示参数的数据类型,可以为byte,short,ushort,int,uint,long,ulong,charp,bool,invbool,perm:指定了在sysfs中相应文件的访问权限。(一般不考虑),ext_name:用户看到的参数名,对外的参数名,nump:指向一个整数,其值表示有多少个参数存放在数组中,module_param_array_named(ext_name,int_name,type,nump,perm);,模块参数传递举例,西安电子科技大学软件学院,32,#include#include#include#define MAX_ARRAY 6static int int_var=0;static const char*str_var=default;static int int_array6;int narr;module_param(int_var,int,0);MODULE_PARM_DESC(int_var,A integer variable);module_param(str_var,charp,0);MODULE_PARM_DESC(str_var,A string variable);module_param_array(int_array,int,模块参数传递举例,西安电子科技大学软件学院,33,static int _init hello_init(void)int i;printk(KERN_ALERT Hello,my LKM.n);printk(KERN_ALERT int_var%d.n,int_var);printk(KERN_ALERT str_var%s.n,str_var);for(i=0;i narr;i+)printk(int_array%d=%dn,i,int_arrayi);return 0;static void _exit hello_exit(void)printk(KERN_ALERT Bye,my LKM.n);module_init(hello_init);module_exit(hello_exit);加载命令:insmod hello_param.ko int_var=100 str_var=hello int_array=100,200,西安电子科技大学软件学院,34,设备的分类,字符设备 块设备 网络接口设备,西安电子科技大学软件学院,35,字符设备,字符设备是能够像字节流(例如文件)一样被访问的设备,一般不使用缓存技术。字符设备驱动程序实现这种特性至少需要实现open、close、read和write系统调用。,西安电子科技大学软件学院,36,块设备,对块设备来说,最大的不同就是能够容纳文件系统(例如磁盘),并且大都使用缓存技术。块设备和字符设备的区别仅仅在于内核内部管理数据的方式,也就是内核和驱动程序的接口不同。然而这些差异对用户是透明的。另外,块设备的接口必须支持挂载(mount)文件系统。典型的块设备mtd,西安电子科技大学软件学院,37,网络接口,任何网络事务都要经过一个网络接口来完成。网络接口由内核中的网络子系统驱动,负责发送和接收数据包,但它无需了解每项事务是如何映射实际传送的数据包的。内核和网络驱动程序之间的通信完全不同于内核和字符以及块设备驱动程序之间的通信,内核调用一套和数据包传输相关的函数,而不是read、write等。,西安电子科技大学软件学院,38,初识两个结构,file结构:在内核中标识一个打开的设备(文件)file_operations结构:用于访问驱动程序的函数指针如果用面向对象的概念来考虑问题,那么file可以看作一个对象,而操作它的函数(由file_operations结构标识)就是对象的方法。,西安电子科技大学软件学院,39,file结构,file结构是在中定义的一个数据结构,用来代表一个打开的文件(包括设备文件和普通文件)。它与用户空间程序中的FILE没有任何关联。,西安电子科技大学软件学院,40,struct filemode_tf_mode,loff_tf_pos,unsigned intf_flags,struct file_operations*f_op,void*private_data,struct dentry*f_dentry,西安电子科技大学软件学院,41,file_operations结构,file_operations结构是一个定义在中的函数指针数组。每个文件都通过file结构中的f_op字段与它自己的函数集相关联。这些函数负责系统调用的实现,而这个结构负责系统调用的映射。,西安电子科技大学软件学院,42,Struct file_operationsssize_t(*read)(struct file*,char*,size_t,loff_t*),ssize_t(*write)(struct file*,char*,size_t,loff_t*)int(*ioctl)(struct inode*,struct file*,unsigned int,unsigned long),int(*open)(struct inode*,strct file*),西安电子科技大学软件学院,43,这两个数据结构是我们理解驱动程序的最基本的数据结构。file结构是系统生成的,编写驱动程序时会用就行。而file_operations是要开发者自己来实现的。这个数据结构是我们学习的重点,它可采用如下的方式来声明:,西安电子科技大学软件学院,44,struct file_operations mydev_fops=llseek:mydev_llseek,read:mydev_read,write:mydev_write,ioctl:mydev_ioctl,open:mydev_open,release:mydev_release,;,西安电子科技大学软件学院,45,设备驱动在系统内部的组织,主设备号次设备号,在系统中,设备类型和主设备号唯一标识驱动程序,而次设备号是由主设备号确定的驱动程序来使用的,它对系统没有任何意义。,在驱动程序中,次设备号用来区分共享同一驱动的不同设备或者不同功能。,西安电子科技大学软件学院,46,驱动程序向系统注册,int register_chrdev(unsigned int major,const char*name,struct file_operations*fops);,在传统方式下,向系统添加一个驱动程序就意味着为其分配一个主设备号。主设备号在驱动模块注册时就必须提供。,参数major就是显式的为该驱动分配的主设备号。也可以动态的分配(major0)。,西安电子科技大学软件学院,47,设备文件节点,应用程序如何知道这个看起来很丑陋的主设备号(就像IP一样难以记忆),聪明的内核开发者早就为我们考虑到了给设备起个名字(就像域名一样)。,给设备起名字,就是在文件系统上显式的创建一个文件节点,即设备文件。因此,应用程序看到的设备和文件是一样的,他们都是文件系统上的一个节点,有着相同的接口。,西安电子科技大学软件学院,48,创建设备文件节点,mknod/dev/mydev1 c 254 0mydev1就是设备的名字,它是/dev目录下的一个文件节点 c表示字符设备 254是主设备号 0是次设备号,西安电子科技大学软件学院,49,字符设备驱动程序表chrdevs,struct device_struct const char*name;struct file_oprations*fops;static struct device_struct chrdevsMAX_CHRDEV;,西安电子科技大学软件学院,50,chrdevs 是个全局变量,它在字符设备管理中处于核心地位。Linux通过它组织起MAX_CHRDEV(255)个device_struct结构来记录相关设备的名称以及其对应的设备操作函数接口(fops)。每个已加载的字符设备在该数组中占有一项,而主设备号就是该结构的数组下标。驱动程序的动态加载和卸载,就是动态的增加或删除chrdevs数组中的某一项。,西安电子科技大学软件学院,51,chrdevs,device_struct,file_operations,0,1,2,3,4,5,6,西安电子科技大学软件学院,52,驱动程序的注册,当有驱动模块向系统注册时(假定主设备号为x,且大于0),系统检查chrdevs数组x项是否为空,若空,用注册函数提供的参数填充该项内容,然后正确返回;若非空,说明这个主设备号已经被占用了,出错返回。如果驱动模块提供的主设备号是0(动态分配),系统会从chrdevs数组中由高到低寻找一个空的位置,填充,返回。,西安电子科技大学软件学院,53,一个完整的驱动程序框架,#include#include#include#include#include#define DEV_NAMETestDevice#define DEV_MAJOR0#define READ_BUF_SIZE1024#define WRITE_BUF_SIZE1024,int major=DEV_MAJOR struct Mydevice const char*name;/*设备的名字*/unsigned int major;/*主设备号*/unsigned int minor;/*次设备号*/unsigned char*read_buffer;/*读缓冲区*/unsigned char*write_buffer;/*写缓冲区*/wait_queue_head_t read_queue;/*读等待队列*/wait_queue_head_t write_queue;/*写等待队列*/struct semaphoresem;/*竞态时用到的信号量*/;,54,西安电子科技大学软件学院,int my_open(struct inode*inode,struct file*filp)struct Mydevice*dev=kmalloc(sizeof(struct Mydevice),GFP_KERNEL);if(dev=NULL)printk(KERN_ALERT allocate device memory failed.n);return(-ENOMEM);dev-name=DEV_NAME;dev-major=MAJOR(inode-i_rdev);dev-minor=MINOR(inode-i_rdev);dev-read_buffer=kmalloc(sizeof(READ_BUF_SIZE),GFP_KERNEL);if(dev-read_buffer=NULL)printk(KERN_ALERT allocate read buffer memory failed.n);dev-write_buffer=kmalloc(sizeof(WRITE_BUF_SIZE),GFP_KERNEL);if(dev-read_buffer=NULL)printk(KERN_ALERT allocate write buffer memory failed.n);init_waitqueue_head(,55,西安电子科技大学软件学院,int my_release(struct inode*inode,struct file*filp)struct Mydevice*dev=filp-private_data;if(dev-read_buffer!=NULL)kfree(dev-read_buffer);if(dev-write_buffer!=NULL)kfree(dev-write_buffer);kfree(dev);printk(The function of Myrelease has been called!n);return 0;,56,西安电子科技大学软件学院,ssize_t my_read(struct file*filp,char*buf,size_t count,loff_t*offp)char*pdata=kmalloc(sizeof(count),GFP_KERNEL);if(pdata=NULL)return(-ENOMEM);copy_to_user(buf,pdata,count);*offp+=count;printk(The function of Myread has been called!n);return count;,57,西安电子科技大学软件学院,ssize_t my_write(struct file*filp,char*buf,size_t count,loff_t*offp)char*pdata=kmalloc(sizeof(count),GFP_KERNEL);if(pdata=NULL)return(-ENOMEM);copy_from_user(pdata,buf,count);*offp+=count;printk(The function of Myread has been called!n);return count;,58,西安电子科技大学软件学院,int my_ioctl(struct inode*inode,struct file*filp,unsigned int cmd,unsigned long arg)switch(cmd)case 1:printk(This is command 1!n);break;case 2:printk(This is command 2!n);break;case 3:printk(This is command 3!n);break;default:printk(There is no such command!n);return-1;return 0;,59,西安电子科技大学软件学院,struct file_operations fops=open:my_open,/*open函数*/release:my_release,/*write函数*/read:my_read,/*read函数*/write:my_write,/*write函数*/ioctl:my_ioctl,/*ioctl函数*/;用上面声明的各函数声明文件操作结构file_operations。,60,西安电子科技大学软件学院,int my_init(void)int res=register_chrdev(DEV_MAJOR,DEV_NAME,61,西安电子科技大学软件学院,int my_cleanup(void)unregister_chrdev(major,DEV_NAME);printk(My device release success!n);return 0;module_init(my_init);module_exit(my_cleanup);MODULE_LICENSE(GPL);,62,西安电子科技大学软件学院,西安电子科技大学软件学院,63,傻瓜步骤,设计驱动程序1.1实现各个文件操作函数1.2声明函数指针数组1.3完成初始化函数,并向系统注册。完成清除函数。建立设备文件节点mknod/dev/testdev c xxx 0 设计测试应用程序,西安电子科技大学软件学院,64,傻瓜步骤,加载驱动程序insmodchar_dev.o运行应用程序进行测试卸载驱动程序rmmodchar_dev,西安电子科技大学软件学院,65,谢谢!,