在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的垃圾信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。
1 自动生成设备文件节点
当设备节点比较少时,我们可以手动生成设备节点,但随着设备数量的增加和主次设备号的自动分配,再使用手动创建就不太现实了,这时候就需要用到udev/mdev机制,完成设备节点的自动创建。
完成设备节点自动创建的代码如下。代码中最重要的两个函数是 class_create() 和 class_device_create() 。
module init 代码:
Tips:为了把代码写的简单一点,错误处理的并不严谨,如果拿到真实项目上使用请做严谨的错误判断和处理。
/* ko模块源码中的init函数 */
69 int __init cdriver_init(void)
70 {
71 int minor = 0;
72
/* 如果注册字符设备时第一个入参是0,系统就会自动分配主设备号并返回 */
73 major = register_chrdev(0, "cdriver", &cdriver_fops); /* 创建设备类,并在类中创建设备 */
75 led_class = class_create(THIS_MODULE, "leds"); /*创建leds设备类*/
76 if (NULL == led_class)
77 return -EINVAL;
78 for (minor = 0; minor < 3; minor++) { /*在类中创建多个子设备*/
79 led_class_dev[minor] = class_device_create(led_class, NULL, MKDEV(major, minor), NULL, "led%d", minor);
80 if (NULL == led_class_dev[minor])
81 return -EINVAL;
82 }
83 /* io物理地址映射从open函数中拿到了这里(因为多个子设备共享,防止每次打开子设备时都映射一次)*/
84 pgpfcon = ioremap(GPFCON_ADD_BASE, sizeof(int)*2);
85 if (pgpfcon == NULL)
86 return -ENOMEM;
87 pgpfdata = pgpfcon + 1;
88
89 return 0;
90 }
module exit 代码:
/* ko模块源码中的exit函数 */
92 void __exit cdriver_exit(void)
93 {
94 int minor = 0;
95
96 iounmap(pgpfcon);
97
98 unregister_chrdev(major, "cdriver"); /*注销掉设备*/
99 for (minor = 0; minor < 3; minor++)
100 class_device_unregister(led_class_dev[minor]); /*注销掉设备文件节点*/
101 class_destroy(led_class); /*销毁设备类,注意和注销设备文件节点的顺序不能颠倒*/
102
103 }
2 设备和驱动的关系
设备属于资源,驱动属于加工和使用资源的途径和手段。
很明显,使用 面向对象 的思想来管理设备和驱动再合适不过了。一个个设备就是一个个对象,具有相同或相似特性的设备就是一类设备,行为相似的一类设备就可以共享一个驱动程序,该驱动程序就是类内的方法。
在本篇中,LED就是一类设备,LED驱动程序就是类内的方法(open方法、write方法),LED0、LED1和LED2就是具体的对象。
那靠什么来区分不同的对象呢?——应用程序靠设备文件节点;内核驱动程序靠 次设备号 。
# 在嵌入式设备上生成的3个led设备文件节点
/work/001-led # ls /dev/led* -al
crw-rw---- 1 0 0 251, 0 Jan 1 00:42 /dev/led0 #次设备号为0
crw-rw---- 1 0 0 251, 1 Jan 1 00:42 /dev/led1 #次设备号为1
crw-rw---- 1 0 0 251, 2 Jan 1 00:42 /dev/led2 #次设备号为2
在应用程序中打开、读写和操作各个次设备,在内核驱动程序中都可以感知到当前操作的是哪个设备(次设备号会从应用层通过入参形式带下来)。
先来看一下对open函数的处理:
14 int cdriver_open(struct inode *inode, struct file *file)
15 {
/* 最关键的是这行,从inode指针中获取次设备号 */
16 int minor = MINOR(inode->i_rdev);
17
18 printk("cdriver open success!\n");
19
/* 下面是通过次设备号来区分并配置各个LED设备
具体怎么算的不用关心,和原理图以及手册相关 */
20 *pgpfcon &= ~(0x3 << ((minor+4)*2)); /*将GPF5设置为输出引脚*/
21 *pgpfcon |= 0x1 << ((minor+4)*2);
22
23 *pgpfdata |= 0x1 << (minor+4); /*将LED的初始状态设置为关闭*/
24
25 return 0;
26 }
再来看一下对write函数的处理:
35 ssize_t led_write(struct file *file, const char __user *user_buff, size_t len, loff_t * offset)
36 {
37 int cmd_code = 0xff;
/* 最关键的是这行代码,从file指针中获取inode,再获取次设备号 */
38 int minor = MINOR(file->f_dentry->d_inode->i_rdev);
39
40 //printk("write \n");
41 if (len != sizeof(int))
42 return -EINVAL;
43
44 //printk("usr space: buff is %#x \n", *(int *)user_buff);
45
46 if (copy_from_user(&cmd_code, user_buff, len))
47 return -EFAULT;
48 //printk("cmd_code is %#x \n", cmd_code);
49
/* 根据次设备号(minor)来识别和操作不同的led设备;
具体算法请忽略,和原理图以及数据手册相关(不是重点)*/
50 if (1 == cmd_code)
51 *pgpfdata &= ~(0x1 << (minor+4));
52 else if (0 == cmd_code)
53 *pgpfdata |= 0x1 << (minor+4);
54 else ;
55
56 return 0;
57 }
3 基于多设备实现流水灯
最后,在应用层我们通过操作三个LED设备来实现一个流水灯的效果。
到这里我们也能感觉出来了,在Linux系统下实现流水灯和单片机上的 逻辑/策略 并没有什么不同,不同的是 框架/机制 。
所以,内核驱动编程的核心和重点是掌握Linux驱动框架。
为了完整性,最后把应用层代码贴出来(代码比较冗余,有兴趣的自己可以优化一下)。
1 #include <sys/types.h>
2 #include <sys/stat.h>
3 #include <fcntl.h>
4 #include <stdio.h>
5
6 int main(int argc, char **argv)
7 {
8 int fd1, fd2, fd3;
9 int val = 0xff;
10
/* 这个延时是为了将insmod和该应用程序同时加到/etc/init.d/rcS中时,
保证执行顺序的*/
11 usleep(500*1000);
12
13 fd1 = open("/dev/led0", O_RDWR);
14 if (fd1 < 0)
15 {
16 printf("error, can't open %s\n", "/dev/led0");
17 return 0;
18 }
19 fd2 = open("/dev/led1", O_RDWR);
20 if (fd2 < 0)
21 {
22 printf("error, can't open %s\n", "/dev/led1");
23 return 0;
24 }
25 fd3 = open("/dev/led2", O_RDWR);
26 if (fd3 < 0)
27 {
28 printf("error, can't open %s\n", "/dev/led2");
29 return 0;
30 }
31
32 for(;;){
33 val = 1;
34 write(fd3, &val, sizeof(int));
35 usleep(200 * 1000);
36 val = 0;
37 write(fd3, &val, sizeof(int));
38 usleep(200 * 1000);
39
40
41 val = 1;
42 write(fd2, &val, sizeof(int));
43 usleep(200 * 1000);
44 val = 0;
45 write(fd2, &val, sizeof(int));
46 usleep(200 * 1000);
47
48
49 val = 1;
50 write(fd1, &val, sizeof(int));
51 usleep(200 * 1000);
52 val = 0;
53 write(fd1, &val, sizeof(int));
54 usleep(200 * 1000);
55 }
56
57 (void)close(fd1);
58 (void)close(fd2);
59 (void)close(fd3);
60
61 return 0;
62 }
63
恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~