1、Linux 驱动学习总结汇报驱动学习总结汇报 2019年年11月月12日日内核模块内核模块BootloderBootloder并发控制并发控制中断处理中断处理设备驱动的结构设备驱动的结构Linux内核重要子系统内核重要子系统1.1.系统调用接口系统调用接口2.2.进程管理进程管理3.3.内存管理内存管理4.4.虚拟文件系统虚拟文件系统5.5.网络堆栈网络堆栈6.6.设备设备驱动驱动最简单的嵌入式系统最简单的嵌入式系统MTK的的Bootloader在嵌入式操作系统中,在嵌入式操作系统中,BootLoader是在操作系统内是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间核运行之前运行
2、。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。以便为最终调用操作系统内核准备好正确的环境。MTK的的bootloader有两部分组成:有两部分组成:(1)第第1部分部分bootloader,也就是也就是MTK内部内部(in-house)的的pre-loader,这部分依赖平台。这部分依赖平台。(2)第第2部分部分bootloader,也就是也就是LittleKernel,这部分依赖这部分依赖操作系统,负责引导操作系统,负责引导linux操作系统和操作系统和Androi
3、d框架。框架。源码位置:源码位置:vendormediatekproprietarybootablebootloaderMTK的的Bootloader正常启动的主要工作如下:正常启动的主要工作如下:(1)设备上电后,设备上电后,BootROM开始运行。开始运行。(2)BootROM初始化软件堆栈初始化软件堆栈(softwarestack)、通信端口和可引导存储通信端口和可引导存储设备设备(比如比如NAND/EMMC)。(3)BootROM从存储器中加载从存储器中加载pre-loader到内部到内部SRAM(ISRAM)中,因中,因为这时候还没有初始化外部的为这时候还没有初始化外部的DRAM。(
4、4)BootROM跳转到跳转到pre-loader的入口处并执行。的入口处并执行。(5)Pre-loader初始化初始化DRAM和加载和加载LK到到RAM中。中。(6)Pre-loader跳转到跳转到LK中并执行,然后中并执行,然后LK做一些初始化,比如显示的做一些初始化,比如显示的初始化等。初始化等。(7)LK从存储器中加载引导镜像从存储器中加载引导镜像(bootimage),包括包括linux内核和内核和ramdisk(Android呢?呢?)(8)LK跳转到跳转到linux内核并执行。内核并执行。MTK的的Bootloaderpre-loaders中涉及的硬件部分中涉及的硬件部分(1)P
5、LL模块模块1)PLL模块用于调整处理器和外部内存的频率。模块用于调整处理器和外部内存的频率。2)在在PLL模块初始化后,处理器和外部内存的频率可由模块初始化后,处理器和外部内存的频率可由26MHZ/26MHZ增加到增加到1GHZ/192MHZ。(2)UART模块模块1)UART模块用于调试或是模块用于调试或是META(MobileEngineeringTestingArchitecture)模式下的握手。模式下的握手。2)默认情况下,默认情况下,UART4初始化波特率为初始化波特率为9216000bps和用于调试和用于调试信息的输出,信息的输出,UART1初始化为初始化为115200bps和
6、作为和作为UARTMETA端口。端口。但也可以使用但也可以使用UART1作为调试或是作为调试或是UARTMETA端口。端口。(3)计时器计时器(timer)模块模块这是个基本的模块,用来计算硬件模块所需要的延时或是超时时间。这是个基本的模块,用来计算硬件模块所需要的延时或是超时时间。(4)内存模块内存模块1)Pre-loader由由bootROM加载和在芯片组内部的加载和在芯片组内部的SRAM中执行,因中执行,因为外部的为外部的DRAM还没有初始化。还没有初始化。2)为了准备软件整个可执行环境,为了准备软件整个可执行环境,pre-loader采用内置的内存设置来采用内置的内存设置来初始化初始化
7、DRAM(DRAMisinitializeduponpre-loaderbuilt-inmemorysettigns)。这样,。这样,LK就能够被加载到就能够被加载到DRAM中并执行。中并执行。(5)GPIO模块模块(6)PMIC模块模块为了提供一些基本的硬件功能,比如控制外设电源,为了提供一些基本的硬件功能,比如控制外设电源,pre-loader初始化初始化上层模块上层模块(uppermodules)。(7)RTC模块模块1)当通过当通过power按键开机后,按键开机后,pre-loader拉高拉高RTC的的PWBB来保持设来保持设备一直有电备一直有电(keepthedevicealive)
8、和继续引导和继续引导LK。2)RTC闹钟闹钟(alarm)有可能是设备开机的启动源,对于这种情况,设有可能是设备开机的启动源,对于这种情况,设备部需要按备部需要按power按键就可自动启动。按键就可自动启动。(8)USB模块模块当当USB线插入时,它初始化来和外部工具通信,比如用于升级线插入时,它初始化来和外部工具通信,比如用于升级系统的下载工具或是系统的下载工具或是META模式触发器的模式触发器的META工具。工具。(9)NAND模块模块(10)MSDC模块模块Pre-loader可以从可以从NANDflash或是或是EMMC中加载中加载LK,这两者只,这两者只能选择其中一种来启动能选择其中
9、一种来启动。LK中涉及的硬件部分中涉及的硬件部分LK是第是第2个个loader,它由,它由pre-loader引导并执行。从根本上来说引导并执行。从根本上来说(basically),pre-loader已经初始化了相关的硬件模块,而不需要已经初始化了相关的硬件模块,而不需要在在LK中重新配置这些模块了。但一些模块在中重新配置这些模块了。但一些模块在LK中被重新复位来中被重新复位来配置硬件寄存器,这样可创造一个干净的环境。比如计时器模块,配置硬件寄存器,这样可创造一个干净的环境。比如计时器模块,在在LK中,计时器重新复位清零硬件计数来对计时进行复位。所中,计时器重新复位清零硬件计数来对计时进行复
10、位。所有在有在LK中需要初始化的列在下面:中需要初始化的列在下面:(1)计时器模块计时器模块通过复位硬件寄存器来复位计时。通过复位硬件寄存器来复位计时。(2)串口模块串口模块LK采用串口模块来配置它的输入采用串口模块来配置它的输入/输出系统,在这个模块初始化输出系统,在这个模块初始化后,我们可以使用后,我们可以使用LK提供的提供的“printf()”等函数来使用串口功能。等函数来使用串口功能。(3)I2C模块模块(4)PWM模块模块(5)PMIC模块模块(6)RTC模块模块和计时器模块一样,在和计时器模块一样,在U-Boot中,中,I2C/PMIC/RTC重新复位寄存器来重新复位寄存器来复位这
11、些模块。复位这些模块。(7)LED模块模块通过这通过这poweroffcharging个模块,设备能够通知用户当前的充电状态。个模块,设备能够通知用户当前的充电状态。(8)充电模块充电模块这个模块负责关机充电这个模块负责关机充电(poweroffcharging)、低电压充电低电压充电(lowercharginginthesystem)。(9)LCD模块模块使用这个模块,设备能够显示使用这个模块,设备能够显示logo或是任何通知的消息。或是任何通知的消息。(10)NAND模块模块因为因为U-Boot也需要从也需要从flash读取镜像读取镜像(比如内核或是比如内核或是ramdisk),所以有必要
12、在所以有必要在U-Boot中初始化中初始化NAND相关的功能。相关的功能。(11)MSDC模块模块支持支持MSDC启动启动一些重要的数据结构一些重要的数据结构1.1.大部分驱动程序涉及三个重要的内核数据结构:大部分驱动程序涉及三个重要的内核数据结构:文件操作文件操作file_operationsfile_operations结构体结构体 文件对象文件对象filefile结构体结构体 索引节点索引节点inodeinode结构体结构体Linux设备驱动设备驱动LinuxLinux下设备的属性下设备的属性设备的类型:字符设备、块设备、网络设备设备的类型:字符设备、块设备、网络设备主设备号:标识设备对
13、应的驱动程序。一般主设备号:标识设备对应的驱动程序。一般“一个主设一个主设备号对应一个驱动程序备号对应一个驱动程序”次设备号:每个驱动程序负责管理它所驱动的几个硬件次设备号:每个驱动程序负责管理它所驱动的几个硬件实例,这些硬件实例则由次设备号来表示。同一驱动下实例,这些硬件实例则由次设备号来表示。同一驱动下的实例编号,用于确定设备文件所指的设备。的实例编号,用于确定设备文件所指的设备。可通过可通过ls l“ls l“设备文件名设备文件名”命令查看设备的主次设备命令查看设备的主次设备号,以及设备的类型。号,以及设备的类型。18分配和释放字符设备号分配和释放字符设备号1.1.编写驱动程序要做的第一
14、件事,为字符设备获取一个设备编写驱动程序要做的第一件事,为字符设备获取一个设备号。号。2.2.事先知道所需要的设备编号(主设备号)的情况:事先知道所需要的设备编号(主设备号)的情况:int register_chrdev_region(dev_t first,unsigned int register_chrdev_region(dev_t first,unsigned count,const char count,const char*name)name)firstfirst是要分配的起始设备编号值。是要分配的起始设备编号值。firstfirst的次设备号的次设备号通常设置为通常设置为0 0
15、。Count Count 所请求的连续设备编号的个数。所请求的连续设备编号的个数。NameName设备名称,指和该编号范围建立关系的设备。设备名称,指和该编号范围建立关系的设备。分配成功返回分配成功返回0 0。19分配和释放字符设备号分配和释放字符设备号1.1.动态分配设备编号(主要是主设备号)动态分配设备编号(主要是主设备号)int alloc_chrdev_region(dev_t int alloc_chrdev_region(dev_t*dev,unsigned dev,unsigned baseminor,unsigned count,const char baseminor,uns
16、igned count,const char*name)name)dev dev 是一个仅用于输出的参数是一个仅用于输出的参数,它在函数成功完成时保它在函数成功完成时保存已分配范围的第一个编号。存已分配范围的第一个编号。baseminor baseminor 应当是请求的第一个要用的次设备号,它常应当是请求的第一个要用的次设备号,它常常是常是 0.0.count count 和和 name name 参数跟参数跟request_chrdev_region request_chrdev_region 的一样的一样.20分配和释放字符设备号分配和释放字符设备号1.1.不再使用时,释放这些设备编号。
17、使用以下函数:不再使用时,释放这些设备编号。使用以下函数:void unregister_chrdev_region(dev_t from,void unregister_chrdev_region(dev_t from,unsigned count)unsigned count)在模块的卸载函数中调用该函数。在模块的卸载函数中调用该函数。21字符设备的注册字符设备的注册1.1.内核内部使用内核内部使用struct cdevstruct cdev结构表示字符设备。编写设备结构表示字符设备。编写设备驱动的第二步就是注册该设备。驱动的第二步就是注册该设备。包含包含头文件。头文件。获取一个独立的获取
18、一个独立的cdevcdev结构:结构:struct cdev struct cdev*my_cdev=cdev_alloc();my_cdev=cdev_alloc();调用调用cdev_initcdev_init初始化初始化cdevcdev结构体结构体void cdev_init(struct cdev void cdev_init(struct cdev*cdev,struct cdev,struct file_operations file_operations*fops);fops);初始化该设备的所有者字段:初始化该设备的所有者字段:dev-cdev.owner=THIS_MODUL
19、E;dev-cdev.owner=THIS_MODULE;初始化该设备的可用操作集:初始化该设备的可用操作集:dev-cdev.ops=&device_fops;dev-cdev.ops=&device_fops;22字符设备的注册字符设备的注册1.1.编写设备驱动的第二步就是注册该设备。编写设备驱动的第二步就是注册该设备。cdev cdev 结构已建立和初始化结构已建立和初始化,最后通过最后通过cdev_addcdev_add函数把函数把它告诉内核:它告诉内核:int cdev_add(struct cdev int cdev_add(struct cdev*dev,dev_t num,de
20、v,dev_t num,unsigned int count);unsigned int count);dev dev 是要添加的设备的是要添加的设备的 cdev cdev 结构结构,num num 是这个设备对应的第一个设备编号是这个设备对应的第一个设备编号,count count 是应当关联到设备的设备号的数目是应当关联到设备的设备号的数目.卸载字符设备时,调用相反的动作函数:卸载字符设备时,调用相反的动作函数:void cdev_del(struct cdev void cdev_del(struct cdev*dev);dev);23LinuxLinux设备驱动的并发控制设备驱动的并发
21、控制24设备驱动的并发控制设备驱动的并发控制 1.1.在驱动程序中,当多个线程同时访问相同的资源时,可在驱动程序中,当多个线程同时访问相同的资源时,可能会引发能会引发“竞态竞态”,必须对共享资源进行并发控制。,必须对共享资源进行并发控制。2.2.并发和竞态广泛存在。并发和竞态广泛存在。3.3.并发控制的目的:并发控制的目的:使得线程访问共享资源的操作是原子操作。使得线程访问共享资源的操作是原子操作。1.1.原子操作:原子操作:在执行过程中不会被别的代码路径所中断的操作。在执行过程中不会被别的代码路径所中断的操作。1.1.驱动程序中的全局变量是一种典型的共享资源。驱动程序中的全局变量是一种典型的
22、共享资源。251.1.考虑一个非常简单的共享资源的例子:一个全局整型变考虑一个非常简单的共享资源的例子:一个全局整型变量和一个简单的临界区,其中的操作仅仅是将整型变量量和一个简单的临界区,其中的操作仅仅是将整型变量的值增加的值增加1 1:i+i+1.1.该操作可以转化成下面三条机器指令序列:该操作可以转化成下面三条机器指令序列:得到当前变量得到当前变量i i的值并拷贝到一个寄存器中的值并拷贝到一个寄存器中 将寄存器中的值加将寄存器中的值加1 1 把把i i的新值写回到内存中的新值写回到内存中 原子操作原子操作26Linux内核的并发控制内核的并发控制 1.1.在内核空间的内核任务需要考虑同步在
23、内核空间的内核任务需要考虑同步 内核空间中的共享数据对内核中的所有任务可见,所以内核空间中的共享数据对内核中的所有任务可见,所以当在内核中访问数据时,就必须考虑是否会有其他内核当在内核中访问数据时,就必须考虑是否会有其他内核任务并发访问的可能、是否会产生竞争条件、是否需要任务并发访问的可能、是否会产生竞争条件、是否需要对数据同步。对数据同步。27确定保护对象确定保护对象 找出哪些数据需要保护是关键所在找出哪些数据需要保护是关键所在 内核任务的局部数据仅仅被它本身访问,显然不需要内核任务的局部数据仅仅被它本身访问,显然不需要保护。保护。如果数据只会被特定的进程访问,也不需加锁如果数据只会被特定的
24、进程访问,也不需加锁 大多数内核数据结构都需要加锁:若有其它内核任务大多数内核数据结构都需要加锁:若有其它内核任务可以访问这些数据,那么就给这些数据加上某种形式可以访问这些数据,那么就给这些数据加上某种形式的锁;若任何其它东西能看到它,那么就要锁住它。的锁;若任何其它东西能看到它,那么就要锁住它。Linux内核的并发控制28Linux内核的并发控制内核的并发控制1.1.并发控制的机制并发控制的机制 中断屏蔽,原子数操作,自旋锁和信号量都是解决中断屏蔽,原子数操作,自旋锁和信号量都是解决并发问题的机制。并发问题的机制。中断屏蔽很少被单独使用,原子操作只能针对整数中断屏蔽很少被单独使用,原子操作只
25、能针对整数来进行。因此自旋锁和信号量应用最为广泛。来进行。因此自旋锁和信号量应用最为广泛。291.1.锁机制可以避免竞争状态正如门锁和门一样,门后的房间锁机制可以避免竞争状态正如门锁和门一样,门后的房间可想象成一个临界区。可想象成一个临界区。2.2.在一段时间内,房间里只能有一个内核任务存在,当一个在一段时间内,房间里只能有一个内核任务存在,当一个任务进入房间后,它会锁住身后的房门;当它结束对共享任务进入房间后,它会锁住身后的房门;当它结束对共享数据的操作后,就会走出房间,打开门锁。如果另一个任数据的操作后,就会走出房间,打开门锁。如果另一个任务在房门上锁时来了务在房门上锁时来了,那么它就必须
26、等待房间内的任务出那么它就必须等待房间内的任务出来并打开门锁后,才能进入房间。来并打开门锁后,才能进入房间。加锁机制 301.1.任何要访问临界资源的代码首先都需要占住相应的锁,这任何要访问临界资源的代码首先都需要占住相应的锁,这样该锁就能阻止来自其它内核任务的并发访问:样该锁就能阻止来自其它内核任务的并发访问:任务任务 1 1 试图锁定队列 成功:获得锁 访问队列 为队列解除锁 任务任务2 2 试图锁定队列失败:等待 等待 等待 成功:获得锁 访问队列 为队列解除锁加锁机制 31原子数操作原子数操作1.1.整型原子数操作整型原子数操作 原子变量初始化原子变量初始化atomic_t test=
27、ATOMIC_INIT(i);atomic_t test=ATOMIC_INIT(i);设置原子变量的值设置原子变量的值void atomic_set(atomic_t void atomic_set(atomic_t*v,int i)v,int i)获得原子变量的值获得原子变量的值atomic_read(v)atomic_read(v)原子变量加原子变量加void atomic_add(int i,atomic_t void atomic_add(int i,atomic_t*v)v)原子变量减原子变量减void atomic_sub(int i,atomic_t void atomic_s
28、ub(int i,atomic_t*v)v)32原子数操作原子数操作1.1.整型原子数操作整型原子数操作 原子变量的自增操作原子变量的自增操作void atomic_inc(atomic_t void atomic_inc(atomic_t*v)v)原子变量的自减操作原子变量的自减操作void atomic_dec(atomic_t void atomic_dec(atomic_t*v)v)操作并测试操作并测试 (测试其是否为(测试其是否为0 0,0 0为为truetrue,否为,否为falsefalse)atomic_inc_and_test(atomic_t atomic_inc_and_
29、test(atomic_t*v)v)atomic_dec_and_test(atomic_t atomic_dec_and_test(atomic_t*v)v)int atomic_sub_and_test(int i,atomic_t int atomic_sub_and_test(int i,atomic_t*v)v)操作并返回操作并返回 (返回新值)(返回新值)int atomic_add_return(int i,atomic_t int atomic_add_return(int i,atomic_t*v)v)int atomic_sub_return(int i,atomic_t
30、int atomic_sub_return(int i,atomic_t*v)v)33原子数操作原子数操作1.1.原子位操作原子位操作 设置位设置位void set_bit(int nr,volatile unsigned long void set_bit(int nr,volatile unsigned long*addr)addr)清除位清除位void clear_bit(int nr,volatile unsigned void clear_bit(int nr,volatile unsigned long long*addr)addr)改变位改变位change_bit(nr,p)ch
31、ange_bit(nr,p)测试位测试位test_bit(int nr,const volatile unsigned test_bit(int nr,const volatile unsigned long long*p)p)测试并操作位测试并操作位test_and_set_bit(nr,p)test_and_set_bit(nr,p)34自旋锁自旋锁1.1.自旋锁是专为防止多处理器并发而引入的一种锁,它自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分。而对于单处理在内核中大量应用于中断处理等部分。而对于单处理器来说,防止中断处理中的并发可简单采用关闭中断器来说
32、,防止中断处理中的并发可简单采用关闭中断的方式,不需要自旋锁。的方式,不需要自旋锁。2.2.自旋锁最多只能被一个内核任务持有,若一个内核任自旋锁最多只能被一个内核任务持有,若一个内核任务试图请求一个已被持有的自旋锁,那么这个任务就务试图请求一个已被持有的自旋锁,那么这个任务就会一直进行忙循环,也就是旋转,等待锁重新可用。会一直进行忙循环,也就是旋转,等待锁重新可用。3.3.自旋锁可以在任何时刻防止多于一个的内核任务同时自旋锁可以在任何时刻防止多于一个的内核任务同时进入临界区,因此这种锁可有效地避免多处理器上并进入临界区,因此这种锁可有效地避免多处理器上并发运行的内核任务竞争共享资源。发运行的内
33、核任务竞争共享资源。35自旋锁自旋锁1.1.自旋锁的初衷就是:自旋锁的初衷就是:在短期间内进行轻量级的锁定。一个被争用的自在短期间内进行轻量级的锁定。一个被争用的自旋锁使得请求它的线程在等待锁重新可用的期间进行旋锁使得请求它的线程在等待锁重新可用的期间进行自旋(特别浪费处理器时间),所以自旋锁不应该被自旋(特别浪费处理器时间),所以自旋锁不应该被持有时间过长。如果需要长时间锁定的话持有时间过长。如果需要长时间锁定的话,最好使用最好使用信号量。信号量。36自旋锁自旋锁1.1.自旋锁防止在不同自旋锁防止在不同CPUCPU上的执行单元对共享资源的同上的执行单元对共享资源的同时访问,以及不同进程上下文
34、互相抢占导致的对共享时访问,以及不同进程上下文互相抢占导致的对共享资源的非同步访问。资源的非同步访问。2.2.在单在单CPUCPU且不可抢占的内核下,自旋锁的所有操作都且不可抢占的内核下,自旋锁的所有操作都是空操作。是空操作。3.3.自旋锁不允许任务睡眠。自旋锁不允许任务睡眠。37自旋锁自旋锁1.1.自旋锁的基本形式如下:自旋锁的基本形式如下:spin_lock(&mr_lock);spin_lock(&mr_lock);/*临界区临界区*/spin_unlock(&mr_lock)spin_unlock(&mr_lock);38自旋锁自旋锁1.1.自旋锁原语要求包含文件是自旋锁原语要求包含文
35、件是 .锁的类型是锁的类型是 spinlock_t.spinlock_t.2.2.锁的两种初始化方法:锁的两种初始化方法:spinlock_t my_lock=SPIN_LOCK_UNLOCKED;spinlock_t my_lock=SPIN_LOCK_UNLOCKED;void spin_lock_init(spinlock_t void spin_lock_init(spinlock_t*lock);lock);3.3.进入一个临界区前进入一个临界区前,必须获得需要的必须获得需要的 locklock。void spin_lock(spinlock_t void spin_lock(spi
36、nlock_t*lock);lock);自旋锁等待是不可中断的。一旦你调用自旋锁等待是不可中断的。一旦你调用spin_lock,spin_lock,将自旋直到锁变为可用。将自旋直到锁变为可用。4.4.释放一个锁:释放一个锁:void spin_unlock(spinlock_t void spin_unlock(spinlock_t*lock);lock);39自旋锁自旋锁1.1.关中断的自旋锁关中断的自旋锁 Spin_lock_irq()Spin_lock_irq()Spin_unlock_irq()Spin_unlock_irq()Spin_lock_irqsave()Spin_lock_
37、irqsave()Spin_unlock_irqrestore Spin_unlock_irqrestore()40信号量信号量1.1.LinuxLinux中的信号量是一种睡眠锁。中的信号量是一种睡眠锁。如果有一个任务试图获得一个已被持有的信号量时如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。,信号量会将其推入等待队列,然后让其睡眠。当持有信号量的进程将信号量释放后,在等待队列当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号中的一个任务将被唤醒,从而便可以获得这个信号量。量。信号量的睡眠特性,使得信号量适用于锁会被
38、长时信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;间持有的情况;2.2.信号量的操作信号量的操作 信号量支持两个原子操作信号量支持两个原子操作P()P()和和V()V(),前者做测试操,前者做测试操作,后者叫做增加操作。作,后者叫做增加操作。LinuxLinux中分别叫做中分别叫做down()down()和和up()up()。41信号量信号量42信号量信号量43Linux信号量的实现信号量的实现1.1.内核代码必须包含内核代码必须包含 ,才能使用信,才能使用信号量。号量。2.2.相关的类型是相关的类型是 struct semaphorestruct semaphore信号量的定义s
39、tructsemaphoreatomic_tcount;intsleepers;wait_queue_head_twait;44Linux信号量的实现信号量的实现1.1.信号量的声明和初始化信号量的声明和初始化 直接创建一个信号量直接创建一个信号量 struct semaphore struct semaphore*sem;sem;接着使用接着使用 sema_init sema_init 来初始化这个信号量:来初始化这个信号量:void sema_init(struct semaphore void sema_init(struct semaphore*sem,int sem,int val)
40、val);1.1.互斥模式的信号量声明,内核提供宏定义互斥模式的信号量声明,内核提供宏定义.DECLARE_MUTEX(name);DECLARE_MUTEX(name);信号量初始化为信号量初始化为 1 1 DECLARE_MUTEX_LOCKED(name);DECLARE_MUTEX_LOCKED(name);信号量初始化为信号量初始化为0 045自旋锁自旋锁忙等待,无调度开销;忙等待,无调度开销;进程抢占被禁止;进程抢占被禁止;锁定期间不能休眠;锁定期间不能休眠;信号量信号量拿不到就切换进程,有调度开销;拿不到就切换进程,有调度开销;锁定期间可以休眠;锁定期间可以休眠;46Linux的
41、中断处理47为什么会有中断为什么会有中断1.1.中断最初是为克服对中断最初是为克服对I/OI/O接口控制采用程序查询所带来的接口控制采用程序查询所带来的处理器低效率而产生的。处理器低效率而产生的。处理器速度一般比外设快很多处理器速度一般比外设快很多 用轮询的方式来查询设备的状态,用轮询的方式来查询设备的状态,CPUCPU效率不高,效率不高,CPUCPU和和外设不能并行工作。外设不能并行工作。中断机制让中断机制让CPUCPU启动设备后,就去处理其他任务,只有启动设备后,就去处理其他任务,只有当外设真正完成数据传输的准备,请求当外设真正完成数据传输的准备,请求CPUCPU服务的时候服务的时候,CP
42、UCPU才转过来处理外设的请求。才转过来处理外设的请求。48中断和异常中断和异常1.1.外部中断:外部中断:外部设备所发出的外部设备所发出的I/OI/O请求。请求。1.1.随着计算机系统结构的不断改进以及应用技术的日益提高随着计算机系统结构的不断改进以及应用技术的日益提高,中断的适用范围也随之扩大,出现了所谓的内部中断(,中断的适用范围也随之扩大,出现了所谓的内部中断(或叫异常)。或叫异常)。2.2.异常:异常:为解决机器运行时所出现的某些随机事件及编程方便而出为解决机器运行时所出现的某些随机事件及编程方便而出现的。现的。49I/O中断处理中断处理1.1.为了保证系统对外部的响应,一个中断处理
43、程序必须被尽为了保证系统对外部的响应,一个中断处理程序必须被尽快的完成。因此,把所有的操作都放在中断处理程序中并快的完成。因此,把所有的操作都放在中断处理程序中并不合适不合适2.2.LinuxLinux中把紧随中断要执行的操作分为三类中把紧随中断要执行的操作分为三类 紧急的紧急的(critical)(critical)一般关中断运行。诸如对一般关中断运行。诸如对PICPIC应答中断,对应答中断,对PICPIC或是硬件或是硬件控制器重新编程,或者修改由设备和处理器同时访问的控制器重新编程,或者修改由设备和处理器同时访问的数据数据 非紧急的非紧急的(noncritical)(noncritical
44、)如修改那些只有处理器才会访问的数据结构如修改那些只有处理器才会访问的数据结构(例如按下例如按下一个键后读扫描码一个键后读扫描码),这些也要很快完成,因此由中断,这些也要很快完成,因此由中断处理程序立即执行,不过一般在开中断的情况下处理程序立即执行,不过一般在开中断的情况下50I/O中断处理中断处理1.1.LinuxLinux中把紧随中断要执行的操作分为三类中把紧随中断要执行的操作分为三类 非紧急可延迟的非紧急可延迟的(noncritical deferrable)(noncritical deferrable)这些操作可以被延迟较长的时间间隔而不影响内核操这些操作可以被延迟较长的时间间隔而不
45、影响内核操作,有兴趣的进程将会等待数据。内核用下半部分这作,有兴趣的进程将会等待数据。内核用下半部分这样一个机制来在一个更为合适的时机用独立的函数来样一个机制来在一个更为合适的时机用独立的函数来执行这些操作。执行这些操作。如把缓冲区内容拷贝到某个进程的地址空间如把缓冲区内容拷贝到某个进程的地址空间(例如把例如把键盘缓冲区内容发送到终端处理程序进程键盘缓冲区内容发送到终端处理程序进程)。51注册中断服务例程注册中断服务例程 1.1.中断号是一个宝贵且常常有限的资源。内核维护一个中断中断号是一个宝贵且常常有限的资源。内核维护一个中断号的注册表。号的注册表。2.2.要使用中断,就要进行中断号的申请,
46、也就是要使用中断,就要进行中断号的申请,也就是IRQ(Interrupt ReQuirement)IRQ(Interrupt ReQuirement)。3.3.只有当设备需要中断的时候才申请占用一个只有当设备需要中断的时候才申请占用一个IRQIRQ,或者是,或者是在申请在申请IRQIRQ时采用共享中断的方式,让更多的设备使用中时采用共享中断的方式,让更多的设备使用中断。断。52注册中断服务例程注册中断服务例程 1.1.在在 实现中断注册接口实现中断注册接口:int request_irq(unsigned int irq,int request_irq(unsigned int irq,irq
47、return_t(irqreturn_t(*handler)(int,void handler)(int,void*,struct,struct pt_regs pt_regs*),),unsigned long flags,unsigned long flags,const char const char*dev_name,dev_name,void void*dev_iddev_id););void free_irq(unsigned int irq,void void free_irq(unsigned int irq,void*dev_id);dev_id);1.1.request_i
48、rq request_irq 的返回值是的返回值是 0 0 指示申请成功,为负值时表指示申请成功,为负值时表示错误码。函数返回示错误码。函数返回 -EBUSY-EBUSY 表示已经有另一个驱动占用表示已经有另一个驱动占用了所要申请的中断线。了所要申请的中断线。53注册中断服务例程注册中断服务例程 1.1.request_irqrequest_irq的参数说明:的参数说明:unsigned int irq,unsigned int irq,要申请的中断号。要申请的中断号。irqreturn_t(irqreturn_t(*handler)(int,void handler)(int,void*,s
49、truct,struct pt_regs pt_regs*),),要安装的中断处理函数指针。要安装的中断处理函数指针。const char const char*dev_name,dev_name,用在用在 /proc/interrupts/proc/interrupts 中显示中断的拥有者。中显示中断的拥有者。54注册中断服务例程注册中断服务例程 1.1.request_irqrequest_irq的参数说明:的参数说明:unsigned long flags,unsigned long flags,与中断管理相关的位掩码选项。与中断管理相关的位掩码选项。FlagsFlags的每个位有不同含
50、义的每个位有不同含义 SA_INTERRUPT SA_INTERRUPT 当该位被设置时,当该位被设置时,表示这是一个表示这是一个“快快速速”中断。快速中断处理例程运行时,屏蔽中断。中断。快速中断处理例程运行时,屏蔽中断。SA_SHIRQ SA_SHIRQ 这个位表示中断可以在设备间共享。这个位表示中断可以在设备间共享。void void*dev_iddev_id这个指针用于共享的中断号。做为驱动程序的私有数据这个指针用于共享的中断号。做为驱动程序的私有数据区(可用来识别那个设备产生的中断)。不使用共享中区(可用来识别那个设备产生的中断)。不使用共享中断线方式时,可设置为断线方式时,可设置为N
侵权处理QQ:3464097650--上传资料QQ:3464097650
【声明】本站为“文档C2C交易模式”,即用户上传的文档直接卖给(下载)用户,本站只是网络空间服务平台,本站所有原创文档下载所得归上传人所有,如您发现上传作品侵犯了您的版权,请立刻联系我们并提供证据,我们将在3个工作日内予以改正。