第九章心得:
HAL ( Hardware Abstraction Layer,硬件抽象腔,〉是建立在Linux驱动之上的一套翻字库。这套程序 j率并不属于 Linux 内核, 而是属于 Linux 内核层之上的应用层。
加入hal的目的:
(1)统一硬件的调用接口。由于HAL有标准的调用接卧,所以可以利用 HAL屏蔽Linux 驱动复杂,不统一的借口
(2)解决了GPl版权问题。由于 Linux 内核基于GPL协议,而Android基于Apache Licence 2 .0 ,协议.因此Google玩了个“穿越飞将原本位于 Linux驱动中的敏感代码向上移了一个层次二 这样 这些敏感代码就摆脱了 GPL协议的束缚。那些不想开源的 Linux驱动作者也就没必要开源了。
(3)针对一些特殊的要求。 对于有些硬件,可能需要访问→些用户空间的资源,或在内核空间 不方便完成的工作以及特殊需求。在这种情况下,可以利用位于用户空间的HAL代码来辅助Linux 驱动完成一些工作。
为led驱动添加HAL步骤:
第 1 步,编写 Linux 驱动
“编写 Linux 驱动”,从表明上看是废话,但如果要为 Linux 驱动添加 HAL,而且想尽量保护敏 感数据。 Linux 驱动的代码就要尽量简洁,尽可能将业务逻辑放到 HALLibrary 中。
第 2 步:编写 HAL Library HAL Library
HAL Library HAL Library 就是普通的 Linux Library *.so )文件。但这类库文件有一个接口。通过 HAL _MODULE_INFO _ SYM 变量实现。 Service Library 就是通过在这个接口中定义的 ID 定位 HAL Library 的。
第 3 步:编写 Service Library
尽管这步并不是必需的,但新的 HAL 架构要求我们这样做。 Service Library 也是 Linux Library。 这一步比较灵活。 Service Library 可以是一般的 Linux Library,也可以识别Library。在本章的 LED 驱动例子中将 Service Libraty和 Library 合到了一起。也就是说, Service Library就是JNI Library. 实际上这一步除了用 CIC件实现的*.so 库文件外,还应该包含一个用 Java 编写的服务管理类 ( ServiceManager)。 ServiceManager会调用 Service Library。而 APK 程序会调用 ServiceManager类米 访问 Service Library。
第十章心得
打印调试信息printk。printk 函数在前面的章节己多数使用过。该函数的用法与printf 函数类似,具不过printk 函数 运行在内核空间。
printk是在内核中运行的向控制台输出显示的函数 ,linux内核首先在内核空间分配一个静态缓存区,作为显示用的空间,然后调用sprintf,格式化显示字符串,最后调用tty_write向终端进行信息的显示。
printk与printf的差异,是什么导致一个运行在内核态而另一个运行用户态?其实这两个函数的几乎是相同的,出现这种差异是因为tty_write函数需要使用fs指向的被显示的字符串,而fs是专门用于存放用户态段选择符的,因此,在内核态时,为了配合tty_write函数,printk会把fs修改为内核态数据段选择符ds中的值,这样才能正确指向内核的数据缓冲区,当然这个操作会先对fs进行压栈保存,调用tty_write完毕后再出栈恢复。总结说来,printk与printf的差异是由fs造成的,所以差异也是围绕对fs的处理。
proc_mkdir
name: 虚拟目录名称。
parent: 虚拟目录父目录的 proc_dir_entry结构体指针。如果直接在/proc 目录下建立虚拟目录,该参数的值为 NULL。
create_proc_entry
name: 虚拟文件名称。 mode: 虚拟文件的访问权限, 等同于 Linux 文件的访问权限。 parent: 虚拟文件父目录的 proc_ dir _ entry 结构体指针。如果直接在/proc 剖录下建立虚拟 文件,该参数的值为 NULL。
create_proc_read_entry
name: 虚拟文件名称。mode:虚拟文件的访问权限,等同于 Linux 文件的访问权限。Base:虚拟文件父目录的 proc_ dir _ entry 结构体指针。如果直接在/proc 下建立虚拟文件, 该参数的值为 NULL。
read_proc:处理读动作的函数指针。 data: 用于虚拟文件系统的数据(任意类型的指针〉。该值就是 proc_ dir_entry.read _proc 函数 的最后一个参数值。相当于与某个虚拟文件永久绑定的数据。如果不市要设置该数据,可以为 NULL. remove _proc _ entry 。name: 要删除的虚拟文件的名称。 parent: 虚拟文件父目录的 proc_ dir _ entry结构体指钊。如果直接在/proc 目录下建立虚拟 文件, 该参数的值为 NULL.
第十一章心得:
中断屏蔽、原子操作、自旋锁、 信号量、 互斥体都 是解决并发问题的机制。中断屏蔽很少单独使用,原子操作只能对整数和位进行操作,而自旋锁、 信号量、 互斥体的应有比较广泛。当然,如果强调代码片段的执行顺序, 可以使用完成量。 自旋锁会由于不断自旋而导致死循环(也就是死锁〉,而且锁定状态会造成 臼U 闲置,因此用 自旋锁保护的临界区的代码不能执行时间过长,当然,更不允许临界区出现阻塞情况。信号量允许 临界区阻惑,因此适用于大临界区的情况. 读写↓自旋锁和读写信号量分别是放宽了条件的自旋锁和信号量,它们允许多个执行单元同时对 .共享资源执行读操作。
Linux中与完成量相关的操作:
(1)定义完成量struct completion my_completion;
(2)初始完成量init_completion(&my_completion);
(3)等待完成量wait_for_completion;
(4)唤醒完成量complete();
Linux 系统中与互斥体相关的操作主要有如下 4 种。
- 定义互斥体
定义互斥体需要使用 mutex 结构体,代码如下: struc.t mutex my_mutex;
2. 初始化直斥体 如果定义了 mutex 纯构体变量F耳以使隔nutex_init函数初始化该变量,
3. 获取互斥体
4. 释放放互斥休
信号量的分类
在学习信号量之前,我们必须先知道——Linux提供两种信号量:(1) 内核信号量,由内核控制路径使用(2) 用户态进程使用的信号量,这种信号量又分为POSIX信号量和SYSTEMV信号量。POSIX信号量又分为有名信号量和无名信号量。有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。无名信号量,其值保存在内存中。倘若对信号量没有以上的全面认识的话,你就会很快发现自己在信号量的森林里迷失了方向。第十二章心得
阻塞操作是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。
因为阻塞的进程会进入休眠状态,因此,必须确保有一个地方能够唤醒休眠的进程。唤醒进程的地方最大可能发生在中断里面,因为硬件资源获得的同时往往伴随着一个中断。
注意:驱动程序需要提供阻塞(等待队列,中断)和非阻塞方式(轮询,异步通知)访问设备。
休眠(被阻塞)的进程处于一个特殊的不可执行状态。这点非常重要,否则,没有这种特殊状态的话,调度程序就可能选出一个本不愿意被执行的进程,更糟糕的是,休眠就必须以轮询的方式实现了。进程休眠有各种原因,但肯定都是为了等待一些事件。事件可能是一段时间、从文件I/O读更多数据,或者是某个硬件事件。一个进程还有可能在尝试获得一个已经占用的内核信号量时被迫进入休眠。休眠的一个常见原因就是文件I/O -- 如进程对一个文件执行了read()操作,而这需要从磁盘里读取。还有,进程在获取键盘输入的时候也需要等待。无论哪种情况,内核的操作都相同:进程把它自己标记成休眠状态,把自己从可执行队列移出,放入等待队列,然后调用schedule()选择和执行一个其他进程。唤醒的进程刚好相反:进程被设置为可执行状态,然后再从等待队列中移到可执行队列。
休眠有两种相关的进程状态:TASK_INTERRUPTIBLE and TASK_UNINTERRUPTIBLE。它们的惟一区别是处于TASK_UNINTERRUPTIBLE状态的进程会忽略信号,而处于TASK_INTERRUPTIBLE状态的进程如果收到信号会被唤醒并处理信号(然后再次进入等待睡眠状态)。两种状态的进程位于同一个等待队列上,等待某些事件,不能够运行。休眠通过等待队列进行处理。等待队列是由等待某些事件发生的进程组成的简单链表。内核用wake_queue_head_t来代表等待队列。
等待队列可以通过DECLARE_WAITQUEUE()静态创建。
也可用init_waitqueue_head()动态创建。进程把自己放入等待队列中并设置成不可执行状态。等与等待队列相关的事件发生的时候,队列上的进程会被唤醒。为了避免产生竞争条件,休眠和唤醒的实现不能有纰漏。
等待队列
在Linux驱动程序中,可以使用等待队列来实现阻塞进程的唤醒。
进程通过执行下面几步将自己加入到一个等待队列中:
当然,首先是定义等待队列头,并初始化:
(1)wait_queue_head_t wait;
(2)init_waitqueue_head(&wait);