关注微信公众号: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
评论