资源预览内容
第1页 / 共86页
第2页 / 共86页
第3页 / 共86页
第4页 / 共86页
第5页 / 共86页
第6页 / 共86页
第7页 / 共86页
第8页 / 共86页
第9页 / 共86页
第10页 / 共86页
亲,该文档总共86页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述
第二部分Linux设备驱动程序1第一章设备驱动简介2设备驱动程序的角色是内核的一部分,属于内核中的设备管理子系统是应用程序和实际设备间的软件层提供对硬件的基本操作,如open,read,write,ioctl,close等只提供硬件操作机制,如何使用硬件(操作策略)应由应用决定,驱动不应该包含策略驱动程序既可以直接编译到内核中(zImage),或者编译为可动态加载的模块(.ko文件,用insmod程序加载)3Linux设备的分类字符( char ) 设备-是一种可以按字节流来存取的设备-实现 open, close, read, 和 write等系统调用-文本控制台( /dev/console )和串口( /dev/ttyS0)、内存、Flash等块(block)设备-按整块数据存取(如512字节)-如磁盘设备(/dev/hda)-可以带有文件系统网络设备-负责网络数据包的发送和接收,如eth04第二章内核模块基础5模块代码结构hello world模块实例分析hello.c6初始化和退出函数初始化函数module_init()-由insmod调用-注册设备,请求资源等退出函数module_exit()-由rmmod调用-取消设备注册、释放资源等初始化中的错误处理(goto的使用)int _init my_init_function(void) int err; /* registration takes a pointer and a name */ err = register_this(ptr1, skull); if (err) goto fail_this; err = register_that(ptr2, skull); if (err) goto fail_that; err = register_those(ptr3, skull); if (err) goto fail_those; return 0; /* success */fail_those: unregister_that(ptr2, skull);fail_that: unregister_this(ptr1, skull);fail_this: return err; /* propagate the error */7模块的编译和加载模块编译Makefile分析Makefile8模块加载参数参数的值可由 insmod 或者 modprobe 在加载时指定参数类型可以是bool,charp,int等声明方式module_param()如static char *whom = world;static int howmany = 1;module_param(howmany, int, S_IRUGO);module_param(whom, charp, S_IRUGO);hellop.c9课后练习输入helloworld模块例子代码,编译并加载模块给helloworld模块增加参数,重新编译并加载10第三章字符设备驱动程序11设备文件和设备号Linux上的设备操作都是通过设备文件进行如crw-rw-rw- 1 root tty 4, 64 Apr 11 2002 /dev/ttyS0 c表示字符设备,b表示块设备主设备号标识设备相连的驱动,次设备号决定引用哪个设备12设备号的分配和释放静态分配int register_chrdev_region(dev_t first, unsigned int count, char *name);-first :起始设备编号(通常为0)-count:请求的设备编号个数-name:设备名动态分配int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);-dev:内核分配的主次设备号释放设备号void unregister_chrdev_region(dev_t first, unsigned int count); 注意:一定要检查返回值,确保分配成功!13关键数据结构include/linux/fs.hstruct (定义设备操作方法)struct struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char _user *, size_t, loff_t *);ssize_t (*aio_read) (struct kiocb *, char _user *, size_t, loff_t);ssize_t (*write) (struct file *, const char _user *, size_t, loff_t *);。int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);。int (*open) (struct inode *, struct file *);int (*flush) (struct file *);int (*release) (struct inode *, struct file *);struct file(对应每个打开的文件,在open时创建)struct inode(内核内部表示文件的结构)14字符设备初始化和注册(1)字符设备用cdev结构表示cdev结构的初始化struct cdev *my_cdev = cdev_alloc();my_cdev-ops = &my_fops;或者void cdev_init(struct cdev *cdev, struct *fops);15字符设备初始化和注册(2)设备注册int cdev_add(struct cdev *dev, dev_t num, unsigned int count);-dev:cdev结构-num:设备编号-count:设备数(通常是1)设备注销void cdev_del(struct cdev *dev);16设备的方法(1)open方法(打开设备)-初始化设备、分配资源等int (*open)(struct inode *inode, struct file *filp);release方法(释放设备)-释放资源、关闭设备等-并不是在应用程序每次调用close时都会调用release17设备的方法(2)read方法(从设备读取数据)ssize_t read(struct file *filp, char _user *buff, size_t count, loff_t *offp);count:请求传输的数据大小buff:缓冲区offp:正在存取的文件位置返回值:等于count,完整读取大于0但小于count,部分传输等于0,已到达文件尾小于0,出错write方法(往设备写入数据)ssize_t write(struct file *filp, const char _user *buff, size_t count, loff_t *offp);返回值:等于count,完整写入大于0但小于count,部分写入等于0,没有写入小于0,出错18设备的方法(3)用户空间和内核空间的数据传送unsigned long copy_to_user(void _user *to,const void *from,unsigned long count); unsigned long copy_from_user(void *to,const void _user *from,unsigned long count); 注意:需要检查返回值19设备的方法(4)llseek方法(用于设备定位)-有些设备不能定位,如串口等字节流设备-调用read、write时会更新文件当前位置指针-如果设备不支持llseek,需要调用以下函数打开设备:int nonseekable_open(struct inode *inode; struct file *filp); 同时 结构中设置 llseek 方法为 no_llseek20设备的方法(5)ioctl方法(用于其他的设备控制操作,如获取参数、改变设置、硬件控制等)int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);-cmd:命令参数-arg:指针或者数值ioctl命令-系统范围内唯一-Documentation/ioctl-number.txt(内核使用的ioctl幻数)-由4个字段组成type-幻数number-序号direction-数据传送方向size-数据大小21设备的方法(6)定义ioctl命令的宏_IO(type,nr)(没有参数的命令)_IOR(type, nr, datatype)(读数据)_IOW(type,nr,datatype)(写数据) _IOWR(type,nr,datatype)(读写)返回值:对于无效的命令,通常返回-EINVAL命令参数传递:put_user(datum, ptr)-返回datum变量的值给应用程序get_user(local, ptr)-从应用程序获取参数值保存在local变量中22设备的方法(7)scull的ioctl命令 分析main.c应用程序的调用方法(数值或指针方式):int quantum;ioctl(fd,SCULL_IOCSQUANTUM, &quantum); ioctl(fd,SCULL_IOCTQUANTUM, quantum); ioctl(fd,SCULL_IOCGQUANTUM, &quantum); quantum = ioctl(fd,SCULL_IOCQQUANTUM); 23实例分析scull设备驱动代码分析main.c24课后练习输入scull驱动代码,编译并加载,使用cp、dd、cat等命令对scull设备进行读写操作25第四章内核时间和定时器26内核时间单位jiffies-时钟滴答计数器-两个jiffies的间隔一般为10ms(1/HZ,HZ=100,即1秒产生的时钟滴答数)-volatile类型,如unsigned long j, stamp_1, stamp_half, stamp_n;j = jiffies; (当前时间)stamp_1 = j + HZ;(1秒)stamp_half = j + HZ/2;(半秒)stamp_n = j + n * HZ / 1000; (n毫秒)27延时函数(1)忙等待while (time_before(jiffies, j1) cpu_relax();-浪费CPU资源,降低性能释放CPUwhile (time_before(jiffies, j1) schedule();-延时不准确,可能大于预期的值28延时函数(2)超时set_current_state(TASK_INTERRUPTIBLE);schedule_timeout (delay);-jiffies为单位短延时(忙等待)void ndelay(unsigned long nsecs);-纳秒void udelay(unsigned long usecs);-微秒void mdelay(unsigned long msecs);-毫秒jit模块代码分析29内核定时器(1)调度一个函数在将来一个特定的时间执行,如查询设备、关闭硬件等定时器函数是异步执行的,属于软中断类型定时器函数的一些限制-不允许存取用户空间-不能存取 current 指针-不能进行睡眠或者调度. 不能调用 schedule 或者某种 wait_event, 也不能调用任何其他可能睡眠的函数. 例如kmalloc定时器函数执行后可以再次注册30内核定时器(2)定时器API:#include struct timer_list /* . */ unsigned long expires; void (*function)(unsigned long); unsigned long data;void init_timer(struct timer_list *timer);struct timer_list TIMER_INITIALIZER(_function, _expires, _data);void add_timer(struct timer_list * timer);int del_timer(struct timer_list * timer);-expires:定时器将要运行的jiffies值-function:定时器到期时执行的函数-data:传递给function的参数,可以使指针jit模块代码分析31课后练习输入jit驱动代码,编译并加载,读取/proc目录下的相应文件,观察不同延时方法的表现32第五章并发和竞态33什么是并发和竞态CPU的多处理特性,导致多个线程同时执行,如内核抢占、中断、异步执行(定时器)、SMP(多处理器)等资源共享容易导致竞态scull的问题分析-内存泄露34如何避免竞态用内核提供的并发控制原语(信号量、锁定等)减少资源共享(如全局变量等)信号量和互斥-临界区(操作共享资源)-信号量是一个整数,一个进程只有在信号量大于0时才能进入临界区,同时信号量减1,小于0时需要等待(休眠)-信号量初始值为1时就成为互斥35并发控制(1)-linux信号量初始化void sema_init(struct semaphore *sem, int val);-val:信号量初始值互斥体初始化编译时:DECLARE_MUTEX(name); DECLARE_MUTEX_LOCKED(name);-初始处于锁定状态运行时:void init_MUTEX(struct semaphore *sem);void init_MUTEX_LOCKED(struct semaphore *sem);36并发控制(2)-自旋锁特性:-可以在不能休眠的代码中使用-提高性能-主要用于可抢占内核和多CPU系统使用规则:-拥有锁时不能休眠(不能调用任何可能导致休眠的函数,如kmalloc,copy_from_user等)-持有自旋锁的时间应尽可能短,否则内核延迟将增加,高优先级进程将被迫长时间等待,影响性能37自旋锁API初始化:spinlock_t my_lock = SPIN_LOCK_UNLOCKED; (编译时)void spin_lock_init(spinlock_t *lock); (运行时)获取自旋锁:void spin_lock(spinlock_t *lock);void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);-禁止中断,同时保存当前中断允许状态void spin_lock_irq(spinlock_t *lock);-禁止中断void spin_lock_bh(spinlock_t *lock)-禁止软中断释放自旋锁:void spin_unlock(spinlock_t *lock);void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);void spin_unlock_irq(spinlock_t *lock);void spin_unlock_bh(spinlock_t *lock);判断能否获得自旋锁,同时不会阻塞:int spin_trylock(spinlock_t *lock);int spin_trylock_bh(spinlock_t *lock);38第六章内存分配39kmalloc函数(1)和malloc相似有可能阻塞不对所分配的内存区段清零所分配的内存区段在物理上是连续的40kmalloc函数(2)#include void *kmalloc(size_t size, int flags); -s ize:需要分配的内存大小-flags:标志-GFP_KERNEL(代表进程分配,可能导致休眠,最常用)-GFP_ATOMIC(在中断服务程序、定时器函数环境中使用,不会导致休眠)41kmalloc函数(3)只能分配预定义的,固定大小的字节数实际分配的大小可能大于请求的字节数kmalloc能分配的最小字节数是32或64不要分配太大的内存(大于128K)42kfree函数释放由kmalloc分配的内存void kfree(void *obj); obj:kmalloc返回的指针43IO内存(1)通常指外设的寄存器或设备内存,如显存或网卡缓冲区等映射到内存地址空间。也是通过CPU地址总线和数据总线读写需要将IO内存的物理地址映射到内核的虚拟地址(ioremap)不要直接使用指针访问IO内存,应使用内核提供的读写函数(可读性、可移植性好,经过优化)44IO内存(2)IO内存的分配和映射-请求分配IO内存区域#include struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);start:起始地址len:长度成功返回非NULL指针-释放IO内存区域void release_mem_region(unsigned long start, unsigned long len); 45IO内存(3)将设备的IO地址(如寄存器地址等)映射到内核的虚拟地址空间#include void * ioremap(unsigned long offset, unsigned long size); -offset:设备的IO地址(物理地址)-size:映射范围由于体系结构差异,不能直接操作返回的指针,应使用内核提供的IO操作函数取消ioremap所做的映射void iounmap(void *addr); -addr:ioremap返回的内核虚拟地址46IO内存(4)读IO内存unsigned int ioread8(void *addr);-8位unsigned int ioread16(void *addr);-16位unsigned int ioread32(void *addr);-32位addr:ioremap返回的指针写IO内存void iowrite8(u8 value, void *addr);void iowrite16(u16 value, void *addr);void iowrite32(u32 value, void *addr);连续读写void ioread8_rep(void *addr, void *buf, unsigned long count);void ioread16_rep(void *addr, void *buf, unsigned long count);void ioread32_rep(void *addr, void *buf, unsigned long count);void iowrite8_rep(void *addr, const void *buf, unsigned long count);void iowrite16_rep(void *addr, const void *buf, unsigned long count);void iowrite32_rep(void *addr, const void *buf, unsigned long count);buf:数据缓冲区count:数据大小读写整块IO内存void memset_io(void *addr, u8 value, unsigned int count);void memcpy_fromio(void *dest, void *source, unsigned int count);void memcpy_toio(void *dest, void *source, unsigned int count);47第七章中断处理48设备通讯的三种方式轮询中断DMA(直接内存存取)49什么是中断中断是外设给CPU的信号,可以临时打断CPU执行的代码,转而执行中断处理程序一个中断通常和一个处理程序关联中断具有优先级,高优先级的中断可以嵌套低优先级的中断在CPU相应中断时,同级的中断会被自动屏蔽中断处理程序时异步执行的,需要注意防止竞态中断处理程序必须是原子执行的,不能进入休眠状态50中断处理程序(1)注册和释放#include int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *), unsigned long flags, const char *dev_name, void *dev_id);irq:请求的中断号(可以参考CPU数据手册)handler:和irq相关联的中断处理程序flags:中断标志,通常取SA_INTERRUPTdev_name:中断的所有者dev_id:私有数据,如不使用,可以设为NULLvoid free_irq(unsigned int irq, void *dev_id);通常应该在打开设备的时候请求中断,而不是在模块初始化时,防止没有使用设备而占用中断资源。/proc/interrupts可以显示系统中断的状态51中断处理程序(2)运行限制:-不能和用户空间传递数据-不能等待任何事件-分配内存时应使用GFP_ATOMIC参数-不能给信号量加锁-不能执行调度程序中断处理程序的主要工作是响应中断(设置中断响应标志),将设备的数据读入驱动的缓冲区,同时唤醒等待数据的用户进程中断处理程序的返回值-IRQ_HANDLED:已处理-IRQ_NONE:未处理,或者不是本设备产生的中断short模块代码分析52前半部和后半部(1)当中断处理程序要做较长时间的处理时,应分为两部分前半部执行时关闭中断,因此执行过程要尽可能短(request_irq注册的处理程序)后半部由前半部调度,在推后的更安全的时间执行(此时可以允许中断)典型的处理过程如前半部只是获取设备数据到缓冲,然后直接退出,数据的处理、进程唤醒等耗时的操作由后半部执行53前半部和后半部(2)内核提供的后半部处理机制tasklet-较快,但必须是原子执行的tasklet的声明:DECLARE_TASKLET(name, function, data);name:tasklet名称function:tasklet被调度时执行的函数data:指针参数如:void short_do_tasklet(unsigned long);DECLARE_TASKLET(short_tasklet, short_do_tasklet, 0);tasklet的调度(一般有前半部调用)tasklet_schedule(&short_tasklet);54前半部和后半部(3)工作队列-运行周期较长,但允许休眠工作队列的声明:static struct work_struct short_wq;INIT_WORK(&short_wq, (void (*)(void *) short_do_tasklet, NULL);工作队列的调度:schedule_work(&short_wq);short模块代码分析55第八章块设备驱动程序56块设备的特性可以随机读写固定大小的数据块可以提高性能块大小通常是4096字节内核使用的扇区大小是512字节57块设备的注册和注册块设备的注册int register_blkdev(unsigned int major, const char *name); major:主设备号,如为0,由内核动态分配name:设备名块设备的注销int unregister_blkdev(unsigned int major, const char *name); 58块设备的操作#include struct block_device_operationsint (*open)(struct inode *inode, struct file *filp); int (*release)(struct inode *inode, struct file *filp); int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); 大部分的块设备ioctl命令都有内核处理,驱动实现的较少int (*media_changed) (struct gendisk *gd); int (*revalidate_disk) (struct gendisk *gd); 对包含可移动介质的块设备(如光驱等),用来判断设备介质是否改变并作出响应和字符驱动的主要区别是没有读写函数59gendisk结构内核用来显示磁盘驱动器或分区主要成员struct block_device_operations *fops; 设备操作集合.struct request_queue *queue; 设备I/O 请求队列结构分配和初始化struct gendisk *alloc_disk(int minors); void add_disk(struct gendisk *gd); 释放void del_gendisk(struct gendisk *gd);sbull驱动模块初始化函数分析60块设备操作函数open和close-设置驱动和硬件的状态. 包括起停磁盘, 加锁一个可移出设备的门, 分配 DMA 缓冲等等-不一定由应用程序调用,可能由内核直接调用,如mount等ioctl-大部分ioctl由内核处理,驱动处理的很少sbull模块设备操作代码分析61I/O请求I/O请求的处理-在内核认为需要启动对设备读写的时候调用-请求队列包含当前需要处理的请求,由内核对所有I/O请求进行调度(合并或重排等,性能考虑)后将请求加入队列请求队列的初始化和清除#include request_queue_t blk_init_queue(request_fn_proc *request, spinlock_t *lock); request:请求处理函数void blk_cleanup_queue(request_queue_t *); 从队列中获取请求struct request *elv_next_request(request_queue_t *queue); (不删除请求)从队列中删除请求void blkdev_dequeue_request(struct request *req); 通知内核请求已处理void end_request(struct request *req, int success); sucess:指示请求是否成功完成62请求的结构(1)一个request可能包含多个bio,一个bio可能包含多个bio_vec(段)bio结构图63请求队列图请求的结构(2)64请求的结构(3)遍历request中的biorq_for_each_bio(bio, request)遍历bio中的段bio_for_each_segment(bvec, bio, segno); 65请求完成函数int end_that_request_first(struct request *req, int success, int count); success:驱动是否完成请求的扇区的传送count:完成传送的扇区数-告知内核驱动已完成count个扇区传送void end_that_request_last(struct request *req); -通知等待请求完成的进程,同时释放request结构sbull模块代码分析66第九章网络设备驱动程序67网络设备的特点将接口注册到内核中,供内核在需要时调用没有设备文件(没有read,write等调用)异步接收数据,需要将数据推送给内核驱动和协议相互独立需要支持设置网络地址,修改发送参数,以及维护流量和错误统计等操作68net_device结构分配#include struct net_device *alloc_netdev(int sizeof_priv, const char *name, void (*setup)(struct net_device *);sizeof_priv :私有数据区的大小name:接口名(如eth0等)setup:初始化函数的指针, 用来设置 net_device 结构的剩余部分(snull的net_device初始化函数分析)如:snull_devs0 = alloc_netdev(sizeof(struct snull_priv), sn%d, snull_init);对于以太网,可以简化为:#include struct net_device *alloc_etherdev(int sizeof_priv); 默认使用eth%d 作为name参数. ether_setup()为初始化函数。释放void free_netdev(struct net_device *dev); 69网络设备的注册和注销int register_netdev(struct net_device *dev); void unregister_netdev(struct net_device *dev); 70网络接口的打开和关闭(1)open和close接口由ifconfig调用open时所做的处理:-申请必要的资源(中断、I/O地址空间等)-设置硬件地址(MAC地址)启动发送队列void netif_start_queue(struct net_device *dev); 71网络接口的打开和关闭(2)close所做的处理:-释放所申请到的资源-停止发送队列-void netif_stop_queue(struct net_device *dev); 72数据包的发送sk_buff结构#include 包含要发送的数据包sk_buff的分配和释放struct sk_buff *dev_alloc_skb(unsigned int len); (使用GFP_ATOMIC标志调用kmalloc,可以在中断处理函数中使用)void dev_kfree_skb(struct sk_buff *skb); 73发送队列的操作发送函数hard_start_stransmit()停止发送netif_stop_queue(struct net_device *dev); 重启发送netif_wake_queue(struct net_device *dev); 完全停止发送void netif_tx_disable(struct net_device *dev); (确保返回时没有数据包在发送 )74接收数据包流程中断方式分配一个缓存区来保存报文(dev_alloc_skb)调用memcpy将报文数据拷贝到缓存区更新数据包统计计数调用netif_rx将skb提交给上层75中断处理函数通过网卡中的中断状态寄存器判断是接收还是发送中断,或是错误指示、状态改变等snull中断处理函数分析76驱动实例练习-毫秒级精度秒表77功能描述液晶显示秒表计数(分:秒:毫秒),开发板#1键用来启动和停止秒表,#2键用于复位秒表。78实现要点秒表计数采用内核定时器函数按键检测采用中断方式应用程序读取/proc下的文件(如/proc/ms_clock),或/dev下的设备文件如(/dev/ms_clock),不断刷新当前秒表计数79程序大致流程模块初始化部分:-注册按键中断处理程序-初始化定时器-创建/proc/ms_clock文件,包含分钟、秒、毫秒计数值,初始化为00 00 00(/proc文件的创建可以参考linux设备驱动程序中的“内核时间”部分的例子)-如使用/dev/ms_clock设备,需按字符设备驱动要求注册设备第一次检测到按键时,在中断处理程序中启动定时器每毫秒更新/proc/clock_ms文件内容再次检测到按键时,在中断处理程序停止定时器应用程序循环读取/proc/clock_ms文件内容,刷新当前秒表显示80项目实战-嵌入式LinuxIP电话终端81项目需求硬件平台:-S3C2410开发板软件功能需求:-基于SIP协议(linphone开源协议栈)-设计一个基于Qt的GUI界面,包含呼出、接听、挂断,可以输入SIP地址,地址簿管理功能-板上的四个按键分别用于呼出(接听)、挂断、增大音量、减小音量-可扩展视频功能82总体要求以小组为单位,小组长为项目经理,项目完成时间两个月需提交的项目文档-项目计划书(包含进度计划、关键时间点、任务分工等)-系统概要设计-系统详细设计-系统测试报告-使用说明项目计划和设计阶段都需要进行评审83项目实现要点linphone开源SIP协议栈的移植linphoneAPI的调用Qt图形前端设计声卡驱动和应用开发按键驱动程序开发84项目组成员分工与职责项目经理-负责项目进度计划安排,任务分工,项目进度监控等(提交项目计划书)应用开发人员-Qt界面开发-linphone协议栈移植驱动开发人员-声卡驱动和应用开发-按键驱动开发(提交概要设计、详细设计)系统集成-功能模块整合-文件系统制作和发布系统测试-程序实现是否和需求吻合(提交测试报告)85参考资料86
网站客服QQ:2055934822
金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号