关注微信公众号:Linux 内核拾遗
文章来源:https://mp.weixin.qq.com/s/LU_3jNRG3EkG2VDr0TzIGg
本文将结合前面介绍过的设备驱动知识,实现一个真实且完整的设备驱动,包含用户空间程序和内核驱动程序。
Linux 设备驱动系列(一)——设备驱动介绍
Linux设备驱动系列(二)——第一个设备驱动程序
Linux设备驱动系列(三)——参数传递
Linux设备驱动系列(四)——设备号
Linux设备驱动系列(五)——字符驱动设备文件
Linux设备驱动系列(六)——文件操作
1 内核空间程序(设备驱动)
本文将要实现的这个设备驱动,它允许用户程序将字符串或者其他数据存储进去,并且在读取设备文件的时候将先前保存的数据返回给用户程序。
1.1 几个有用的函数
首先介绍几个当前设备驱动程序用到的几个有用的函数。
1.1.1 kmalloc()/kfree()
跟用户空间一样,内核空间中同样需要动态申请和释放内存,这是通过 kmalloc()和 kfree()函数来完成的。
kmalloc()函数用于在内核空间中动态分配一段在物理上连续的内存空间,相对应的 kfree()用于释放 kmalloc()分配的内存地址空间,但是并不会擦除这段内存空间先前写入的数据。
#include <linux/slab.h>
void *kmalloc(size_t size, gfp_t flags);void kfree(const void *objp)
复制代码
需要说明的是,flags 参数指定的是内存管理器分配内存时候的一些行为。
下面是一些常用的 flags,其他的 flags 可以查阅"linux/gfp.h" 头文件中的定义:
GFP_USER:代表用户分配内存空间,可能会睡眠;
GFP_KERNEL:分配内核 RAM 内存空间,可能会睡眠;
GFP_ATOMIC:内存分配过程不会睡眠,通常可以用在中断上下文中;
GFP_HIGHUSER:从高端内存中分配。
1.1.2 copy_to/from_user()
由于用户态地址空间和内核态地址空间是相互隔离的,因此就需要 copy_to_user()和 copy_from_user()这两个函数,用于在用户态和内核态之间进行内存数据拷贝。
unsigned long copy_to_user(const void __user *to, const void *from, unsigned long n);unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
复制代码
这两个函数定义在"linux/uaccess.h"头文件中,其中带__user 的参数是用户空间内存地址,另外一个是内核空间内存地址,n 表示要拷贝的数据字节数。
这两个函数的返回值表示的是未能成功拷贝的字节数,取值为 0 表示拷贝成功。
1.2 系统调用与文件操作函数
当用户程序通过设备文件与内核设备驱动进行交互时,通常涉及以下几个常见的系统调用。
1.2.1 open()
当用户程序通过 open()系统调用打开设备文件时,会调用到相应的 open()回调函数。
在将要实现的设备驱动中,open()函数通过 kmalloc()分配一段内存空间,用于后续数据的缓冲区。
static int my_open(struct inode *inode, struct file *file){ if((data_buf = kmalloc(mem_size , GFP_KERNEL)) == 0){ pr_info("Failed to allocate memory for data_buf\n"); return -1; } strcpy(data_buf, "Hello_World"); pr_info("Device File Opened\n"); return 0;}
复制代码
1.2.2 write()
当用户程序通过 write()系统调用往设备驱动写入数据时,会调用到相应的 write()回调函数。
在将要实现的设备驱动中,write()函数通过 copy_from_user()将用户数据拷贝到内核地址空间,并且保存在先前分配的缓冲区中。
static ssize_t my_write(struct file *filp, const char __user *buf, size_t len, loff_t *off){ if( copy_from_user(data_buf, buf, len) ) pr_err("Data Write Error\n"); else pr_info("Data Write Done\n"); return len;}
复制代码
1.2.3 read()
当用户程序通过 read()系统调用从设备驱动读取数据时,会调用到相应的 read()回调函数。
在将要实现的设备驱动中,read()函数通过 copy_to_user()将缓存区中保存的数据拷贝到用户地址空间。
static ssize_t my_read(struct file *filp, char __user *buf, size_t len, loff_t *off){ if( copy_to_user(buf, data_buf, mem_size) ) pr_err("Data Read : Err!\n"); else pr_info("Data Read : Done!\n"); return mem_size;}
复制代码
1.2.4 close()
当用户程序通过 close()系统调用关闭设备文件时,会调用到相应的 close()回调函数。
在将要实现的设备驱动中,close()函数通过 kfree()释放先前分配的缓冲区内存空间,避免内存泄露。
static int my_release(struct inode *inode, struct file *file){ kfree(data_buf); pr_info("Device File Closed\n"); return 0;}
复制代码
1.3 完整的设备驱动代码
kernel_driver.c
#include <linux/kernel.h>#include <linux/init.h>#include <linux/module.h>#include <linux/kdev_t.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/device.h>#include<linux/slab.h>#include<linux/uaccess.h>#include <linux/err.h>
#define mem_size 1024
dev_t dev = 0;static struct class *dev_class;static struct cdev my_cdev;uint8_t *data_buf;
static int my_open(struct inode *inode, struct file *file){ if((data_buf = kmalloc(mem_size , GFP_KERNEL)) == 0){ pr_info("Failed to allocate memory for data_buf\n"); return -1; } strcpy(data_buf, "Hello_World"); pr_info("Device File Opened\n"); return 0;}
static int my_release(struct inode *inode, struct file *file){ kfree(data_buf); pr_info("Device File Closed\n"); return 0;}
static ssize_t my_read(struct file *filp, char __user *buf, size_t len, loff_t *off){ if( copy_to_user(buf, data_buf, mem_size) ) pr_err("Data Read : Err!\n"); else pr_info("Data Read : Done!\n"); return mem_size;}
static ssize_t my_write(struct file *filp, const char __user *buf, size_t len, loff_t *off){ if( copy_from_user(data_buf, buf, len) ) pr_err("Data Write Error\n"); else pr_info("Data Write Done\n"); return len;}
static struct file_operations fops ={ .owner = THIS_MODULE, .read = my_read, .write = my_write, .open = my_open, .release = my_release,};
static int __init my_init(void){ if((alloc_chrdev_region(&dev, 0, 1, "my_dev")) <0){ pr_info("Failed to allocate major number\n"); return -1; } pr_info("Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));
cdev_init(&my_cdev,&fops);
if((cdev_add(&my_cdev,dev,1)) < 0){ pr_info("Failed to add the device to the system\n"); goto r_class; }
if(IS_ERR(dev_class = class_create(THIS_MODULE,"my_class"))){ pr_info("Failed to create the struct class\n"); goto r_class; }
if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"my_device"))){ pr_info("Failed to create the Device\n"); goto r_device; }
pr_info("Device Driver Inserted\n"); return 0;
r_device: class_destroy(dev_class);r_class: unregister_chrdev_region(dev,1); return -1;}
static void __exit my_exit(void){ device_destroy(dev_class, dev); class_destroy(dev_class); cdev_del(&my_cdev); unregister_chrdev_region(dev, 1); pr_info("Device Driver Removed\n");}
module_init(my_init);module_exit(my_exit);
MODULE_LICENSE("GPL");MODULE_AUTHOR("feifei <feifei@gmail.com>");MODULE_DESCRIPTION("Simple Linux device driver");MODULE_VERSION("1.4");
复制代码
2 用户空间程序
下面是一个用户空间程序代码,它通过 open()/close()和 read()/write()系统调用,实现了与我们的设备驱动程序的交互。
user_prog.c
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>
char buf[1024];
int main(){ int fd; char option;
printf("*********************************\n"); fd = open("/dev/my_device", O_RDWR); if(fd < 0) { printf("Failed open device file\n"); return 0; }
while(1) { printf("****** Choose the Option ********\n"); printf(" (1) Write \n"); printf(" (2) Read \n"); printf(" (3) Exit \n"); printf("*********************************\n"); scanf(" %c", &option);
switch(option) { case '1': printf("Enter the data to write:"); scanf(" %[^\t\n]s", buf); printf("Writing ..."); write(fd, buf, strlen(buf)+1); printf("Done!\n"); break; case '2': printf("Reading ..."); read(fd, buf, 1024); printf("Done!\n\n"); printf("Data = %s\n\n", buf); break; case '3': printf("Exiting ..."); goto out; default: printf("Invalid Option: %c\n",option); break; } }
out: close(fd);}
复制代码
最终编译运行的演示效果如下:
关注微信公众号:Linux 内核拾遗
文章来源:https://mp.weixin.qq.com/s/LU_3jNRG3EkG2VDr0TzIGg
评论