1. 写驱动的步骤
1.1 确定主设备号
一般都是直接让系统自己指定
static int major = 0; // 0就表示让系统自己选一个未使用的设备号
1.2 实现驱动层的read,write,open,close,ioctl等等函数
这些函数不需要全部实现,依据驱动需求实现需要用到的函数。这些函数是一些内核层次的函数。和内核提供的接口(read, write, open, close, ioctl等等)是有关系的,内核提供的这些函数会调用这些函数。
static ssize_t hello_drv_read(struct file * file, char __user * buf, size_t size, loff_t *offset) { // 读取设备对应的寄存器的值,并处理后将结果通过buf返回给上层函数open ... } static ssize_t hello_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) { // 目前还没用到该函数 ... } static int hello_drv_open(struct inode *node, struct file *file) { // 初始化硬件操作,配置对应的寄存器, 比如开时钟,设置GPIO模式等等操作 ... } static int hello_drv_close(struct inode *node, struct file *file) { // 关闭硬件设备,设置对应的寄存器 ... }
1.3 定义file_operation结构体
结构体,release函数对应于close函数,在关闭文件时,系统调用close函数中就会调用该release函数
将1.2中的函数绑定到该结构体内
static struct file_operations hello_drv = { .owner = THIS_MODULE, // 使用".成员变量"的赋值是gcc的用法 .open = hello_drv_open, .read = hello_drv_read, .write = hello_drv_write, .release = hello_drv_close, };
1.4 注册驱动函数
major = register_chrdev(0, "hello", &hello_drv); // 返回值就是系统分配的设备号
1.5 设置入口函数
驱动在挂载的命令就是"insmod xxx.ko", 而insmod就会调用该函数,完成驱动的注册
static int __init hello_init(void) { // 1. 注册驱动 // 2. 添加设备节点 } module_init(hello_init); // insmod
1.6 设置出口函数
使用rmmod会调用该函数
static void __exit hello_exit(void) { // 删除设备节点 // 取消驱动注册 } module_exit(hello_exit); // rmmod
1.7 添加设备节点
hello_class = class_create(THIS_MODULE, "hello_class"); err = PTR_ERR(hello_class); if (IS_ERR(hello_class)) { unregister_chrdev(major, "hello"); return -1; } device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); // /dev/hello
2. 实例代码
驱动程序简单地开辟一个字符串数组,在应用层程序写数据时存放在这个数组内,读数据时将该数组内的数据返回。
2.1 驱动程序代码
hello_drv.c
#include <linux/module.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/miscdevice.h> #include <linux/kernel.h> #include <linux/major.h> #include <linux/mutex.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/stat.h> #include <linux/init.h> #include <linux/device.h> #include <linux/tty.h> #include <linux/kmod.h> #include <linux/gfp.h> // 1. 确定主设备号 static int major = 0; static char kernel_buf[1024]; static struct class *hello_class; #define MIN(a,b) (a<b?a:b) // 3. 定义自己的read,write,open,release函数 static ssize_t hello_drv_read(struct file * file, char __user * buf, size_t size, loff_t *offset) { int err; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); err = copy_to_user(buf, kernel_buf, MIN(size,1024)); return MIN(size, 1024); } static ssize_t hello_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) { int err; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); err = copy_from_user(kernel_buf, buf, MIN(size,1024)); return MIN(size, 1024); } static int hello_drv_open(struct inode *node, struct file *file) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); return 0; } static int hello_drv_close(struct inode *node, struct file *file) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); return 0; } // 2. 定义自己的file_operations结构体 static struct file_operations hello_drv = { .owner = THIS_MODULE, //gcc用法 .open = hello_drv_open, .read = hello_drv_read, .write = hello_drv_write, .release = hello_drv_close, }; // 4. 注册驱动函数,将file_operation结构体告诉内核 // 5. 注册驱动函数需要一个入口函数,在安装驱动的时候回去调用该函数,从而完成注册 static int __init hello_init(void) { int err; major = register_chrdev(0, "hello", &hello_drv); hello_class = class_create(THIS_MODULE, "hello_class"); err = PTR_ERR(hello_class); if (IS_ERR(hello_class)) { unregister_chrdev(major, "hello"); return -1; } device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); // /dev/hello return 0; } // 6. 出口函数,卸载驱动的时候会调用该函数 static void __exit hello_exit(void) { device_destroy(hello_class, MKDEV(major, 0)); class_destroy(hello_class); unregister_chrdev(major, "hello"); } // 7. 提供设备信息,自动创建设备节点 module_init(hello_init); // insmod module_exit(hello_exit); // rmmod MODULE_LICENSE("GPL"); // 这个必须要有
2.2 应用测试程序代码
hello_drv_test.c
#include<stdio.h> #include<sys/types.h> #include<unistd.h> #include<fcntl.h> #include<sys/stat.h> #include<string.h> int main(int argc, char **argv) { int fd; int ret; char buf[1024]; if (argc < 2) { printf("Usage: %s -w <string>\n", argv[0]); printf(" %s -r\n", argv[0]); return -1; } fd = open("/dev/hello", O_RDWR); if (fd == -1) { perror("open error"); return -1; } // 写入数据 if (strcmp(argv[1], "-w") == 0) { if (argc < 3) { printf("Usage: %s -w string\n", argv[0]); printf(" %s -r\n", argv[0]); return -1; } if (write(fd, argv[2], strlen(argv[2])) == -1) { perror("write error"); return -1; } printf("write successful\n"); } // 读出数据 if (strcmp(argv[1], "-r") == 0) { ret = read(fd, buf, 1024); if (ret == -1) { perror("read error"); return -1; } else if (ret > 0) { printf("data: %s\n", buf); } printf("read successful\n"); } close(fd); return 0; }
2.3 Makefile
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- export ARCH CROSS_COMPILE KERN_DIR = /home/hxd/workdir/ebf_linux_kernel_6ull_depth1/build_image/build # linux内核目录,驱动程序是需要内核的一些头文件的 all: make -C $(KERN_DIR) M=`pwd` modules $(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.c clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order rm -f hello_drv_test obj-m += hello_drv.o
2.4 运行
- 执行make编译好后,将hello_drv_test程序文件和hello_drv.ko驱动文件拷贝到板子上,
- 使用命令insmod hello_drv.ko进行驱动的挂载,挂载好后可发现/dev目录下多了个hello字符设备文件
- 执行hello_drv_test程序