第九章心得:
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);