Linux字符设备驱动实例
共 4470字,需浏览 9分钟
·
2020-12-27 10:21
globalmem
看 linux 设备驱动开发详解时,字符设备驱动一章,写的测试代码和应用程序,加上自己的操作,对初学者我觉得非常有帮助。
写这篇文章的原因是因为我看了我之前发表的文章,还没有写过字符设备相关的,至于里面提到的结构体的作用,有很多详细的文章说明,我就不做更深的叙述。
代码在github上,点击下面阅读原文可以直达
https://github.com/weiqifa0/globalmem/blob/main/README.md
把这部分放在github上也有好处,后续可以增加删除一些东西,以后自己需要使用的时候也方便许多。
我们讨论字符设备驱动,就有必要知道他的结构体和头文件,像一些后来的封装什么的,大部分还是脱离不了操作这个结构体里面的东西。
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_CDEV_H
#define _LINUX_CDEV_H
#include
#include
#include
#include
struct file_operations;
struct inode;
struct module;
struct cdev {
struct kobject kobj; /*内嵌kobject结构体,方便以后应用,也会在sys下生成相关设备文件*/
struct module *owner;/*所属于的模块,正常就是本模块THIS_MODULE*/
const struct file_operations *ops;/*文件的操作结构体,设备也是一个文件*/
struct list_head list;/*字符设备的链表头*/
dev_t dev;/*设备号*/
unsigned int count;
} __randomize_layout;/*初始化cdev,并建立和file_operation的联系*/
void cdev_init(struct cdev *, const struct file_operations *);
/*申请cdev内存*/
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_set_parent(struct cdev *p, struct kobject *kobj);
int cdev_device_add(struct cdev *cdev, struct device *dev);
void cdev_device_del(struct cdev *cdev, struct device *dev);
void cdev_del(struct cdev *);
void cd_forget(struct inode *);
#endif
加载内核模块insmod globalmem.ko 错误需要的修改。
insmod: can't insert 'globalmem.ko': Device or resource busy
出错的原因:
模块使用的是静态分配设备号的方式,而这个设备号已经被系统中的其他设备所占用。查看未被占用的设备号,需要到pro/devices下面去查看。
查看设备号的方法:
# cat /proc/devices
编译内核版本
#uname -a
Linux bsp-ubuntu1804 4.15.0-117-generic #118-Ubuntu SMP Fri Sep 4 20:02:41 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
加载模块之后使用lsmod查看模块
weiqifa@bsp-ubuntu1804:~/c/globalmem$ sudo insmod globalmem.ko
weiqifa@bsp-ubuntu1804:~/c/globalmem$ lsmod |grep global
globalmem 16384 0
weiqifa@bsp-ubuntu1804:~/c/globalmem$
weiqifa@bsp-ubuntu1804:~/c/globalmem$ cat /proc/devices |grep global
230 globalmem
weiqifa@bsp-ubuntu1804:~/c/globalmem$
创建设备文件节点
使用mknod创建设备节点的时候,后面跟上的参数需要跟我们在/proc/devices下面看到的对应,也就是我们在驱动里面申请的主设备号。
weiqifa@bsp-ubuntu1804:~/c/globalmem$ sudo mknod /dev/globalmem c 230 0
weiqifa@bsp-ubuntu1804:~/c/globalmem$ ls /dev/globalmem -al
crw-r--r-- 1 root root 230, 0 Dec 22 16:19 /dev/globalmem
weiqifa@bsp-ubuntu1804:~/c/globalmem$
使用命令读写设备文件
Linux 下的 echo 和cat 命令是十分有用,这两个命令可以让在不写代码的情况下就可以完成调试读写设备。
weiqifa@bsp-ubuntu1804:~/c/globalmem$ sudo chmod 777 /dev/globalmem
weiqifa@bsp-ubuntu1804:~/c/globalmem$ sudo echo "linux" > /dev/globalmem
weiqifa@bsp-ubuntu1804:~/c/globalmem$ cat /dev/globalmem
linux
cat: /dev/globalmem: No such device or address
weiqifa@bsp-ubuntu1804:~/c/globalmem$ cat /dev/globalmem
linux
cat: /dev/globalmem: No such device or address
weiqifa@bsp-ubuntu1804:~/c/globalmem$ sudo echo "linuxgdb" > /dev/globalmem
weiqifa@bsp-ubuntu1804:~/c/globalmem$ cat /dev/globalmem
linuxgdb
cat: /dev/globalmem: No such device or address
weiqifa@bsp-ubuntu1804:~/c/globalmem$
通过代码来读写设备文件
代码在下面阅读原文的链接里面。
weiqifa@bsp-ubuntu1804:~/c/globalmem$ gcc app-main.c && ./a.out
str:LINUX,GDB
weiqifa@bsp-ubuntu1804:~/c/globalmem$
使用传入参数设置主设备号
内核模块参数我觉得是一个比较冷门的知识点,冷门的原因是因为我们在做项目的时候很少使用这个参数,但是实际上这个参数非常有用。
我们可以把内核模块当做main函数,main函数是可以接收传参的,内核模块也可以在加载的时候接收传入的参数。
如下是把主设备号传给内核模块,但是需要注意,这个主设备号不能被占用了。
weiqifa@bsp-ubuntu1804:~/c/globalmem$ sudo insmod globalmem.ko globalmem_major=231
weiqifa@bsp-ubuntu1804:~/c/globalmem$ cat /proc/devices |grep globalmem
231 globalmem
weiqifa@bsp-ubuntu1804:~/c/globalmem$
增加自动创建设备节点的驱动文件
每次手动创建设备文件节点总是很麻烦,而且在实际编写设备驱动的时候,不会出现自己手动创建设备节点这种低端的操作。
当然了,聪明的内核提供了接口让我们在注册驱动的时候也把设备文件节点注册上去。
具体代码可以查看globalmem2.c里面的代码。
weiqifa@bsp-ubuntu1804:~/c/globalmem$ chmod 777 globalmem.ko
weiqifa@bsp-ubuntu1804:~/c/globalmem$ sudo insmod globalmem.ko
[sudo] password for weiqifa:
weiqifa@bsp-ubuntu1804:~/c/globalmem$ ls /dev/globalmem
/dev/globalmem
weiqifa@bsp-ubuntu1804:~/c/globalmem$ ls /dev/globalmem -al
crw------- 1 root root 238, 0 Dec 22 17:18 /dev/globalmem
weiqifa@bsp-ubuntu1804:~/c/globalmem$
/*修改权限后才可以正常进行独写操作*/
weiqifa@bsp-ubuntu1804:~/c/globalmem$ sudo chmod 777 /dev/globalmem
weiqifa@bsp-ubuntu1804:~/c/globalmem$ gcc app-main.c && ./a.out
write data ok!
str:LINUX,GDB
weiqifa@bsp-ubuntu1804:~/c/globalmem$
使用lseek操作文件位置
具体对应的文件是app-main2.c
weiqifa@bsp-ubuntu1804:~/c/globalmem$ gcc app-main2.c && ./a.out
file ret:0
write data ok! fd:3
str:123456789ABCDEF10111213141516171819201617181920
lseek:1
str:23456789ABCDEF10111213141516171819201617181920
weiqifa@bsp-ubuntu1804:~/c/globalmem$