第五章:Linux驱动介绍
5.1 驱动原理:
LINUX提供标准接口函数给底层,底层驱动按照LINUX编程规则进行驱动编写。操作系统是通过各种驱动程序来驾驭硬件设备的,它为用户屏蔽了各种各样的设备,驱动硬件是操作系统最基本的功能,并且提供统一的操作方式。设备驱动程序是内核的一部分,硬件驱动程序是操作系统最基本的组成部分,在Linux内核源程序中也占有60%以上。因此,熟悉驱动的编写是很重要的.
Linux内核中采用可加载的模块化设计(LKMs,Loadable Kernel Modules),一般情况下编译的Linux内核是支持可插入式模块的,也就是将最基本的核心代码编译在内核中,其他的代码可以编译到内核中,或者编译为内核的模块文件(在需要时动态加载)。
5.2 内核模块的主要相关命令
◆lsmod:列出当前系统中加载的模块,其中左边第一列是模块名,第二列是该模块大小,第三列
则是使用该模块的对象数目。
◆rmmod:是用于将当前模块卸载。
◆insmod和modprobe是用于加载当前模块,但insmod不会自动解决依存关系,即如果要加
载的模块引用了当前内核符号表中不存在的符号,则无法加载,也不会去查在其他尚未加载的模块中是否定义了该符号;modprobe可以根据模块间依存关系以及/f文件中的内容自动加载其他有依赖关系的模块。
5.3 设备分类
Linux系统的设备分为三类:字符设备--(包含一个混杂设备)、块设备和网络设备。
字符设备通常指像普通文件或字节流一样,以字节为单位顺序读写的设备,如并口设备、虚拟控制台等。字符设备可以通过设备文件节点访问,它与普通文件之间的区别在于普通文件可以被随机访问(可以前后移动访问指针),而大多数字符设备只能提供顺序访问,因为对它们的访问不会被系统所缓存。但也有例外,例如帧缓存(framebuffer)是一个可以被随机访问的字符设备。
块设备通常指一些需要以块为单位随机读写的设备,如IDE硬盘、SCSI硬盘、光驱等。块设备也是通过文件节点来访问,它不仅可以提供随机访问,而且可以容纳文件系统(例如硬盘、闪存等)。Linux可以使用户态程序像访问字符设备一样每次进行任意字节的操作,只是在内核态内部中的管理方式和内核提供的驱动接口上不同。
$ ls –l /dev
crw-rw----    1 root uucp 4, 64 08-30 22:58 ttyS0 /*串口设备, c表示字符设备*/ brw-r----- 1 root disk 8, 0 08-11 23:03 sda /*硬盘设备,b表示块设备*/ 5.4 设备驱动程序工作原理
模块在调用insmod命令时被加载,此时的入口点是init_module()函数,通常在该函数中完成设备的注册。同样,模块在调用rmmod命令时被卸载,此时的入口点是cleanup_module()函数,在该函数中完成设备的卸载。在设备完成注册加载之后,用户的应用程序就可以对该设备进行一定的操作,
如open()、read()、write()等,而驱动程序就是用于实现这些操作,在用户应用程序调用相应入口函数时执行相关的操作。
5.5 应用程序、库、内核、驱动程序的关系:
4层软件关系如下:
1.应用程序通过open函数打开设备文件;
2.库根据open函数执行swi中断,引起异常进入内核;
3.内核根据异常相关参数(应用程序传递的)到相应驱动程序,并返回一文件句柄给库;
4.库根据文件句柄,触发库提供的write或ioclt函数(函数相关参数由应用程序提供)执行swi触
发异常后进入内核;
5.内核根据传递的相关参数调用驱动程序相关函数进行相关操作,如点亮led等。
嵌入式linux培训视频5.6 LINUX驱动程序开发步骤:
1.查看原理图、数据手册,了解设备的操作方法;
2.在内核中到相近的驱动程序,以它为模块进行开发,有时候需要从零开始;
3.实现驱动程序的初始化:比如向内核注册这个驱动程序,这样应用程序传入文件名时,内核才能
到相应的驱动程序;
4.设计所要实现的操作,比如open、close、read、write等函数;
5.实现中断服务(中断并不是每个设备驱动所必须的)。
6.编译该驱动程序到内核中,或者用insmod命令加载;
7.测试驱动程序。
5.7 驱动程序的加载和卸载:
可以将驱动程序静态编译进内核中,也可以将它作为模块在使用时再加载。在配置内核时,如果某个配置项设为m,就表示它将会被编译成一个模块。在linux2.6内核中,模块的扩展名为.ko,可以使用insmod 命令加载,使用rmmod命令卸载,使用lsmod命令查看内核中已经加载了哪些模块。
当使用insmod加载模块时,模块的初始化函数被调用,它用来向内核注册驱动程序;当使用rmmod卸载模块时,模块的清除函数被调用。在驱动代码中,这两个函数要么取固定的名字:init_module和
5.8关键概念
5.8.1 不可剥夺型(non-preemptive kernel):(分时操作系统内核)
要求每个任务主动放弃CPU的使用权。
优点:响应中断快;几乎无须使用信号量保护共享数据,运行中的任务占有CPU,而不必担心被别的任务抢占。
缺点:响应时间,高优先级的任务已经进入就绪态,但还不能运行,要等,直到当前运行着的任务释放CPU。
5.8.2 可剥夺型内核(preemptive kernel):(实时操作系统内核)
最高优先级的任务一旦就绪,总能得到CPU的使用权。当一个运行着的任务使一个比它优先级高的任务进入就绪态时,当前任务的CPU使用权就被剥夺了,或者说被挂起了,更高优先级的任务立刻得到了CPU的使用权。如果是中断服务子程序使一个高优先级的任务进入就绪态,中断完成时,中断了的任务被挂起,优先级高的任务开始运行。可剥夺型内核使得任务级响应时间得以最优化。
5.8.3 可重入函数:
可以被一个以上的任务调用,而不必担心数据被破坏。可重入函数任何时候都可以被中断,一段时间以后又可以运行,而相应的数据不会丢失。可重入函数或者只使用局部变量,即变量保存在CPU寄存器中或堆栈中;或者使用全局变量,则要对全局变量予以保护。(一个函数被多个任务调用时,每个任务都有自己独立的栈函数存放该函数运行的中间变量)。
5.8.4 资源:
任何被任务所占用的实体都可称为资源。资源可以是输入/输出设备,也可以是一个变量,一个结构或一个数组。
5.8.5 共享资源:
可以被一个以上使用的资源叫做共享资源。为了防止数据被破坏,每个任务在与共享资源打交道时,必须独占该资源。这叫做互斥(mutual exclusion)。
5.8.6 代码的临界段
也称为临界区,指处理时不可分割的代码。一旦这部分代码开始执行,则不允许任何中断打入。为确
保临界段代码的执行不被中断,在进入临界段之前必须关中断,而临界段代码执行完后,要立即开中断。
5.8.7 实时系统的特点
如果逻辑和时序出现偏差,将会引起严重后果。
有两种类型的实时系统:
1.软实时系统:系统的宗旨是使各个任务尽快的运行,而不要求限定某一任务在多长时间内完成;如
uc/os操作系统。
2.硬实时系统:各个任务不仅须执行无误,而且要做到准时。像VxWorks通过硬件完成(例如通过
定时器等方式),优先级高任务先执行,且每个任务执行时间可以指定。
大多数实时系统是两者的结合。如uc/os中可以通过中断发生来改变任务运行状态(任务切换)就是一种硬件机制来完成。
5.8.8 死锁:
也称为抱死(deadlock或deadly embrace),指两个任务无限期的相互等待对方控制着的资源。最简单的防止死锁的方法,让每个任务都:
(1)先得到全部需要的资源,再做下一步的工作;
(2)用同样的顺序申请多个资源;
(3)释放资源时,使用相反的顺序(按啥顺序得到,就按啥顺序释放,和中断进栈、出栈类似)。
内核通过定义等待超时来化解死锁-当等待时间超过了某一确定值,而信号量还是无效状态时,就会返回某种形式的出现超时错误的代码。这个出错代码告知该任务,不是得到了资源使用权,而使系统错误。
5.8.9 Linux将进程状态描述为如下五种:
TASK_RUNNING:可运行状态。处于该状态的进程能被调度执行而成为当前进程。
TASK_INTERRUPTIBLE:可中断的睡眠状态。处于该状态的进程在所需资源有效时被唤醒,也能通过信号或定时中断唤醒。
TASK_UNINTERRUPTIBLE:不可中断的睡眠状态。处于该状态的进程仅当所需资源有效时被唤醒。
TASK_ZOMBIE:僵尸状态。表示进程结束且已释放资源,但其task_struct(任务结构体)仍未释放。相当于进程已经结束,但在内核的任务表里面还有相应的结构资源。
TASK_STOPPED:暂停状态。处于该状态的进程通过其他进程的信号才能被唤醒。
如果处于暂停状态的进程被其它进程唤醒,但资源没有到位,那么就进入睡眠状态。
5.8.10 linux内核的三种调度方法:
1.SCHED_OTHER 分时调度策略;(各任务时间片平均分配,linux
2.4以前的内核)
2.SCHED_FIFO实时调度策略,先到先服务;(各任务谁优先级高,先运行谁,linux改进版本,要付费的)
3.SCHED_RR实时调度策略,时间片轮转(同优先级就可以按时间分片,不是同优先级按实时调度)(如linux2.6版本,半实时操作系统)。
实时进程将得到优先调用,实时进程根据实时优先级决定调度权值,分时进程则通过nice(优先级)
和counter(个数,时间片)值决定权值,nice越小(优先级越高),counter越大,被调度的概率越大,也就是曾经使用了cpu最少的进程将会得到优先调度。
SHCED_RR和SCHED_FIFO的不同:
当采用SHCED_RR策略的进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平。
SCHED_FIFO一旦占用cpu则一直运行。一直运行直到有更高优先级任务到达或自己放弃。
如果有相同优先级的实时进程(根据优先级计算的调度权值是一样的)已经准备好,FIFO时必须等待该进程主动放弃后才可以运行这个优先级相同的任务。而RR可以让每个任务都执行一段时间。
相同点:
1)RR和FIFO都只用于实时任务。
2)创建时优先级大于0(1-99)。
3)按照可抢占优先级调度算法进行。
4)就绪态的实时任务立即抢占非实时任务。
(SCHED_FIFO是实时,一个任务在执行,相同优先级其它任务不能执行;SHCED_RR是实时,同优先级协商式,一个任务在执行,相同优先级可以同时以时间片方式执行)。
5.8.11 所有任务都采用linux分时调度策略时:
1.创建任务指定采用分时调度策略,并指定优先级nice值(-20~19)。
2.将根据每个任务的nice值确定在cpu上的执行时间(counter)。
3.如果没有等待资源,则将该任务加入到就绪队列中。
4.调度程序遍历就绪队列中的任务,通过对每个任务动态优先级的计算(counter+20-nice)结果,
选择计算结果最大的一个去运行,当这个时间片用完后(counter减至0)或者主动放弃cpu时,该任务将被放在就绪队列末尾(时间片用完)或等待队列(因等待资源而放弃cpu)中。
5.此时调度程序重复上面计算过程,转到第4步。
6.当调度程序发现所有就绪任务计算所得的权值都为不大于0时,重复第2步。
5.8.12 所有任务都采用FIFO时,
1.创建进程时指定采用FIFO,并设置实时优先级rt_priority(1-99)。
2.如果没有等待资源,则将该任务加入到就绪队列中。
3.调度程序遍历就绪队列,根据实时优先级计算调度权值(1000+rt_priority),选择权值最高的任务
使用cpu,该FIFO任务将一直占有cpu直到有优先级更高的任务就绪(即使优先级相同也不行)或者主动放弃(等待资源)。
4.调度程序发现有优先级更高的任务到达(高优先级任务可能被中断或定时器任务唤醒,再或被当前
运行的任务唤醒,等等),则调度程序立即在当前任务堆栈中保存当前cpu寄存器的所有数据,重新从高优先级任务的堆栈中加载寄存器数据到cpu,此时高优先级的任务开始运行。重复第3步。
5.如果当前任务因等待资源而主动放弃cpu使用权,则该任务将从就绪队列中删除,加入等待队列,
此时重复第3步。
5.8.13 所有任务都采用RR调度策略时
1.创建任务时指定调度参数为RR,并设置任务的实时优先级和nice值(nice值将会转换为该任务的
时间片的长度)。
2.如果没有等待资源,则将该任务加入到就绪队列中。
3.调度程序遍历就绪队列,根据实时优先级计算调度权值(1000+rt_priority),选择权值最高的任务
使用cpu。
4.如果就绪队列中的RR任务时间片为0,则会根据nice值设置该任务的时间片,同时将该任务放
入就绪队列的末尾。重复步骤3。
5.当前任务由于等待资源而主动退出cpu,则其加入等待队列中。重复步骤3。
系统中既有分时调度,又有时间片轮转调度和先进先出调度:
1)RR调度和FIFO调度的进程属于实时进程,以分时调度的进程是非实时进程。
2)当实时进程准备就绪后,如果当前cpu正在运行非实时进程,则实时进程立即抢占非实时进程。
3)RR进程和FIFO进程都采用实时优先级做为调度的权值标准,RR是FIFO的一个延伸。FIFO时,
如果两个进程的优先级一样,则这两个优先级一样的进程具体执行哪一个是由其在队列中的未知决定的,这样导致一些不公正性(优先级是一样的,为什么要让你一直运行?),如果将两个优先级一样的任务的调度策略都设为RR,则保证了这两个任务可以循环执行,保证了公平。
5.8.14 进程调度依据(系统进程调度原理)
Linux只有一个可运行队列,处于TASK_RUNNING状态的实时进程和普通进程都加入到这个可运行队列中。Linux的进程调度采用了动态优先级和权值调控的方法,既可实现上述三种调度策略,又能确保实时进程总是比普通进程优先使用CPU。举例说明:linux是半实时操作系统,进程之间运行占用时间如下分析:
1.范例1说明:
1)假设系统有1000个时钟滴答,也就是有1000个时间片;
时间片不是固定的,它是人为设定的,可以通过修改系统滴答时钟相关驱动参数来修改系统滴答时钟频率(是1秒为单位,1秒滴答时钟1000次,那么时间片就为1000,时钟节拍为1ms)。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。