Ubuntu 上为Android 系统编写Linux 内核驱动程序

这里,我们不会为真实的硬件设备编写内核驱动程序。为了方便描述为Android 系统编写内核驱动程序的过程,我们使用一

个虚拟的硬件设备,这个设备只有一个4 字节的寄存器,它可读可写。想起我们第一次学习程序语言时,都喜欢用“Hello, World”

作为例子,这里,我们就把这个虚拟的设备命名为“hello”,而这个内核驱动程序也命名为hello 驱动程序。其实,Android 内

核驱动程序和一般Linux 内核驱动程序的编写方法是一样的,都是以Linux 模块的形式实现的,具体可参考前面Android 学习

启动篇一文中提到的Linux Device Drivers 一书。不过,这里我们还是从Android 系统的角度来描述Android 内核驱动程序的编

写和编译过程。

一. 参照这两篇文章在Ubuntu 上下载、编译和安装Android 最新源代码和在Ubuntu 上下载、编译和安装Android 最新内核源

代码(Linux Kernel)准备好Android 内核驱动程序开发环境。

二. 进入到kernel/common/drivers 目录,新建hello 目录:

linuxidc@www.linuxidc.com:~/Android$cd kernel/common/drivers

linuxidc@www.linuxidc.com:~/Android/kernel/common/drivers$mkdir hello

三. 在hello 目录中增加hello.h 文件:

1.#ifndef_HELLO_Android_H_
2.#define_HELLO_ANDROID_H_
3.#include<linux/cdev.h>
4.#include<linux/semaphore.h>
5.#defineHELLO_DEVICE_NODE_NAME "hello"
8.#defineHELLO_DEVICE_FILE_NAME "hello"
9.#defineHELLO_DEVICE_PROC_NAME "hello"
10.#defineHELLO_DEVICE_CLASS_NAME "hello"
12.structhello_android_dev {
13. intval;
14.struct semaphore sem;
15.struct cdev dev;
16.};
17.#endif

这个头文件定义了一些字符串常量宏,在后面我们要用到。此外,还定义了一个字符设备结构体hello_Android_dev,这个就

是我们虚拟的硬件设备了,val 成员变量就代表设备里面的寄存器,它的类型为int,sem 成员变量是一个信号量,是用同步

访问寄存器val 的,dev 成员变量是一个内嵌的字符设备,这个Linux 驱动程序自定义字符设备结构体的标准方法。

四.在hello 目录中增加hello.c 文件,这是驱动程序的实现部分。驱动程序的功能主要是向上层提供访问设备的寄存器的值,

包括读和写。这里,提供了三种访问设备寄存器的方法,一是通过proc 文件系统来访问,二是通过传统的设备文件的方法来

访问,三是通过devfs 文件系统来访问。下面分段描述该驱动程序的实现。

首先是包含必要的头文件和定义三种访问设备的方法:

1.#include<linux/init.h>
2.#include<linux/module.h>
3.#include<linux/types.h>
4.#include<linux/fs.h>
5.#include<linux/proc_fs.h>
6.#include<linux/device.h>
7.#include<asm/uaccess.h>
8.
9.#include"hello.h"
10.
11./*主设备和从设备号变量*/
12.staticint hello_major = 0;
13.staticint hello_minor = 0;
14.
15./*设备类别和设备变量*/
16.staticstruct class* hello_class = NULL;
17.staticstruct hello_Android_dev* hello_dev = NULL;
18.
19./*传统的设备文件操作方法*/
20.staticint hello_open(struct inode* inode, struct file* filp);
21.staticint hello_release(struct inode* inode, struct file* filp);
22.staticssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t*f_pos);
23.staticssize_t hello_write(struct file* filp, const char __user *buf, size_t count,loff_t* f_pos);
24.
25./*设备文件操作方法表*/
26.staticstruct file_operations hello_fops = {
27..owner = THIS_MODULE,
28..open = hello_open,
29..release = hello_release,
30..read = hello_read,
31..write = hello_write,
32.};
33.
34./*定义设备属性*/
35.staticDEVICE_ATTR(val, S_IRUGO | S_IWUSR, hello_val_show, hello_val_store);
36.
37./*访问设置属性方法*/
38.staticssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char*buf);
39.staticssize_t hello_val_store(struct device* dev, struct device_attribute* attr,const char* buf, size_t count); 定义传统的设备文

件访问方法,主要是定义hello_open、hello_release、hello_read 和hello_write 这四个打开、释放、读和写设备文件的方法:

1./*打开设备方法*/
2.staticint hello_open(struct inode* inode, struct file* filp) {
3.struct hello_Android_dev* dev;
4.
5. /*将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用*/
6. dev =container_of(inode->i_cdev, struct hello_android_dev, dev);
7.filp->private_data = dev;
8.
9.return 0;
10.}
11.
12./*设备文件释放时调用,空实现*/
13.staticint hello_release(struct inode* inode, struct file* filp) {
14.return 0;
15.}
16.
17./*读取设备的寄存器val 的值*/
18.staticssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t*f_pos) {
19.ssize_t err = 0;
20.struct hello_android_dev* dev = filp->private_data;
21.
22. /*同步访问*/
23.if(down_interruptible(&(dev->sem))) {
24.return -ERESTARTSYS;
25. }
26.
27.if(count < sizeof(dev->val)) {
28. gotoout;
29. }
30.
31. /*将寄存器val 的值拷贝到用户提供的缓冲区*/
32.if(copy_to_user(buf, &(dev->val), sizeof(dev->val))) {
33. err= -EFAULT;
34. gotoout;
35. }
36.
37. err= sizeof(dev->val);
38.
39.out:
40.up(&(dev->sem));
41.return err;
42.}
43.
44./*写设备的寄存器值val*/
45.staticssize_t hello_write(struct file* filp, const char __user *buf, size_t count,loff_t* f_pos) {
46.struct hello_android_dev* dev = filp->private_data;
47.ssize_t err = 0;
48.
49. /*同步访问*/
50.if(down_interruptible(&(dev->sem))) {
51.return -ERESTARTSYS;
52. }
53.
54.if(count != sizeof(dev->val)) {
55. gotoout;
56. }
57.
58. /*将用户提供的缓冲区的值写到设备寄存器去*/
59.if(copy_from_user(&(dev->val), buf, count)) {
60. err= -EFAULT;
61. gotoout;
62. }
63.
64. err= sizeof(dev->val);
65.
66.out:
67.up(&(dev->sem));
68.return err;
69.}

定义通过devfs 文件系统访问方法,这里把设备的寄存器val 看成是设备的一个属性,通过读写这个属性来对设备进行访问,

主要是实现hello_val_show 和hello_val_store 两个方法,同时定义了两个内部使用的访问val值的方法__hello_get_val 和

__hello_set_val:

1. /*读取寄存器val 的值到缓冲区buf  中,内部使用*/
2. static ssize_t__hello_get_val(struct hello_Android_dev* dev, char* buf) {
3. int val= 0;
4.
5. /*同步访问*/
6. if(down_interruptible(&(dev->sem))){
7. return -ERESTARTSYS;
8. }
9.
10. val = dev->val;
11. up(&(dev->sem));
12.
13. return snprintf(buf,PAGE_SIZE, "%d/n",val);
14. }
15.
16. /*把缓冲区buf 的值写到设备寄存器val 中去,内部使用*/
17. static ssize_t__hello_set_val(struct hello_Android_dev* dev, const char* buf, size_t count){
18. int val= 0;
19.
20. /*将字符串转换成数字*/
21. val = simple_strtol(buf, NULL,10);
22.
23. /*同步访问*/
24. if(down_interruptible(&(dev->sem))){
25. return -ERESTARTSYS;
26. }
27.
28. dev->val = val;
29. up(&(dev->sem));
30.
31. return count;
32. }
33.
34. /*读取设备属性val*/
35. static ssize_thello_val_show(struct device* dev, struct device_attribute* attr, char* buf) {
36. struct hello_Android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);
37.
38. return __hello_get_val(hdev,buf);
39. }
40.
41. /*写设备属性val*/
42. static ssize_thello_val_store(struct device* dev, struct device_attribute* attr, const char*buf, size_t coun
t) {
43. struct hello_Android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);
44.
45. return __hello_set_val(hdev,buf, count);
46. }

定义通过proc 文件系统访问方法,主要实现了hello_proc_read 和hello_proc_write 两个方法,同时定义了在proc 文件系统

创建和删除文件的方法hello_create_proc 和hello_remove_proc:

1. /*读取设备寄存器val 的值,保存在page  缓冲区中*/
2. static ssize_thello_proc_read(char* page, char**start, off_t off, int count, int*eof, void* data) {
3. if(off> 0) {
4. *eof = 1;
5. return 0;
6. }
7.
8. return __hello_get_val(hello_dev,page);
9. }
10.
11. /*把缓冲区的值buff 保存到设备寄存器val 中去*/
12. static ssize_thello_proc_write(struct file* filp, const char __user *buff, unsigned long len, void* data) {
13. int err= 0;
14. char*page = NULL;
15.
16. if(len> PAGE_SIZE) {
17. printk(KERN_ALERT"The buff is too large: %lu./n",len);
18. return -EFAULT;
19. }
20.
21. page = (char*)__get_free_page(GFP_KERNEL);
22. if(!page){
23. printk(KERN_ALERT"Failed to alloc page./n");
24. return -ENOMEM;
25. }
26.
27. /*先把用户提供的缓冲区值拷贝到内核缓冲区中去*/
28. if(copy_from_user(page,buff, len)) {
29. printk(KERN_ALERT"Failed to copy buff from user./n");
30. err = -EFAULT;
31. goto out;
32. }
33.
34. err = __hello_set_val(hello_dev,page, len);
35.
36. out:
37. free_page((unsigned long)page);
38. return err;
39. }
40.
41. /*创建/proc/hello 文件*/
42. static void hello_create_proc(void) {
43. struct proc_dir_entry*entry;
44.
45. entry =create_proc_entry(HELLO_DEVICE_PROC_NAME, 0, NULL);
46. if(entry){
47. entry->owner = THIS_MODULE;
48. entry->read_proc =hello_proc_read;
49. entry->write_proc =hello_proc_write;
50. }
51. }
52.
53. /*删除/proc/hello 文件*/
54. static void hello_remove_proc(void) {
55.remove_proc_entry(HELLO_DEVICE_PROC_NAME, NULL);
56. }

最后,定义模块加载和卸载方法,这里只要是执行设备注册和初始化操作:

1./*初始化设备*/
2.staticint __hello_setup_dev(struct hello_Android_dev* dev) {
3. interr;
4. dev_tdevno = MKDEV(hello_major, hello_minor);
5.
6.memset(dev, 0, sizeof(struct hello_Android_dev));
7.
8.cdev_init(&(dev->dev), &hello_fops);
9.dev->dev.owner = THIS_MODULE;
10.dev->dev.ops = &hello_fops;
11.
12. /*注册字符设备*/
13. err= cdev_add(&(dev->dev),devno, 1);
14.if(err) {
15.return err;
16. }
17.
18. /*初始化信号量和寄存器val 的值*/
19.init_MUTEX(&(dev->sem));
20.dev->val = 0;
21.
22.return 0; 23.}
24.
25./*模块加载方法*/
26.staticint __init hello_init(void){
27. interr = -1;
28.dev_t dev = 0;
29.struct device* temp = NULL;
30.
31.printk(KERN_ALERT"Initializing hello device./n");
32.
33. /*动态分配主设备和从设备号*/
34. err= alloc_chrdev_region(&dev, 0, 1, HELLO_DEVICE_NODE_NAME);
35.if(err < 0) {
36.printk(KERN_ALERT"Failed to alloc char dev region./n");
37. gotofail;
38. }
39.
40.hello_major = MAJOR(dev);
41.hello_minor = MINOR(dev);
42.
43. /*分配helo 设备结构体变量*/
44.hello_dev = kmalloc(sizeof(struct hello_Android_dev), GFP_KERNEL);
45.if(!hello_dev) {
46. err= -ENOMEM;
47.printk(KERN_ALERT"Failed to alloc hello_dev./n");
48. gotounregister;
49. }
50.
51. /*初始化设备*/
52. err= __hello_setup_dev(hello_dev);
53.if(err) {
54.printk(KERN_ALERT"Failed to setup dev: %d./n", err);
55. gotocleanup;
56. }
57.
58. /*在/sys/class/目录下创建设备类别目录hello*/
59.hello_class = class_create(THIS_MODULE, HELLO_DEVICE_CLASS_NAME);
60.if(IS_ERR(hello_class)) {
61. err= PTR_ERR(hello_class);
62.printk(KERN_ALERT"Failed to create hello class./n");
63. gotodestroy_cdev;
64. }
65.
66. /*在/dev/目录和/sys/class/hello 目录下分别创建设备文件hello*/
67. temp= device_create(hello_class, NULL, dev, "%s",HELLO_DEVICE_FILE_NAME);
68.if(IS_ERR(temp)) {
69. err= PTR_ERR(temp);
70.printk(KERN_ALERT"Failed to create hello device.");
71. gotodestroy_class;
72. }
73.
74. /*在/sys/class/hello/hello 目录下创建属性文件val*/
75. err= device_create_file(temp, &dev_attr_val);
76.if(err < 0) {
77.printk(KERN_ALERT"Failed to create attribute val.");
78. gotodestroy_device;
79. }
80.
81.dev_set_drvdata(temp, hello_dev);
82.
83. /*创建/proc/hello 文件*/
84.hello_create_proc();
85.
86.printk(KERN_ALERT"Succedded to initialize hello device./n");
87.return 0;
88.
89.destroy_device:
90.device_destroy(hello_class, dev);
91.
92.destroy_class:
93.class_destroy(hello_class);
94.
95.destroy_cdev:
96.cdev_del(&(hello_dev->dev));
97.
98.cleanup:
99.kfree(hello_dev);
100.
101.unregister:
102.unregister_chrdev_region(MKDEV(hello_major, hello_minor), 1);
103.
104.fail:
105.return err;
106.}
107.
108./*模块卸载方法*/
109.staticvoid __exit hello_exit(void) {
110.dev_t devno = MKDEV(hello_major, hello_minor);
111.
112.printk(KERN_ALERT"Destroy hello device./n");
113.
114. /*删除/proc/hello 文件*/
115.hello_remove_proc();
116.
117. /*销毁设备类别和设备*/
118.if(hello_class) {
119.device_destroy(hello_class, MKDEV(hello_major, hello_minor));
120.class_destroy(hello_class);
121. }
122.
123. /*删除字符设备和释放设备内存*/
124.if(hello_dev) {
125.cdev_del(&(hello_dev->dev));
126.kfree(hello_dev);
127. }
128.
129. /*释放设备号*/
130.unregister_chrdev_region(devno, 1);
131.}
132.
133.MODULE_LICENSE("GPL");
134.MODULE_DESCRIPTION("FirstAndroid Driver");
135.
136.module_init(hello_init);
137.module_exit(hello_exit);

五.在hello 目录中新增Kconfig 和Makefile 两个文件,其中Kconfig 是在编译前执行配置命令make menuconfig 时用到的,而

Makefile是执行编译命令make 是用到的:

Kconfig 文件的内容

configHELLO

tristate"First Android Driver"

defaultn

help

This isthe first Android driver.

Makefile文件的内容

obj-$(CONFIG_HELLO)+= hello.o

在Kconfig 文件中,tristate 表示编译选项HELLO 支持在编译内核时,hello 模块支持以模块、内建和不编译三种编译方

法,默认是不编译,因此,在编译内核前,我们还需要执行make menuconfig 命令来配置编译选项,使得hello 可以以模块或

者内建的方法进行编译。

在Makefile 文件中,根据选项HELLO 的值,执行不同的编译方法。

六. 修改arch/arm/Kconfig 和drivers/kconfig 两个文件,在menu "Device Drivers"和endmenu 之间添加一行:

source"drivers/hello/Kconfig"

这样,执行make menuconfig 时,就可以配置hello 模块的编译选项了。.

七. 修改drivers/Makefile 文件,添加一行:

obj-$(CONFIG_HELLO)+= hello/

八. 配置编译选项:

linuxidc@www.linuxdc.com:~/Android/kernel/common$make menuconfig

找到"Device Drivers" => "First Android Drivers"选项,设置为y。

注意,如果内核不支持动态加载模块,这里不能选择m,虽然我们在Kconfig 文件中配置了HELLO 选项为tristate。

要支持动态加载模块选项,必须要在配置菜单中选择Enable loadable module support 选项;在支持动态卸载模块选项,必须

要在Enable loadable module support 菜单项中,选择Module unloading 选项。

九. 编译:

linuxidc@www.linuxdc.com:~/Android/kernel/common$make

编译成功后,就可以在hello 目录下看到hello.o 文件了,这时候编译出来的zImage 已经包含了hello 驱动。

十. 参照在Ubuntu 上下载、编译和安装Android 最新内核源代码(Linux Kernel)一文所示,运行新编译的内核文件,

验证hello 驱动程序是否已经正常安装:

linuxidc@www.linuxdc.com:~/Android$emulator -kernel ./kernel/common/arch/arm/boot/zImage &

linuxidc@www.linuxdc.com:~/Android$adb shell

进入到dev 目录,可以看到hello 设备文件:

root@Android:/# cd dev

root@Android:/dev# ls

进入到proc 目录,可以看到hello 文件:

root@Android:/# cd proc

root@Android:/proc# ls

访问hello 文件的值:

root@Android:/proc# cat hello

0

root@Android:/proc# echo '5' > hello

root@Android:/proc# cat hello

5

进入到sys/class 目录,可以看到hello 目录:

root@Android:/# cd sys/class

root@Android:/sys/class# ls

进入到hello 目录,可以看到hello 目录:

root@Android:/sys/class# cd hello

root@Android:/sys/class/hello# ls

进入到下一层hello 目录,可以看到val 文件:

root@Android:/sys/class/hello# cd hello

root@Android:/sys/class/hello/hello# ls

访问属性文件val 的值:

root@Android:/sys/class/hello/hello# cat val

5

root@Android:/sys/class/hello/hello# echo '0' > val

root@Android:/sys/class/hello/hello# cat val

0

至此,我们的hello 内核驱动程序就完成了,并且验证一切正常。这里我们采用的是系统提供的方法和驱动程序进行

交互,也就是通过proc 文件系统和devfs 文件系统的方法,下一篇文章中,我们将通过自己编译的C 语言程序来访问/dev/hello

文件来和hello 驱动程序交互,敬请期待。