linux设备驱动主要分为三类:字符设备驱动、块设备驱动、网络设备驱动
其中字符设备驱动适合用来入门,结构简单,更多关于设备驱动的东西这里就不作说明。
上传了完整项目文档说明和代码:操作系统课程设计-简单字符设备和块设备驱动程序
字符设备驱动开发流程
1.编写驱动代码
1.1 定义一个cdev 来表示你的驱动所对应的设备
struct cdev chrdev;
1.2 为你设备申请一个设备号
unsigned int TestMajor=0;// 主设备号
unsigned int TestMinor=0;// 次设备号
char *name="demo1";//驱动名称
dev_t dev_no; //设备号
int ret; // 返回结果
dev_no =MKDEV(TestMajor,TestMinor);
if(dev_no>0){
ret=register_chrdev_region(dev_no, 1,name);//静态注册设备号
}
else{//动态申请设备号
alloc_chrdev_region(&dev_no,TestMinor, 1,name);
}
if(ret<0){
return ret;
}
1.3 定义文件操作集
int my_open(struct inode *i,struct file *f){
printk("cdev init\n");
return 0;
}
int my_release(struct inode *i,struct file *f){
printk("cdev release\n");
return 0;
}
static ssize_t my_write(struct file *f,const char __user *u,size_t l,loff_t *o){
printk("write string\n");
return l;
}
static ssize_t my_read(struct file *f,char __user *u,size_t l,loff_t *o){
printk("read string\n");
return l;
}
struct file_operations fops={
.owner=THIS_MODULE,
.open=my_open,
.release=my_release,
.write=my_write,
.read=my_read
};
1.4 初始化 cdev并添加到内核
cdev_init(&chrdev,&fops);
cdev_add(&chrdev,dev_no,1);//先初始化设备号,然后注册cdev
1.5 设备号和cdev 的注销
unregister_chrdev_region(dev_no, 1);
cdev_dev(&chrdev);
在贴上完整代码前先注明一点内容:
一个驱动程序必须的部分如下:
static int my_init(void){
//驱动的初始化
return 0;
}
static void my_exit(void){
//驱动的退出
}
module_init(my_init); //调用系统函数来设置驱动初始化函数
module_exit(my_exit); //设置驱动结束的函数
MODULE_AUTHOR("guoz"); //设置作者 ,非必须
MODULE_DESCRIPTION("this ostest demo1"); //设置描述,非必须
MODULE_LICENSE("GPL"); // 必须加上,含义自行查找
字符设备驱动涉及到了用户态和核态的信息传递,这里放上两个用户传递信息的系统函数:
#include<linux/uaccess.h> //头文件
//函数原型
unsigned long copy_to_user(void *to, const void __user *from, usigned long count);
unsigned long copy_from_user(void __user *to, const void *from, usigned long count);
完整驱动程序代码如下:
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/cdev.h>
#include<linux/fs.h>
#include<linux/kdev_t.h>
#include<linux/types.h>
#include<linux/uaccess.h>
#include<linux/string.h>
struct cdev chrdev;
unsigned int major=0;
unsigned int minor=0;
dev_t dev_no;
int ret;
int my_open(struct inode *i,struct file *f){
printk("cdev init\n");
return 0;
}
int my_release(struct inode *i,struct file *f){
printk("cdev release\n");
return 0;
}
static ssize_t my_write(struct file *f,const char __user *u,size_t l,loff_t *o){
char buf[100];
copy_from_user(buf,u,l);
printk(KERN_EMERG"write string:%s",buf);
return l;
}
static ssize_t my_read(struct file *f,char __user *u,size_t l,loff_t *o){
char *buf="hello,user!";
copy_to_user(u,buf,strlen(buf));
printk(KERN_EMERG"read string:%s",buf);
return l;
}
struct file_operations fops={
.owner=THIS_MODULE,
.open=my_open,
.release=my_release,
.write=my_write,
.read=my_read
};
static int my_init(void){
dev_no=MKDEV(major,minor);
if(dev_no>0){
ret=register_chrdev_region(dev_no,1,"demo1");
}else{
ret=alloc_chrdev_region(&dev_no,0,1,"demo1");
}
if(ret<0){
return ret;
}
cdev_init(&chrdev,&fops);
chrdev.owner=THIS_MODULE;
cdev_add(&chrdev,dev_no,1);
return 0;
}
static void my_exit(void){
unregister_chrdev_region(dev_no,1);
cdev_del(&chrdev);
}
module_init(my_init);
module_exit(my_exit);
MODULE_AUTHOR("guoz");
MODULE_DESCRIPTION("this ostest demo1");
MODULE_LICENSE("GPL");
2.编写Makefile文件
用于编译驱动程序,文件可复制粘贴使用,注意修改 obj-m 的值
ifneq ($(KERNELRELEASE),)
obj-m := demo1.o
else
PWD := $(shell pwd)
KVER := $(shell uname -r)
KDIR := /lib/modules/$(KVER)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
rm -fr .*.cmd *.o *.mod.c *.ko .tmp_versions modules.* Module.*
endif
这里也有坑注意,只有当该行写的命令时才能用 tab 符号开头,而且字符编码需要用 utf-8
3.检查驱动安装是否完成
上面的步骤完成后,结果就是两个文件
demo1.c Makefile
3.1 在当前目录执行 make 命令(默认执行的是make all)
gz@ubuntu:~/Desktop/demo1$ ls
demo1.c Makefile test.c
gz@ubuntu:~/Desktop/demo1$ make
make -C /lib/modules/4.15.0-43-generic/build M=/home/gz/Desktop/demo1 modules
make[1]: Entering directory '/usr/src/linux-headers-4.15.0-43-generic'
CC [M] /home/gz/Desktop/demo1/demo1.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/gz/Desktop/demo1/demo1.mod.o
LD [M] /home/gz/Desktop/demo1/demo1.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.15.0-43-generic'
gz@ubuntu:~/Desktop/demo1$ ls
demo1.c demo1.mod.c demo1.o modules.order test.c
demo1.ko demo1.mod.o Makefile Module.symvers
3.2 向内核插入驱动程序并获取设备号
root@ubuntu:/home/gz/Desktop/demo1# insmod demo1.ko
root@ubuntu:/home/gz/Desktop/demo1# cat /proc/devices
Character devices:
...
243 demo1
...
3.3 添加设备文件
从上一步可以得知 设备号为 243
root@ubuntu:/home/gz/Desktop/demo1# mknod /dev/demo1 c 243 0
root@ubuntu:/home/gz/Desktop/demo1# ll /dev/demo1
crw-r--r-- 1 root root 243, 0 Jan 28 22:28 /dev/demo1
4.编写简单测试程序
测试文件比较简单,这里直接贴完整代码 (test.c)
#include<stdio.h>
#include <unistd.h>
#include<fcntl.h>
int main(void)
{
int fd;
char *buf="test.c";
char buf2[20];
fd=open("/dev/demo1",O_RDWR);
if(fd<0){
printf("fd<0\n");
}else{
printf("fd:%d\n",fd);
}
write(fd,buf,7);
read(fd,buf2,18);
printf("res:%s",buf2);
close(fd);
}
5.检验成果
运行测试程序,结果如下:
root@ubuntu:/home/gz/Desktop/demo1# gcc test.c
root@ubuntu:/home/gz/Desktop/demo1# ./a.out
fd:3
res:hello,user!root
在驱动程序中,使用printk输出的内容我们不能直接看到,这里采用dmesg命令查看
root@ubuntu:/home/gz/Desktop/demo1# dmesg
...
[ 909.055395] cdev init
[ 909.055458] write string:test.c
[ 909.055461] read string:hello,user!
[ 909.055469] cdev release
参考文章:字符设备驱动模块与测试代码编写