Диаграмма архитектуры
Подать заявку на символьный номер устройства
Что такое номер устройства
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
-
Статическое распределение номеров устройств: сначала
cat /proc/devices
проверьте, какие основные номера устройств не заняты, например, если 220 не занято, затем пропуститеsudo insmod dev_t.ko major=220 minor=0
[795650.456421] старший 220
[795650.456423] второстепенный 0
[795650.456424
] -
Динамическое распределение номеров устройств: непосредственно через
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_add
cdev
Функция используется для добавления структуры в систему , то есть добавления символьного устройства
/**
* 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_del
cdev
Функция, используемая для удаления структуры из системы
/**
* 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
системный вызов и драйвер связаны.
структура 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
мы используем упрощенную версию mdev
yes . При использовании для сборки корневой файловой системы она будет создана автоматически .mdev
udev
busybox
busybox
mdev
функция 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;
}
-
sudo insmod file.ko
[ 372.600726] alloc_chrdev_region успешно
[ 372.600728] старший 240
[ 372.600728] младший 0 -
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
может выполняться любая инструкция, конечно, включая чтение данных с диска. Конкретный процесс заключается в том, чтобы сначала прочитать данные в пространство ядра, затем скопировать данные в пространство пользователя и переключиться из режима ядра в пользовательский режим. В этот момент приложение вернулось из системного вызова и получило нужные данные и может продолжить выполнение.
Поскольку процесс должен переключаться из пользовательского пространства в пространство ядра, чтобы использовать аппаратные ресурсы системы, существует три метода переключения: системный вызов , мягкое прерывание и аппаратное прерывание .
Обмен данными пространства пользователя и пространства ядра
Память в пространстве ядра и пользовательском пространстве не могут получить доступ друг к другу. Но многим бизнес-программам необходимо обмениваться данными с ядром, например, приложения используют 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;
}
-
sudo insmod file.ko
[ 217.022060] alloc_chrdev_region success
[ 217.022062] старший 240
[ 217.022062] младший 0
[ 217.024609] старший 240
[ 217.024610] младший 1 -
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/test2fengzc@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
операторов для обработки ошибок следуйте принципу «первым пришел, последним вышел».
Адрес (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");