掘金 后端 ( ) • 2024-04-27 10:18

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

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

1 设备文件

设备文件不是普通的文件,它提供了一种便利的方式来访问系统资源,而不需要应用开发者了解底层设备的工作原理。与大多数Unix系统一样,设备驱动程序本身就是Linux内核的一部分。

  1. 从应用程序角度来看,它表现得跟文件一样,允许程序从中读取数据、写入数据以及进行内存映射等。
  2. 从内核角度看,当设备文件被访问的时候,内核需要识别访问的I/O请求并将其传递给设备驱动程序,由设备驱动执行某些操作以完成请求的处理。

所有的设备文件都保存在/dev/目录下。

设备文件通常在系统初始化的时候创建,设备文件关联的设备可以是真实的硬件设备,例如/dev/ttyS0、/dev/hda1等;也可以是伪设备,例如/dev/null,所有往/dev/null的写请求都会被丢弃。

image-20240426225332725

2 创建设备文件

有两种创建设备文件的方式:手动创建和自动创建。

2.1 手动创建设备文件

mknod命令可以手动创建一个设备文件:

mknod -m <permissions> <name> <device type> <major> <minor>

说明:

  • name:设备文件名,即/dev/;
  • device type:设备类型,c - 字符设备,b - 块设备;
  • ::设备驱动程序的主/次设备号;
  • -m :设备文件权限,跟普通文件类似。

这种方式允许任何人创建设备文件,并且可以在设备驱动加载之后再创建。

2.1.1 完整示例

manually_device_file.c

#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
 
dev_t dev = 0;

static int __init my_init(void)
{
  if((alloc_chrdev_region(&dev, 0, 1, "manually_dev")) <0){
    pr_err("Cannot allocate major number for device\n");
    return -1;
  }
  pr_info("Kernel Module Inserted\n");
  return 0;
}
static void __exit my_exit(void)
{
  unregister_chrdev_region(dev, 1);
  pr_info("Kernel Module 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.1");

编译后往内核插入模块,通过mknod手动创建设备文件:

image-20240426232623599

2.2 自动创建设备文件

udev是Linux内核中的设备管理器,它能够在/dev目录下自动创建或者移除设备节点,自动创建的设备文件都会被udev管理起来。

基本的使用步骤如下:

  1. 引用头文件"linux/device.h"和"linux/kdev_t/h";
  2. 创建struc class;
  3. 使用前面sruct class创建设备。

2.2.1 创建struct class

可以通过class_create为设备驱动创建对应的struct class,对应到/sys/class下面的一个结构,后续可以使用class_destroy来销毁struc class:

struct class * class_create(struct module *owner, const char *name);
void class_destroy (struct class * cls);

2.2.2 创建设备

可以通过device_create创建字符设备,并注册到指定的struct class,相应地会在/sys/device下面创建对应的结构。

struct device *device_create(struct *class, struct device *parent, dev_t dev,
                             void * drvdata, const char *fmt, ...);
void device_destroy (struct class * class, dev_t devt);

说明:

  • class:要注册的目标struct class;
  • parent:即将创建的新设备的父设备;
  • devt:设备的主/次设备号;
  • drvdata:往设备回调函数传递的数据;
  • fmt和...:设备名称。

2.2.3 完整示例

dynamically_device_file.c

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/device.h>
 
dev_t dev = 0;
static struct class *dev_class;

static int __init my_init(void)
{
  if((alloc_chrdev_region(&dev, 0, 1, "my_dev")) <0){
    pr_err("Cannot allocate major number for device\n");
    return -1;
  }
  pr_info("Major = %d Minor = %d \n",MAJOR(dev), MINOR(dev));

  dev_class = class_create(THIS_MODULE,"my_class");
  if(IS_ERR(dev_class)){
    pr_err("Cannot create the struct class for device\n");
    goto r_class;
  }

  if(IS_ERR(device_create(dev_class,NULL,dev,NULL,"my_device"))){
    pr_err("Cannot create the Device\n");
    goto r_device;
  }
  pr_info("Kernel Module 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);
  unregister_chrdev_region(dev, 1);
  pr_info("Kernel Module 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.2");

编译完成后插入内核模块,可以看到/dev相面自动创建了对应的设备文件:

image-20240426232941561

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

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