50,如何编写一个LINUX驱动?
答:一.在系统的资源文件代码中定义platform_device,里面填写对应设备的外设IO起始地址,地址长度,中断,DMA资源等信息资源信息,并把资源信息添加到系统启动初始化流程里面;
二. 通过module_init(xxx_init)和moule_exit(xxx_init)定义驱动入口和出口函数;
三.写出模块加载xxx_init()和退出的实际处理函数xxx_exit(),这里以xxx_init()为例:
在里面调用platform_driver_resigter()注册一个platform_driver结构体,实现其中的probe()和remove()函数以及driver成员结构体中name和owner成员。
51,简述LINUX驱动中字符设备和块设备的区别?
答:字符设备的特点是数据以字符流的方式进行访问,数据的顺序不能错序,乱序和随机读写,字符设备内核中不需要读写的缓冲,其驱动不支持lseek()函数
块设备的特点是数据是固定块大小(典型值有512字节,2KB,4KB)进行读写,块设备可以随机读写,读写的时候内核中需要缓冲,驱动支持lseek()函数,块设备中数据的访问需要先mount到LINUX的目录文件后才能访问里面的数据
LINUX中字符设备架构相对简单,应用编程的系统调用open,close,read,write和ioctl等函数驱动里面有相应的file_operations结构体里面的函数与之对应。
LINUX中块设备架构相对复杂,应用程序的读写会通过块设备里面的文件系统转化为读写的IO请求,块设备驱动里面通过gendisk结构体抽象块设备,并通过对请求队列的处理来实现对块设备的读写。
52,试总结单片机底层开发与LINUX驱动开发有哪些异同?
答:底层的程序包括,内核,bootloader和驱动。基本开发Android硬件产品公司主要需要这一类人。而不同产品中,内核和bootloader变化较小,主要的工作量是在驱动之上。驱动相当于 单片机程序+linux内核接口。
但是从单片机转型为Linux驱动开发的,几个主要问题的,是代码量急剧增加,在单片机中有一些习惯在驱动开发里变成致命的陷阱。比如不喜欢用宏,在驱动大量用到内核复杂结构而单片机往往自写,还有一个并发处理,也是一个难点。
相同点:
单片机开发和LINUX的驱动开发都有对硬件的操作,最底层对硬件的寄存器操作,对时序的理解是一致的。
不同点:
1.单片机是对外设的IO实地址进行直接操作,而LINUX里面,由于使能了MMU,所以对外设IO地址的操作必须先通过ioremap()或者通过静态映射,把外设IO地址映射到内核的虚拟地址空间后才能正确操作。
2.在单片机编写对应设备的驱动不用考虑系统太多的系统分层问题,重用其他的代码量比较小,而LINUX采用分层抽象的思想,在LINUX中编写设备驱动,要按照LINUX已经搭建好的层次结构进行驱动编写,经常调用LINUX提供的函数和机制,代码重用性大。
3.由于LINUX是一个多任务的系统,即使在单核CPU上也存在资源竞争的情况(思考一下,LINUX里面那些地方可能导致资源竞争),所以在对驱动的编写的时候,对竞争资源需要采用一定的资源保护机制,比如原子变量,自旋锁等
4.单片机中断处理时,一般直接在产生中断的进入到中断处理函数里面在关中断的情况下处理完中断就可以。而LINUX里面把中断分为2部分,上半部分和下班部分,在上半部分中,是在关中断情况下,只做最基本和最核心的部分,然后在下半部分在开中断情况下,通过LINUX提供的各种机制来处理(思考: LINUX中断的底半部分有哪些模式)。
53.请从网卡、USB HOST、LCD驱动器、NAND FLASH、WIFI 、音频芯片中选择一个或者2个(可以以具体的芯片为例),对下面的问题做答:
1)如果是外部扩展芯片,请说出你用的芯片的型号
2)画出上题中你选定相应硬件模块与CPU的主要引脚连线
3) 编写上题中你选定相应硬件模块相应LINUX驱动的流程?
54,linux驱动分类?
答:Linux设备驱动的分类
(1)字符设备。
(2) 块设备。
(3) 网络设备。
字符设备指那些必须以串行顺序依次进行访问的设备,如触摸屏、磁带驱动器、鼠标等。块设备可以用任意顺序进行访问,以块为单位进行操作,如硬盘、软驱等。字符设备不经过系统的快速缓冲,而块设备经过系统的快速缓冲。但是,字符设备和块设备并没有明显的界限,如对于Flash设备,符合块设备的特点,但是我们仍然可以把它作为一个字符设备来访问。网络设备在Linux里做专门的处理。Linux的网络系统主要是基于BSD unix的socket 机制。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系统里支持对发送数据和接收数据的缓存,提供流量控制机制,提供对多协议的支持。
55,信号量与自旋锁?
答:自旋锁是专为防止多处理器并发而引入的一种锁,它应用于中断处理等部分。对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,不需要自旋锁。自旋锁最多只能被一个内核任务持有,如果一个内核任务试图请求一个已被争用(已经被持有)的自旋锁,那么这个任务就会一直进行忙循环——旋转——等待锁重新可用。要是锁未被争用,请求它的内核任务便能立刻得到它并且继续进行。自旋锁可以在任何时刻防止多于一个的内核任务同时进入临界区,因此这种锁可有效地避免多处理器上并发运行的内核任务竞争共享资源。事实上,自旋锁的初衷就是:在短期间内进行轻量级的锁定。一个被争用的自旋锁使得请求它的线程在等待锁重新可用的期间进行自旋(特别浪费处理器时间),所以自旋锁不应该被持有时间过长。如果需要长时间锁定的话, 最好使用信号量。但是自旋锁节省了上下文切换的开销。
自旋锁的基本形式如下:
spin_lock(&mr_lock);
//临界区
spin_unlock(&mr_lock);
因为自旋锁在同一时刻只能被最多一个内核任务持有,所以一个时刻只有一个线程允许存在于临界区中。这点很好地满足了对称多处理机器需要的锁定服务。在单处理器上,自旋锁仅仅当作一个设置内核抢占的开关。如果内核抢占也不存在,那么自旋锁会在编译时被完全剔除出内核。简单的说,自旋锁在内核中主要用来防止多处理器中并发访问临界区,防止内核抢占造成的竞争。另外自旋锁不允许任务睡眠(持有自旋锁的任务睡眠会造成自死锁——因为睡眠有可能造成持有锁的内核任务被重新调度,而再次申请自己已持有的锁),它能够在中断上下文中使用。
死锁:假设有一个或多个内核任务和一个或多个资源,每个内核都在等待其中的一个资源,但所有的资源都已经被占用了。这便会发生所有内核任务都在相互等待,但它们永远不会释放已经占有的资源,于是任何内核任务都无法获得所需要的资源,无法继续运行,这便意味着死锁发生了。自死琐是说自己占有了某个资源,然后自己又申请自己已占有的资源,显然不可能再获得该资源,因此就自缚手脚了。递归使用一个自旋锁就会出现这种情况。
信号量是一种睡眠锁。如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。这时处理器获得自由去执行其它代码。当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量。
信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;只能在进程上下文中使用,因为中断上下文中是不能被调度的;另外当代码持有信号量时,不可以再持有自旋锁。
信号量基本使用形式为:
static DECLARE_MUTEX(mr_sem);//声明互斥信号量
if(down_interruptible(&mr_sem))
//可被中断的睡眠,当信号来到,睡眠的任务被唤醒
//临界区
up(&mr_sem);
信号量和自旋锁区别
从严格意义上讲,信号量和自旋锁属于不同层次的互斥手段,前者的实现有赖于后者。
注意以下原则:
如果代码需要睡眠——这往往是发生在和用户空间同步时——使用信号量是唯一的选择。由于不受睡眠的限制,使用信号量通常来说更加简单一些。如果需要在自旋锁和信号量中作选择,应该取决于锁被持有的时间长短。理想情况是所有的锁都应该尽可能短的被持有,但是如果锁的持有时间较长的话,使用信号量是更好的选择。另外,信号量不同于自旋锁,它不会关闭内核抢占,所以持有信号量的代码可以被抢占。这意味者信号量不会对影响调度反应时间带来负面影响。
自旋锁对信号量
需求 建议的加锁方法
低开销加锁 优先使用自旋锁
短期锁定 优先使用自旋锁
长期加锁 优先使用信号量
中断上下文中加锁 使用自旋锁
持有锁是需要睡眠、调度 使用信号量