掘金 后端 ( ) • 2024-04-30 10:55

关注微信公众号: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 <[email protected]>");
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);
}

最终编译运行的演示效果如下:

image-20240429232219129

关注微信公众号:Linux内核拾遗

文章来源:https://mp.weixin.qq.com/s/LU_3jNRG3EkG2VDr0TzIGg