学习目的
了解驱动开发和应用开发的过程,具有一定的基础就行
第一讲:linux驱动开发与裸机开发区别
刚开始听不懂很正常,等之后学了一点你就会知道它说啥了
第二讲:字符设备驱动开发基础
字符设备驱动是最简单的,块设备是最难的
驱动是离不开应用的,但是两个是不同的文件,而我们在裸机单片机开发中,驱动和应用是放在一起的,直接一起编译的,而linux驱动和应用是完全分开的模块
使用应用层的函数调用驱动对应的函数
重点:对于字符设备驱动开发,重点编写应用程序对应的open、close、read、write函数
第三讲:我的第一个Linux驱动
字符设备驱动程序的编写其实就是为了实现下面结构体中的函数(选择性实现)
驱动模块的加载与卸载
针对于驱动程序,我们可以把驱动程序烧进内核或者编译成模块,烧进内核的话会很麻烦因为你需要重新把内核烧录至开发板中,因此我们使用的方法是把驱动程序编译成模块,而把驱动程序编译成模块就需要在驱动程序中编写模块入口函数和出口函数,然后我们在外面执行命令就可以把驱动程序编译成.so模块了 ,然后把.so通过拷贝到nfs的文件夹中通过网络传输.so文件
模块入口和出口函数编写
当你执行注册模块命令的时候,注册模块内的函数就会被执行(chrdevbase_init),同理删除模块命令,执行出口函数
内核里的打印函数:printk函数--内核区
而printf是在用户区使用的函数
加载驱动有什么用呢?就是为了注册字符设备,只有注册到了一个字符设备,你才能开始编写open、write、read这些函数
注册与注销字符设备
register_chrdev的缺点是:它会把主设备号下面的次设备号全部使用,说明空调只能有一种,所以这种很浪费,所以这个函数在后面的话我们不会使用,之所以讲这个函数只是单纯的告诉我们字符设备开发流程而已。
主设备号的赋值需要使用没有使用的主设备号,后面会有一个函数,直接随机分配一个没有用的主设备号
static struct file_operations test_fops;
/* 驱动入口函数 */
static int __init xxx_init(void)
{
/* 入口函数具体内容 *
int retvalue = 0;
/* 注册字符设备驱动 */
retvalue = register_chrdev(200, "chrtest", &test_fops);
if(retvalue < 0){
/* 字符设备注册失败,自行处理 */
}
return 0;
}
16
17 /* 驱动出口函数 */
18 static void __exit xxx_exit(void)
19 {
20 /* 注销字符设备驱动 */
21 unregister_chrdev(200, "chrtest");
22 }
23
24 /* 将上面两个函数指定为驱动的入口和出口函数 */
25 module_init(xxx_init);
26 module_exit(xxx_exit);
主设备号表示一类设备,而次设备就是主设备中的一个设备,比如主设备是空调,那么次设备就是格力空调、美的空调等
完整的驱动代码
驱动加载、注册设备、完成open等函数
#define CHRDEVBASE_MAJOR 200 /* 主设备号 */
19 #define CHRDEVBASE_NAME "chrdevbase" /* 设备名 */
20
21 static char readbuf[100]; /* 读缓冲区 */
22 static char writebuf[100]; /* 写缓冲区 */
23 static char kerneldata[] = {"kernel data!"};
24
25
32 static int chrdevbase_open(struct inode *inode, struct file *filp)
33 {
34 //printk("chrdevbase open!\r\n");
35 return 0;
36 }
37
38
46 static ssize_t chrdevbase_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
47 {
48 int retvalue = 0;
49
50 /* 向用户空间发送数据 */
51 memcpy(readbuf, kerneldata, sizeof(kerneldata));
52 retvalue = copy_to_user(buf, readbuf, cnt);
53 if(retvalue == 0){
54 printk("kernel senddata ok!\r\n");
55 }else{
56 printk("kernel senddata failed!\r\n");
57 }
58
59 //printk("chrdevbase read!\r\n");
60 return 0;
61 }
62
63
71 static ssize_t chrdevbase_write(struct file *filp,
const char __user *buf,
size_t cnt, loff_t *offt)
72 {
73 int retvalue = 0;
74 /* 接收用户空间传递给内核的数据并且打印出来 */
75 retvalue = copy_from_user(writebuf, buf, cnt);
76 if(retvalue == 0){
77 printk("kernel recevdata:%s\r\n", writebuf);
78 }else{
79 printk("kernel recevdata failed!\r\n");
80 }
81
82 //printk("chrdevbase write!\r\n");
83 return 0;
84 }
8
91 static int chrdevbase_release(struct inode *inode,
struct file *filp)
92 {
93 //printk("chrdevbase release! \r\n");
94 return 0;
95 }
96
97 /*
98 * 设备操作函数结构体
99 */
100 static struct file_operations chrdevbase_fops = {
101 .owner = THIS_MODULE,
102 .open = chrdevbase_open,
103 .read = chrdevbase_read,
104 .write = chrdevbase_write,
105 .release = chrdevbase_release,
106 };
107
108 /*
109 * @description : 驱动入口函数
110 * @param : 无
111 * @return : 0 成功;其他 失败
112 */
113 static int __init chrdevbase_init(void)
114 {
115 int retvalue = 0;
116
117 /* 注册字符设备驱动 */
118 retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME,
&chrdevbase_fops);
119 if(retvalue < 0){
120 printk("chrdevbase driver register failed\r\n");
121 }
122 printk("chrdevbase_init()\r\n");
123 return 0;
124 }
125
126 /*
* @description : 驱动出口函数
128 * @param : 无
129 * @return : 无
130 */
131 static void __exit chrdevbase_exit(void)
132 {
133 /* 注销字符设备驱动 */
134 unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
135 printk("chrdevbase_exit()\r\n");
136 }
137
138 /*
139 * 将上面两个函数指定为驱动的入口和出口函数
140 */
141 module_init(chrdevbase_init);
142 module_exit(chrdevbase_exit);
143
144 /*
145 * LICENSE 和作者信息
146 */
147 MODULE_LICENSE("GPL");
148 MODULE_AUTHOR("zuozhongkai");
编写测试 APP--应用开发
static char usrdata[] = {"usr data!"};
22
23 /*
24 * @description : main 主程序
25 * @param - argc : argv 数组元素个数
26 * @param - argv : 具体参数
27 * @return : 0 成功;其他 失败
28 */
29 int main(int argc, char *argv[])
30 {
31 int fd, retvalue;
32 char *filename;
33 char readbuf[100], writebuf[100];
34
35 if(argc != 3){
36 printf("Error Usage!\r\n");
37 return -1;
38 }
39
40 filename = argv[1];
41
42 /* 打开驱动文件 */
43 fd = open(filename, O_RDWR);
44 if(fd < 0){
45 printf("Can't open file %s\r\n", filename);
46 return -1;
47 }
48
49 if(atoi(argv[2]) == 1){ /* 从驱动文件读取数据 */
50 retvalue = read(fd, readbuf, 50);
51 if(retvalue < 0){
52 printf("read file %s failed!\r\n", filename);
53 }else{
54 /* 读取成功,打印出读取成功的数据 */
55 printf("read data:%s\r\n",readbuf);
56 }
57 }
if(atoi(argv[2]) == 2){
60 /* 向设备驱动写数据 */
61 memcpy(writebuf, usrdata, sizeof(usrdata));
62 retvalue = write(fd, writebuf, 50);
63 if(retvalue < 0){
64 printf("write file %s failed!\r\n", filename);
65 }
66 }
67
68 /* 关闭设备 */
69 retvalue = close(fd);
70 if(retvalue < 0){
71 printf("Can't close file %s\r\n", filename);
72 return -1;
73 }
74
75 return 0;
76 }
创建设备节点文件 |
驱动加载成功需要在/dev 目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作
然后使用上面的app对chrdevbase 设备操作测试处理
开发流程
驱动模块加载、设备注册、函数内部编写、应用程序编写测试