本文为一个简单的字符设备驱动,涉及驱动编写、测试程序编写、Makefile编写、驱动加载/卸载,运行于Linux虚拟机,不涉及底层配置。撰写本文的主要目的为记录一下驱动的开发流程,参考了正点原子的驱动开发指南。 驱动代码 创建文件夹 1_chrdevbase/ ,下属 APP/ 与 Driver/ 两个文件夹,前者放测试程序,后者放驱动代码。 在 Driver/ 下创建 chrdevbase.c,驱动代码如下 /* * file name : chrdevbase.c * description : 一个简单的字符设备demo * author : 今朝无言 */ #include<linux/types.h> #include<linux/kernel.h> #include<linux/delay.h> #include<linux/ide.h> #include<linux/init.h> //引入module_init()以及module_exit() #include<linux/module.h> //与module相关的宏 MODULE_LICENSE("GPL"); MODULE_AUTHOR("今朝无言"); #define CHRDEVBASE_MAJOR 200 //主设备号,可通过 cat /proc/devices 查看所有设备及其主设备号 #define CHRDEVBASE_NAME "chrdevbase" //设备名 static char readbuf[100]; //读缓冲区 static char writebuf[100]; //写缓冲区 static char kerneldata[] = {"kernel data!"}; //内核数据,用于传递给测试APP,进行读取测试 /* * description : 打开设备 * @param - inode : 传递给设备的inode * @param - filp : 设备文件 * @return : 0 success;other failed */ static int chrdevbase_open(struct inode *inode, struct file *filp){ printk("chrdevbase open!\n"); return 0; } /* * description : 从设备读取数据 * @param - filp : 设备文件 * @param - buf : 返回给用户空间的数据缓冲区 * @param - cnt : 要读取的数据长度 * @param - offt : 相对文件首地址的偏移 */ static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){ int retvalue = 0; memcpy(readbuf, kerneldata, sizeof(kerneldata)); retvalue = copy_to_user(buf, readbuf, cnt); if(retvalue == 0){ printk("kernel send data ok!\n"); } else { printk("kernel send data failed!\n"); } return 0; } /* * description : 向设备写数据 * @param - filp : 设备文件 * @param - buf : 要写入设备的数据 * @param - cnt : 要写入的数据长度 * @param - offt : 相对文件首地址的偏移 */ static ssize_t chrdevbase_write(struct file *filp, const char *buf, size_t cnt, loff_t *offt){ int retvalue = copy_from_user(writebuf, buf, cnt); retvalue = copy_from_user(writebuf, buf, cnt); if(retvalue == 0){ printk("kernel receive data: %s \n",writebuf); } else { printk("kernel receive data failed!\n"); } return 0; } /* * description : 关闭设备 * @param - filp : 设备文件描述符 * @return : 0 success;other failed */ static int chrdevbase_release(struct inode *inode, struct file *filp){ printk("chrdevbase release! \n"); return 0; } /* * chrdevbase的file_operations结构体 * file_operations的定义见Kernel/include/linux/fs.h * 注意函数定义一定要相同,否则报`initialization from incompatible pointer type [-Werror=incompatible-pointer-types]`错 */ static struct file_operations chrdevbase_fops = { .owner= THIS_MODULE, .open= chrdevbase_open, .read= chrdevbase_read, .write= chrdevbase_write, .release= chrdevbase_release }; /* * description : 驱动入口函数 */ static int __init chrdevbase_init(void){ //若函数没有参数,要加void,否则报`function declaration isn’t a prototype [-Werror=strict-prototypes]`错 int retvalue = 0; retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops); if(retvalue < 0){ printk("chrdevbase driver register failed!\n"); } else { printk("chrdevbase driver register success!\n"); } return 0; } /* * description : 驱动出口函数 */ static void __exit chrdevbase_exit(void){ unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME); printk("chrdevbase exit!\n"); return; } //指定驱动入口和出口函数 module_init(chrdevbase_init); module_exit(chrdevbase_exit); 驱动代码Makefile 在 Driver/ 下创建 Makefile,内容如下 KERNELDIR := /lib/modules/4.15.0-189-generic/build #本机编译就/lib/modules/`uname -r`/build #交叉编译就使用对应的Kernel源码目录 CURRENT_PATH := $(shell pwd) #要生成的模块名 obj-m := chrdevbase.o build: kernel_modules kernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean 测试程序代码 在 APP/ 下创建 chrdevbaseAPP.c,代码如下 /* * file name : chrdevbaseAPP.c * description : chedevbase驱动的测试程序 * author : 今朝无言 */ #include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<stdlib.h> #include<string.h> static char usrdata[] = {"usr data!"}; //用户数据,用于传递给驱动,进行写入测试 // 用法:./chrdevbaseAPP /dev/chrdevbase arg int main(int argc, char *argv[]){ int fd, retvalue; char *filename; char readbuf[100], writebuf[100]; //检查参数 if(argc != 3){ printf("Error Usage!\n"); return -1; } filename = argv[1]; //打开驱动文件 fd = open(filename, O_RDWR); if(fd < 0){ printf("Can't open file %s!\n", filename); return -2; } //arg=1,从驱动文件读取数据 if(atoi(argv[2]) == 1){ retvalue = read(fd, readbuf, 50); if(retvalue < 0){ printf("read file %s failed!\n", filename); } else { printf("read data: %s\n", readbuf); } } //arg=2,向驱动写数据 if(atoi(argv[2]) == 2){ memcpy(writebuf, usrdata, sizeof(usrdata)); retvalue = write(fd, writebuf, 50); if(retvalue < 0){ printf("write file %s failed!\n", filename); } else { printf("write file success!\n"); } } //关闭设备 retvalue = close(fd); if(retvalue < 0){ printf("Can't close file %s!\n", filename); return -3; } return 0; } 测试程序Makefile 在 APP/ 下创建 Makefile,内容如下 build: gcc chrdevbaseAPP.c -o chrdevbaseAPP clean: rm chrdevbaseAPP 编译测试程序 编译驱动 驱动加载 使用 insmod 命令加载刚刚生成的驱动模块 sudo insmod chrdevbase.ko 执行 cat /proc/devices 查看驱动,如下图,可以看到驱动已经加载 创建设备节点文件 使用 mkmod 命令创建驱动节点 sudo mknod /dev/chrdevbase c 200 0 则创建字符设备文件/dev/chrdevbase,对该文件进行读写操作即可使用驱动,其中 ‘c’ 表示字符设备,200为主设备号,0为次设备号。 测试 进入 APP/ 文件夹,执行 sudo ./chrdevbaseAPP /dev/chrdevbase 1 进行设备读取测试,结果如下 可以看到用户接收到了从内核传递来的数据 ‘kernel data’ 。 执行 sudo ./chrdevbaseAPP /dev/chrdevbase 2 进行设备写入测试,结果如下 查看最后6条日志消息: dmesg | tail -6 其中前三条是前面进行读取测试的日志输出,后三条是进行写入测试的日志输出,可以看到内核接收到了用户发送来的数据 ‘usr data’ 。 驱动卸载 使用 rmmod 命令卸载驱动: sudo rmmod chrdevbase.ko 再使用 cat /proc/devices 查看,将发现 chrdevbase 设备已被卸载。 加载/卸载模块时的日志如下:
标签:开发,file,Linux,驱动,include,retvalue,设备,chrdevbase From: https://www.cnblogs.com/kn-zheng/p/17476548.html