写点什么

Linux 设备驱动系列(七)——真实的设备驱动程序

  • 2024-04-29
    浙江
  • 本文字数:3857 字

    阅读完需:约 13 分钟

Linux设备驱动系列(七)——真实的设备驱动程序

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


发布于: 刚刚阅读数: 5
用户头像

聚沙成塔 2023-01-12 加入

分享Linux内核开发相关的编程语言、开发调试工具链、计算机组成及操作系统内核知识、Linux社区最新资讯等

评论

发布
暂无评论
Linux设备驱动系列(七)——真实的设备驱动程序_Linux Kenel_Linux内核拾遗_InfoQ写作社区