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

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 驱动程序交互,敬请期待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值