Символьное устройство под управлением Linux

Диаграмма архитектуры

Подать заявку на символьный номер устройства

Что такое номер устройства

LinuxПредусматривается, что каждое символьное устройство или блочное устройство должно иметь уникальный номер устройства. Номер устройства состоит из старшего номера устройства и младшего номера устройства. Старший номер устройства используется для обозначения определенного типа драйвера, например мыши и клавиатуры, которые можно классифицировать как USBдрайверы. Младший номер устройства используется для представления каждого устройства в этом драйвере. Например, какая мышь, какая клавиатура и т.д.
Поэтому, когда мы разрабатываем символьные драйверы, подача заявки на номер устройства является первым шагом.Только с номером устройства мы можем зарегистрировать устройство в системе.

Тип номера устройства

LinuxНомер устройства представлен в dev_tтипе данных, называемом . dev_tопределяется include/linux/types.hвнутри. Как показано на рисунке ниже, из определения видно, dev_tчто это u32тип, то есть unsigned intтип. Таким образом, номер устройства представляет собой 32-битный тип данных. Среди них старшие 12 бит — это основной номер устройства, а младшие 20 бит — второстепенный номер устройства.

typedef u32 __kernel_dev_t;
typedef __kernel_dev_t          dev_t;

Поэтому, когда мы разрабатываем символьные драйверы, подача заявки на номер устройства является первым шагом.Только с номером устройства мы можем зарегистрировать устройство в системе.

Макрос операции с номером устройства

В файле include/linux/kdev_t.hпредставлены несколько определений макросов для рабочих номеров устройств , а именно:

#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))
  • Макрос MINORBITSуказывает количество младших номеров устройств, всего 20 цифр.

  • Макрос MINORMASKиспользуется для вычисления младшего номера устройства.

  • Макрос MAJORозначает получение dev_tиз него , а суть в dev_tсдвиге вправо на 20 бит.

  • Макрос MINORозначает получить dev_tот него , а суть в том, чтобы взять значение младших 20 бит.

  • Макрос MKDEVиспользуется для составления старшего maи младшего номеров miв dev_tтипизированный номер устройства.

Назначение номера устройства

При написании кода драйвера символьного устройства вы можете назначать номера устройств статически или динамически .

Статическое распределение номеров устройств означает, что разработчики сами указывают номер устройства, например, выбирают номер устройства 50 в качестве основного номера устройства. Поскольку некоторые номера устройств могли использоваться системой, вы не можете использовать эти номера устройств, которые использовались системой, при указании номера устройства. Используйте команду command cat /proc/devices, чтобы проверить, какие номера устройств использовались в текущей системе.

Динамическое распределение номеров устройств означает, что система автоматически выберет номер устройства, который не использовался для нас, что автоматически позволяет избежать проблем с конфликтами распределения номеров устройств.
В ядре предусмотрены функции для динамического выделения номеров устройств и статического распределения номеров устройств, которые определены в include/linux/fs.hнем, как показано в следующей таблице.

Функция статического назначения номера устройства register_chrdev_region
Функция динамического распределения номеров устройств alloc_chrdev_region
  • register_chrdev_regionфункция

    Прототип функции:int register_chrdev_region(dev_t, unsigned, const char *);

    Параметры функции:
    Параметр 1: Начальное значение номера устройства, тип dev_t. Например MKDEV(100,0), это означает, что начальный основной номер устройства равен 100, а начальный дополнительный номер устройства — 0.

    Параметр 2: количество младших номеров устройств, указывающее, сколько существует младших номеров устройств, когда основные номера устройств совпадают.

    Параметр 3: Имя устройства.

    ​ Функция возвращает значение: успех возвращает 0, возвращаемое значение ошибки меньше 0

  • alloc_chrdev_regionфункция

    Прототип функции:int alloc_chrdev_region(dev_t*, unsigned, unsigned, const char *);

    Параметры функции:
    Параметр 1: Сохранить автоматически запрашиваемый номер устройства.

    Параметр 2: начальный адрес младшего номера устройства.Младший номер устройства обычно начинается с 0, поэтому этот параметр обычно устанавливается равным 0.

    ​ Параметр 3: количество номеров устройств, на которые нужно подать заявку.

    Параметр 4: Имя устройства.

    ​ Функция возвращает значение: успех возвращает 0, возвращаемое значение ошибки меньше 0

  • unregister_chrdev_regionПрототип функции
    :extern void unregister_chrdev_region(dev_t, unsigned);

    Функциональная функция: функция освобождения номера устройства, номер устройства должен быть освобожден после отмены символьного устройства.

    Параметры функции:

    Параметр 1: номер устройства, которое необходимо освободить.

    Параметр 2: количество выпущенных номеров устройств.

пример

dev_t.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>

static int major = 0;
static int minor = 0;

module_param(major, int, S_IRUGO);
module_param(minor, int, S_IRUGO);

dev_t dev_num;
static int moduleparam_init(void)
{
    
    
    int ret;
    if (major)
    {
    
    
        printk("major is %d\n", major);
        printk("minor is %d\n", minor);
        dev_num = MKDEV(major, minor);
        ret = register_chrdev_region(dev_num, 1, "chrdev_name");
        if (ret)
        {
    
    
            printk("register_chrdev_region failed\n");
        }
        printk("register_chrdev_region succeed\n");
    }
    else
    {
    
    
        ret = alloc_chrdev_region(&dev_num, 0, 1, "alloc_name");
        if (ret)
        {
    
    
            printk("alloc_chrdev_region failed\n");
        }
        printk("alloc_chrdev_region succeed\n");
        major = MAJOR(dev_num);
        minor = MINOR(dev_num);
        printk("major is %d\n", major);
        printk("minor is %d\n", minor);
    }
    return 0;
}

static void moduleparam_exit(void)
{
    
    
    unregister_chrdev_region(dev_num, 1);
    printk("bye bye\n");
}

module_init(moduleparam_init);
module_exit(moduleparam_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

Makefile

obj-m += dev_t.o
KDIR:=/lib/modules/$(shell uname -r)/build
PWD?=$(shell pwd)
all:
	make -C $(KDIR) M=$(PWD) modules
clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.orde \.*.cmd *mod
  1. Статическое распределение номеров устройств: сначала cat /proc/devicesпроверьте, какие основные номера устройств не заняты, например, если 220 не занято, затем пропуститеsudo insmod dev_t.ko major=220 minor=0

    [795650.456421] старший 220
    [795650.456423] второстепенный 0
    [795650.456424
    ]

  2. Динамическое распределение номеров устройств: непосредственно черезsudo insmod dev_t.ko

    [795552.620925] alloc_chrdev_region успешно
    [795552.620927] старший 240
    [795552.620927] младший 0
    [795632.868577] до свидания

Аннотировать устройства класса символов

структура cdev

Linux, используйте cdevструктуру для описания символьного устройства. файл определения структуры cdev include/linux/cdev.h. код показывает, как показано ниже:

struct cdev {
    
    
	struct kobject kobj;
	struct module *owner; //所属模块
	const struct file_operations *ops; //文件操作结构体
	struct list_head list;
	dev_t dev; //设备号
	unsigned int count;
} __randomize_layout;

функция cdev_init

cdev_initФункция используется для инициализации cdevчленов структуры и установления связи между cdevи . file_operationsПрототип функции выглядит следующим образом ( fs/char_dev.c):

/**
 * cdev_init() - initialize a cdev structure
 * @cdev: the structure to initialize
 * @fops: the file_operations for this device
 *
 * Initializes @cdev, remembering @fops, making it ready to add to the
 * system with cdev_add().
 */
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
    
    
    memset(cdev, 0, sizeof *cdev);
	INIT_LIST_HEAD(&cdev->list);
	kobject_init(&cdev->kobj, &ktype_cdev_default);
    //建立cdev和file_operations之间的联系
	cdev->ops = fops;
}

cdev_add функция

cdev_addcdevФункция используется для добавления структуры в систему , то есть добавления символьного устройства

/**
 * cdev_add() - add a char device to the system
 * @p: the cdev structure for the device
 * @dev: the first device number for which this device is responsible
 * @count: the number of consecutive minor numbers corresponding to this
 *         device
 *
 * cdev_add() adds the device represented by @p to the system, making it
 * live immediately.  A negative error code is returned on failure.
 */
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
    
    
	int error;

	p->dev = dev;
	p->count = count;

	if (WARN_ON(dev == WHITEOUT_DEV))
		return -EBUSY;

	error = kobj_map(cdev_map, dev, count, NULL,
			 exact_match, exact_lock, p);
	if (error)
		return error;

	kobject_get(p->kobj.parent);

	return 0;
}

функция cdev_del

cdev_delcdevФункция, используемая для удаления структуры из системы

/**
 * cdev_del() - remove a cdev from the system
 * @p: the cdev structure to be removed
 *
 * cdev_del() removes @p from the system, possibly freeing the structure
 * itself.
 *
 * NOTE: This guarantees that cdev device will no longer be able to be
 * opened, however any cdevs already open will remain and their fops will
 * still be callable even after cdev_del returns.
 */
void cdev_del(struct cdev *p)
{
    
    
        cdev_unmap(p->dev, p->count);
        kobject_put(&p->kobj);
}

пример

cdev.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>

dev_t dev_num;
struct cdev cdev_test;

struct file_operations cdev_test_ops = {
    
    
    .owner = THIS_MODULE,
};

static int modulecdev_init(void)
{
    
    
    int ret;
    ret = alloc_chrdev_region(&dev_num, 0, 1, "alloc_name");
    if (ret)
    {
    
    
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    cdev_test.owner = THIS_MODULE;
    cdev_init(&cdev_test, &cdev_test_ops);
    cdev_add(&cdev_test, dev_num, 1);
    return 0;
}

static void modulecdev_exit(void)
{
    
    
    unregister_chrdev_region(dev_num, 1);
    cdev_del(&cdev_test);
    printk("bye bye\n");
}

module_init(modulecdev_init);
module_exit(modulecdev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

Makefile

obj-m += cdev.o
KDIR:=/lib/modules/$(shell uname -r)/build
PWD?=$(shell pwd)
all:
	make -C $(KDIR) M=$(PWD) modules
clean:
	rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.orde \.*.cmd *mod

Роль структуры файловых операций

LinuxЕсть очень важное понятие, которое называется «все есть файл», то есть Linuxустройства в нем такие же, как и в обычных файлах. Доступ к устройству подобен доступу к файлу. В прикладной программе мы можем использовать open,read,write,close,ioctlэти несколько системных вызовов для управления драйвером. Когда мы вызываем функцию в приложении open, мы в конечном итоге выполняем функцию в драйвере open. Итак, file_operationsсистемный вызов и драйвер связаны.

изображение-20230329153123789

структура file_operations

file_operationsСтруктура определена в include/linux/fs.hфайле, и эта структура очень большая.

struct file_operations {
    
    
	struct module *owner;//拥有改结构体的模块指针,一般设置为THIS_MODULE
	loff_t (*llseek) (struct file *, loff_t, int); //用于修改文件当前的读写位置
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //用于读取设备文件
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);  //用于向设备文件写数据
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
	int (*iopoll)(struct kiocb *kiocb, bool spin);
	int (*iterate) (struct file *, struct dir_context *);
	int (*iterate_shared) (struct file *, struct dir_context *);
	__poll_t (*poll) (struct file *, struct poll_table_struct *); // 轮询函数,用于查询设备是否可被非阻塞的立即读写
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //提供设备相关控制命令的实现
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //与unlocked_ioctl功能一样
	int (*mmap) (struct file *, struct vm_area_struct *); //用于将设备内存映射到用户空间中去,应用程序可以直接访问他而无须在内核 和应用间进行内存的赋值
	unsigned long mmap_supported_flags;
	int (*open) (struct inode *, struct file *); //用于打开设备文件
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *); //用于释放设备文件
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*setfl)(struct file *, unsigned long);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **, void **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
	void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
	unsigned (*mmap_capabilities)(struct file *);
#endif
	ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
			loff_t, size_t, unsigned int);
	loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
				   struct file *file_out, loff_t pos_out,
				   loff_t len, unsigned int remap_flags);
	int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;

Концепция узла устройства

Что такое узел устройства

В соответствии Linuxс идеей, что все является файлом. Каждое устройство Linuxимеет соответствующий «файл устройства» в системе для их представления, и прикладная программа может управлять соответствующим оборудованием, работая с этим «файлом устройства». Как показано в следующем коде:
fd = open("/dev/hello",O_RDWR);
этот «файл устройства» является узлом устройства. Таким образом, Linuxузел устройства является мостом между прикладной программой и программой-драйвером. Узлы устройств создаются devв каталоге.

crw-rw-r--+ 1 root netdev 10, 242 Mar 19 18:55 /dev/rfkill

Выше rfkillпоказан узел устройства, который находится /devв каталоге, cуказывающий, что это символьное устройство, которое 10является старшим номером устройства и 242второстепенным номером устройства. LinuxСоответствующую структуру можно найти file_operationsпо основному номеру устройства. Используйте младший номер устройства, чтобы узнать, какое устройство является первым в своем роде. Это определяет, какой это драйвер.

Как Linux создает узлы

Создание узлов устройств вручную

Узлы устройств могут быть созданы с помощью команд mknod.

mknodФормат команды:

mknod 设备节点名称 设备类型(字符设备用c,块设备用b) 主设备号 次设备号
举例:
mknod /dev/test c 236 0
пример

file.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>

dev_t dev_num;
struct cdev cdev_test;

static int major = 0;
static int minor = 0;

static int cdev_test_open(struct inode *inode, struct file *file)
{
    
    
    printk("This is a cdev_test_open\n");
    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    
    
    printk("This is a cdev_test_read\n");
    return 0;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    
    
    printk("This is a cdev_test_write\n");
    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    
    
    printk("This is a cdev_test_release\n");
    return 0;
}

struct file_operations cdev_test_ops = {
    
    
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
};

static int file_init(void)
{
    
    
    int ret;
    ret = alloc_chrdev_region(&dev_num, 0, 1, "alloc_name");
    if (ret)
    {
    
    
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    major = MAJOR(dev_num);
    minor = MINOR(dev_num);
    printk("major is %d\n", major);
    printk("minor is %d\n", minor

    cdev_test.owner = THIS_MODULE;
    cdev_init(&cdev_test, &cdev_test_ops);
    cdev_add(&cdev_test, dev_num, 1);

    return 0;
}

static void file_exit(void)
{
    
    
        unregister_chrdev_region(dev_num, 1);
        cdev_del(&cdev_test);
        printk("bye bye\n");
}

module_init(file_init);
module_exit(file_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

app.c(gcc app.c -o app.out)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
    
    
    int fd;
    char buf[64] = {
    
    };
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
    
    
        perror("open error \n");
        return fd;
    }
    close(fd);
    return 0;
}

  • sudo insmod file.ko

    [805697.526236] alloc_chrdev_region успешно
    [805697.526237] старший 240
    [805697.526237] младший 0

  • sudo mknod /dev/test c 240 0

  • sudo ./app.out

    [806101.252401] Это cdev_test_open
    [806101.252402] Это cdev_test_release

Автоматически генерировать узлы устройств

mdevАвтоматическое создание и удаление узлов устройств может быть реализовано с помощью механизма

механизм udev

LinuxЭто пользовательская программа, которая может udevсоздавать или udevудалять узлы устройств в соответствии со статусом устройств в системе. Например, когда драйвер успешно загружен, он Linuxавтоматически /devсоздает соответствующие узлы устройств в каталоге. , /devузел устройства в каталоге будет автоматически удален.
Во встроенных Linuxмы используем упрощенную версию mdevyes . При использовании для сборки корневой файловой системы она будет создана автоматически .mdevudevbusyboxbusyboxmdev

изображение-20230329175552120

функция class_create

class_createФункция определена в include/linux/device.hфайле, который является определением макроса.Использование этой функции создаст файл /sys/classниже , как показано на следующем рисунке:

/* This is a #define to keep the compiler from merging different
 * instances of the __key variable */
#define class_create(owner, name)		\
({
      
      						\
	static struct lock_class_key __key;	\
	__class_create(owner, name, &__key);	\
})

extern struct class * __must_check __class_create(struct module *owner,
						  const char *name,
						  struct lock_class_key *key);

class_createВсего есть два параметра, первый параметр ownerобычно THIS_MODULE, а второй параметр name— имя класса.

функция device_create

После class_createсоздания класса вам нужно использовать device_createфункцию для создания устройства под классом. определяется в include/linux/device.hфайле. Следующее:

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

Первый параметр classуказывает, под каким классом создано устройство,

Второй параметр parent— это родительское устройство, для которого обычно установлено значение NULL, то есть родительского устройства нет.

Третий параметр dev_t— номер устройства. Четвертый параметр drvdata— это данные, которые может использовать устройство NULL.

Пятый параметр fmt— имя узла устройства.

функция device_destroy

Используйте device_destroyфункцию удаления созданного устройства, прототип функции выглядит следующим образом:

extern void device destroy(struct class *cls, dev t devt);

Параметр classпредставляет собой класс удаляемого устройства dev_tи номер удаляемого устройства.

функция class_destroy

Используйте class_destroyфункцию для удаления созданного класса, прототип функции выглядит следующим образом:

extern void class destroy(struct class *cls);

Аргумент class- это класс, который нужно удалить.

пример

file.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>

dev_t dev_num;
struct cdev cdev_test;

static int major = 0;
static int minor = 0;

struct class *class;
struct device *device;

static int cdev_test_open(struct inode *inode, struct file *file)
{
    
    
    printk("This is a cdev_test_open\n");
    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    
    
    printk("This is a cdev_test_read\n");
    return 0;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    
    
    printk("This is a cdev_test_write\n");
    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    
    
    printk("This is a cdev_test_release\n");
    return 0;
}

struct file_operations cdev_test_ops = {
    
    
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
};

static int file_init(void)
{
    
    
    int ret;
    ret = alloc_chrdev_region(&dev_num, 0, 1, "alloc_name");
    if (ret)
    {
    
    
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    major = MAJOR(dev_num);
    minor = MINOR(dev_num);
    printk("major is %d\n", major);
    printk("minor is %d\n", minor);

    cdev_test.owner = THIS_MODULE;
    cdev_init(&cdev_test, &cdev_test_ops);
    cdev_add(&cdev_test, dev_num, 1);

    class_create(THIS_MODULE, "test");
    device_create(class, NULL, dev_num, NULL, "test");

    return 0;
}

static void file_exit(void)
{
    
    
    unregister_chrdev_region(dev_num, 1);
    cdev_del(&cdev_test);

    device_destroy(class, dev_num);
    class_destroy(class);
    printk("bye bye\n");
}

module_init(file_init);
module_exit(file_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

app.c(gcc app.c -o app.out)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    
    
    int fd;
    char buf[64] = {
    
    };
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
    
    
        perror("open error \n");
        return fd;
    }
    close(fd);
    return 0;
}

  1. sudo insmod file.ko

    [ 372.600726] alloc_chrdev_region успешно
    [ 372.600728] старший 240
    [ 372.600728] младший 0

  2. sudo ./app.out

    fengzc@ubuntu:~/study/drivers_test/05_file$ ls -l /sys/class/test
    total 0
    lrwxrwxrwx. 1 root root 0 29 марта 18:07 test -> …/…/devices/virtual/test/test
    fengzc@ubuntu:~/study/drivers_test/05_file$ ls -l /dev/test
    crw------- . 1 корень корень 240, 0 29 марта 18:04 /dev/test

Пространство ядра и пространство пользователя

LinuxСистема делит доступное пространство памяти на две части: одна — пространство ядра , а другая — пространство пользователя . Операционная система и драйверы работают в пространстве ядра (режим ядра), а приложения работают в пространстве пользователя (режим пользователя).
Почему существует различие между пространством ядра и пространством пользователя?

  • Код в пространстве ядра управляет аппаратными ресурсами, а код в пользовательском пространстве может использовать аппаратные ресурсы в системе только через интерфейс системных вызовов, предоставляемый ядром. Такой дизайн может обеспечить безопасность и стабильность самой операционной системы.
  • С другой стороны, код в пространстве ядра более ориентирован на управление системой, а код в пространстве пользователя — на реализацию бизнес-логики. Разделение труда между ними различно.

Как войти в пространство ядра из пользовательского пространства?

Управление аппаратными ресурсами осуществляется в пространстве ядра, и приложения не могут напрямую работать с оборудованием, мы можем выполнять такие задачи, только вызывая интерфейс ядра. Например, если приложение хочет прочитать файл на диске, приложение может инициировать «системный вызов» к ядру, чтобы сообщить ядру: «Я хочу прочитать файл на диске». Этот процесс фактически позволяет процессу войти в состояние ядра из пользовательского состояния через специальную инструкцию.В пространстве ядра CPUможет выполняться любая инструкция, конечно, включая чтение данных с диска. Конкретный процесс заключается в том, чтобы сначала прочитать данные в пространство ядра, затем скопировать данные в пространство пользователя и переключиться из режима ядра в пользовательский режим. В этот момент приложение вернулось из системного вызова и получило нужные данные и может продолжить выполнение.

Поскольку процесс должен переключаться из пользовательского пространства в пространство ядра, чтобы использовать аппаратные ресурсы системы, существует три метода переключения: системный вызов , мягкое прерывание и аппаратное прерывание .

изображение-20230330090940564

Обмен данными пространства пользователя и пространства ядра

Память в пространстве ядра и пользовательском пространстве не могут получить доступ друг к другу. Но многим бизнес-программам необходимо обмениваться данными с ядром, например, приложения используют readфункции для чтения данных из драйвера и используют writeфункции для записи данных в драйвер. Это требует помощи copy_from_userэтих copy_to_userдвух функций для завершения передачи данных. Они копируют данные из пространства пользователя в пространство ядра и копируют данные из пространства ядра в пространство пользователя. Эти две функции определены в linux/include/asm-arm/uaccess.hфайле.

  • copy_to_userПрототип функции
    :unsigned long copy_to_user(void_user *to, const void *from, unsigned long n);

    Функция: скопировать данные из пространства ядра в пространство пользователя.

    Параметры функции: *toуказатель на пространство пользователя. *fromУказатель на пространство ядра, n — количество байтов, скопированных из пространства ядра в пространство пользователя.

    Возвращаемое значение функции: успех возвращает 0

  • copy_from_userПрототип функции
    :unsigned long copy_from_user(void * to, const void_user *from, unsigned long n)

    Функция: копировать данные пространства пользователя в пространство ядра.

    Параметры функции: *toуказатель на пространство ядра. *fromУказатель на пользовательское пространство, nкоторое представляет собой длину данных

    Возвращаемое значение функции: успех возвращает 0

пример

usr.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>

dev_t dev_num;
struct cdev cdev_test;

static int major = 0;
static int minor = 0;

struct class *class;
struct device *device;

static int cdev_test_open(struct inode *inode, struct file *file)
{
    printk("This is a cdev_test_open\n");
    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    char kbuf[32] = "This is a cdev_test_read";
    if (copy_to_user(buf, kbuf, strlen(kbuf)) != 0)
    {
        printk("copy_to_user is error");
        return -EFAULT;
    }

    printk("This is a cdev_test_read\n");
    return 0;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    char kbuf[32] = {0};
    if (copy_from_user(kbuf, buf, size) != 0)
    {
        printk("copy_from_user is error");
        return -EFAULT;
    }
    printk("kbuf: %s\n", kbuf);
    printk("This is a cdev_test_write\n");
    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    printk("This is a cdev_test_release\n");
    return 0;
}

struct file_operations cdev_test_ops = {
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
};

static int usr_init(void)
{
    int ret;
    ret = alloc_chrdev_region(&dev_num, 0, 1, "alloc_name");
    if (ret)
    {
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    major = MAJOR(dev_num);
    minor = MINOR(dev_num);
    printk("major is %d\n", major);
    printk("minor is %d\n", minor);

    cdev_test.owner = THIS_MODULE;
    cdev_init(&cdev_test, &cdev_test_ops);
    cdev_add(&cdev_test, dev_num, 1);

    class = class_create(THIS_MODULE, "test");
    device_create(class, NULL, dev_num, NULL, "test");

    return 0;
}

static void usr_exit(void)
{
    unregister_chrdev_region(dev_num, 1);
    cdev_del(&cdev_test);

    device_destroy(class, dev_num);
    class_destroy(class);
    printk("bye bye\n");
}

module_init(usr_init);
module_exit(usr_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

app.c(gcc app.c -o app.out)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    
    
    int fd;
    char buf1[32] = {
    
    };
    char buf2[32] = {
    
    "nihao"};
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
    
    
        perror("open error \n");
        return fd;
    }
    read(fd, buf1, sizeof(buf1));
    printf("buf1 is %s\n", buf1);


    write(fd, buf2, sizeof(buf2));
    return 0;
}

Использовать личные данные файла

Что такое личные данные файла?

Концепция файловых данных Linuxшироко используется в драйверах. Частные данные файла указывают частные данные private_dataна структуру устройства.

Затем readв writeтаких функциях, как private_dataдоступ к структуре устройства.

Зачем использовать личные данные файла?

LinuxНет четкого указания на то, что должны использоваться личные данные файла, но использование личных данных файла Linuxшироко используется в драйвере, что является Linux«скрытым правилом», которому следует драйвер. По сути, это также отражает Linuxобъектно-ориентированное мышление.

Сценарии использования частных данных

В Linux, старший номер устройства используется для представления соответствующего типа драйвера. Используйте второстепенный номер устройства для представления каждого устройства в этом типе драйвера, если наш драйвер теперь поддерживает устройства с одним и тем же основным устройством, но с разными второстепенными номерами устройств. Как написать наш драйвер, это использует fileприватные данные в структуре private_date.

функция container_of

Прототип функции:container_of(ptr, type, member)

Функция: Получить первый адрес всей структурной переменной через первый адрес члена в структурной переменной.

Параметры функции: Первый параметр ptr— это адрес члена в структурной переменной. Второй параметр type— тип конструкции. Третий параметр member— это конкретное имя структурной переменной.

пример

usr.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>

struct device_test
{
    
    
    dev_t dev_num;
    struct cdev cdev_test;

    int major;
    int minor;

    struct class *class;
    struct device *device;

    char kbuf[32];
};

struct device_test dev1;
struct device_test dev2;

static int cdev_test_open(struct inode *inode, struct file *file)
{
    
    
    dev1.minor = 0;
    dev2.minor = 1;
    file->private_data = container_of(inode->i_cdev, struct device_test, cdev_test);
    printk("This is a cdev_test_open\n");
    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    
    
    struct device_test *dev = (struct device_test *)file->private_data;
    if (copy_to_user(buf, dev->kbuf, strlen(dev->kbuf)) != 0)
    {
    
    
        printk("copy_to_user is error");
        return -EFAULT;
    }
    return 0;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    
    
    struct device_test *dev = (struct device_test *)file->private_data;
    if (dev->minor == 0)
    {
    
    
        if (copy_from_user(dev->kbuf, buf, size) != 0)
        {
    
    
            printk("copy_from_user is error");
            return -EFAULT;
        }
        printk("kbuf is %s\n", dev->kbuf);
    }
    else if (dev->minor == 1)
    {
    
    
        if (copy_from_user(dev->kbuf, buf, size) != 0)
        {
    
    
            printk("copy_from_user is error");
            return -EFAULT;
        }
        printk("kbuf is %s\n", dev->kbuf);
    }

    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    
    
    return 0;
}

struct file_operations cdev_test_ops = {
    
    
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
};

static int usr_init(void)
{
    
    
    int ret;
    ret = alloc_chrdev_region(&dev1.dev_num, 0, 2, "alloc_name");
    if (ret)
    {
    
    
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    dev1.major = MAJOR(dev1.dev_num);
    dev1.minor = MINOR(dev1.dev_num);
    printk("major is %d\n", dev1.major);
    printk("minor is %d\n", dev1.minor);

    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_ops);
    cdev_add(&dev1.cdev_test, dev1.dev_num, 1);

    dev1.class = class_create(THIS_MODULE, "test1");
    device_create(dev1.class, NULL, dev1.dev_num, NULL, "test1");

    dev2.major = MAJOR(dev1.dev_num + 1);
    dev2.minor = MINOR(dev1.dev_num + 1);
    printk("major is %d\n", dev2.major);
    printk("minor is %d\n", dev2.minor);

    dev2.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev2.cdev_test, &cdev_test_ops);
    cdev_add(&dev2.cdev_test, dev1.dev_num + 1, 1);

    dev2.class = class_create(THIS_MODULE, "test2");
    device_create(dev2.class, NULL, dev1.dev_num + 1, NULL, "test2");

    return 0;
}

static void usr_exit(void)
{
    
    
    unregister_chrdev_region(dev1.dev_num, 1);
    unregister_chrdev_region(dev1.dev_num + 1, 1);
    cdev_del(&dev1.cdev_test);
    cdev_del(&dev2.cdev_test);

    device_destroy(dev1.class, dev1.dev_num);
    device_destroy(dev2.class, dev1.dev_num + 1);
    class_destroy(dev1.class);
    class_destroy(dev2.class);
    printk("bye bye\n");
}

module_init(usr_init);
module_exit(usr_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

app.c(gcc app.c -o app.out)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    
    
    int fd1, fd2;
    char buf1[32] = {
    
    "niaho /dev/test1"};
    char buf2[32] = {
    
    "niaho /dev/test2"};
    fd1 = open("/dev/test1", O_RDWR); // 打开设备节点
    if (fd1 < 0)
    {
    
    
        perror("open error \n");
        return fd1;
    }
    write(fd1, buf1, sizeof(buf1));
    close(fd1);

    fd2 = open("/dev/test2", O_RDWR); // 打开设备节点
    if (fd2 < 0)
    {
    
    
        perror("open error \n");
        return fd2;
    }
    write(fd2, buf2, sizeof(buf2));
    close(fd2);
    return 0;
}

  1. sudo insmod file.ko

    [ 217.022060] alloc_chrdev_region success
    [ 217.022062] старший 240
    [ 217.022062] младший 0
    [ 217.024609] старший 240
    [ 217.024610] младший 1

  2. sudo ./app.out

    fengzc@ubuntu:~/study/drivers_test/08_private_data_test$ ls -l /sys/class/test*
    /sys/class/test1:
    всего 0
    lrwxrwxrwx. 1 root root 0 29 марта 22:03 test1 -> …/…/devices/virtual/test1/test1

    /sys/class/test2:
    всего 0
    lrwxrwxrwx. 1 root root 0 29 марта 22:03 test2 -> …/…/devices/virtual/test2/test2

    fengzc@ubuntu:~/study/drivers_test/08_private_data_test$ ls -l /dev/test*
    crw-------. 1 root root 240 , 0 29 марта 22:02 / dev / test1
    crw ------- . 1 корень корень 240 , 1 29 марта 22:02 / dev / test2

    [ 271.300678] Это cdev_test_open
    [ 271.300681] kbuf это niaho /dev/test1
    [ 271.300685] Это cdev_test_open
    [ 271.300686] kbuf это niaho /dev/test2

Разные драйверы устройств

Описание разного оборудования

В Linux, различные устройства, которые не могут быть классифицированы, определяются как разные устройства. По сравнению с символьным устройством, основное устройство разного устройства имеет фиксированное значение 10, и символьное устройство будет потреблять основной номер устройства независимо от того, распределяется ли номер устройства динамически или статически, что является пустой тратой основного номера устройства. Разные устройства будут вызывать class_create()и device_create()автоматически создавать узлы устройств. Таким образом, различное снаряжение можно рассматривать как разновидность снаряжения персонажа. Но это снижает сложность и сохраняет больший номер устройства, чем символьное устройство, которое мы обычно пишем.

Разные устройства miscdeviceописываются с помощью структур, определенных в include/linux/miscdevice.hфайлах. Следующее:

struct miscdevice  {
    
    
	int minor;
	const char *name;
	const struct file_operations *fops;
	struct list_head list;
	struct device *parent;
	struct device *this_device;
	const struct attribute_group **groups;
	const char *nodename;
	umode_t mode;
};

Среди них второстепенный номер устройства minorобычно использует макрос MISC_DYNAMIC_MINOR, что означает, что второстепенный номер устройства назначается автоматически. Разные устройства в основном полагаются на младшие номера для управления различными разными устройствами.

Регистрация и удаление различных устройств

Зарегистрируйте различные misc_registerфункции использования устройства, удалите различные функции использования устройства misc_deregister. Обе функции определены в include\linux\miscdevice.hфайле.

функция misc_register

Прототип функции:int misc_register(struct miscdevice *misc)

Параметр: указатель структуры разного оборудования

Возвращаемое значение: в случае успеха возвращается 0, а в случае неудачи возвращается отрицательное число.

функция misc_deregister

Прототип функции:int misc_deregister(struct miscdevice *misc)

Параметр: указатель структуры разного оборудования

Возвращаемое значение: в случае успеха возвращается 0, а в случае неудачи возвращается отрицательное число.

пример

usr.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>

static int cdev_test_open(struct inode *inode, struct file *file)
{
    
    
    printk("This is a cdev_test_open\n");
    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    
    
    return 0;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    
    
    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    
    
    return 0;
}

struct file_operations cdev_test_ops = {
    
    
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
};

struct miscdevice misc_dev = {
    
    
    .minor = MISC_DYNAMIC_MINOR,
    .name = "test",
    .fops = &cdev_test_ops,
};

static int usr_init(void)
{
    
    
    int ret;
    ret = misc_register(&misc_dev);
    if (ret < 0)
    {
    
    
        printk("misc_register failed");
        return -1;
    }
    return 0;
}

static void usr_exit(void)
{
    
    
    misc_deregister(&misc_dev);
    printk("bye bye\n");
}

module_init(usr_init);
module_exit(usr_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

fengzc@ubuntu:~/study/drivers_test/09_misc$ ls -l /sys/class/misc/test
lrwxrwxrwx. 1 root root 0 29 марта 22:25 /sys/class/misc/test -> …/…/devices/virtual/misc/test
fengzc@ubuntu:~/study/drivers_test/09_misc$ ls -l /dev/test
crw -------. 1 корень корень 10, 56 29 марта 22:25 /dev/test
fengzc@ubuntu:~/study/drivers_test/09_misc$

Обработка ошибок драйвера Linux

При использовании gotoоператоров для обработки ошибок следуйте принципу «первым пришел, последним вышел».

изображение-20230330133156826

Адрес (64-битная система) зарезервирован в ядре 0xffffffffff000~0xfffffffffffдля записи кода ошибки, и этот адрес Linuxсоответствует коду ошибки один к одному. Основные коды ошибок ядра сохраняются в errno-base.hфайле.

/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _ASM_GENERIC_ERRNO_BASE_H
#define _ASM_GENERIC_ERRNO_BASE_H

#define EPERM            1      /* Operation not permitted */
#define ENOENT           2      /* No such file or directory */
#define ESRCH            3      /* No such process */
#define EINTR            4      /* Interrupted system call */
#define EIO              5      /* I/O error */
#define ENXIO            6      /* No such device or address */
#define E2BIG            7      /* Argument list too long */
#define ENOEXEC          8      /* Exec format error */
#define EBADF            9      /* Bad file number */
#define ECHILD          10      /* No child processes */
#define EAGAIN          11      /* Try again */
#define ENOMEM          12      /* Out of memory */
#define EACCES          13      /* Permission denied */
#define EFAULT          14      /* Bad address */
#define ENOTBLK         15      /* Block device required */
#define EBUSY           16      /* Device or resource busy */
#define EEXIST          17      /* File exists */
#define EXDEV           18      /* Cross-device link */
#define ENODEV          19      /* No such device */
#define ENOTDIR         20      /* Not a directory */
#define EISDIR          21      /* Is a directory */
#define EINVAL          22      /* Invalid argument */
#define ENFILE          23      /* File table overflow */
#define EMFILE          24      /* Too many open files */
#define ENOTTY          25      /* Not a typewriter */
#define ETXTBSY         26      /* Text file busy */
#define EFBIG           27      /* File too large */
#define ENOSPC          28      /* No space left on device */
#define ESPIPE          29      /* Illegal seek */
#define EROFS           30      /* Read-only file system */
#define EMLINK          31      /* Too many links */
#define EPIPE           32      /* Broken pipe */
#define EDOM            33      /* Math argument out of domain of func */
#define ERANGE          34      /* Math result not representable */

#endif

Функции в ядре часто возвращают указатели.Если ядро ​​возвращает указатель, возможны три ситуации: допустимые указатели , нулевые указатели и недопустимые указатели .

Как определить, является ли указатель, возвращаемый функцией, действительным адресом или кодом ошибки?Используйте функцию, IS_ERRчтобы проверить возвращаемое значение функции.Если адрес попадает в 0xffffffffooo~oxfffffffffffдиапазон (64-битная система), это означает, что функция не удалось выполнить, IS_ERRчто равно 1. В то же время адрес ошибки, возвращаемый функцией, соответствует номеру linuxошибки. Если вы хотите узнать, какой код ошибки имеет этот адрес, используйте PTR_ERRфункцию для его преобразования. где функция IS_ERRсуммы PTR_ERRопределена в errno.h.

Пример:

if(IS_ERR(dev.device)){
    
    
    ret = PTR_ERR(dev.device)
}

пример

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/errno.h>

struct device_test
{
    
    
    dev_t dev_num;
    struct cdev cdev_test;

    int major;
    int minor;

    struct class *class;
    struct device *device;

    char kbuf[32];
};

struct device_test dev1;

static int cdev_test_open(struct inode *inode, struct file *file)
{
    
    
    file->private_data = &dev1;
    printk("This is a cdev_test_open\n");
    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    
    
    struct device_test *dev = (struct device_test *)file->private_data;
    if (copy_to_user(buf, dev->kbuf, strlen(dev->kbuf)) != 0)
    {
    
    
        printk("copy_to_user is error");
        return -EFAULT;
    }
    return 0;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    
    
    struct device_test *dev = (struct device_test *)file->private_data;
    if (copy_from_user(dev->kbuf, buf, size) != 0)
    {
    
    
        printk("copy_from_user is error");
        return -EFAULT;
    }
    printk("kbuf is %s\n", dev->kbuf);
    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    
    
    return 0;
}

struct file_operations cdev_test_ops = {
    
    
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
};

static int usr_init(void)
{
    
    
    int ret;
    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name");
    if (ret < 0)
    {
    
    
        goto err_chrdev;
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    dev1.major = MAJOR(dev1.dev_num);
    dev1.minor = MINOR(dev1.dev_num);
    printk("major is %d\n", dev1.major);
    printk("minor is %d\n", dev1.minor);

    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_ops);
    ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
    if (ret < 0)
    {
    
    
        goto err_chr_add;
    }

    dev1.class = class_create(THIS_MODULE, "test");
    if(IS_ERR(dev1.class)){
    
    
        goto err_class_create;
    }
    device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
    if(IS_ERR(dev1.device)){
    
    
        goto err_class_device;
    }

    return 0;
err_class_device:
    class_destroy(dev1.class);  

err_class_create:
    cdev_del(&dev1.cdev_test);

err_chr_add:
    unregister_chrdev_region(dev1.dev_num, 1);

err_chrdev:
    return ret;
}

static void usr_exit(void)
{
    
    
    unregister_chrdev_region(dev1.dev_num, 1);
    cdev_del(&dev1.cdev_test);

    device_destroy(dev1.class, dev1.dev_num);
    class_destroy(dev1.class);
    printk("bye bye\n");
}

module_init(usr_init);
module_exit(usr_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

рекомендация

отblog.csdn.net/weixin_45767368/article/details/129857437