> 文章列表 > (Linux驱动入门)字符设备

(Linux驱动入门)字符设备

(Linux驱动入门)字符设备

一、设备相关概念

1.1 设备号

内核中通过类型dev_t来描述设备号,其实质是unsigned int 32位整数,其中高12位为主设备号低20位为次设备号设备号也是一种资源,当我们需要时可以调用函数去申请。

​​​​​​​int register_chrdev_region(dev_t from, unsigned count, const char *name)

这是Linux内核中注册字符设备驱动的函数之一,它的作用是在内核中申请一段设备号,并将其与设备驱动程序进行绑定。具体来说,它的参数含义如下:

  • from:设备号的起始值,通常为0。
  • count:需要注册的设备号数量。
  • name:设备名称,用于在 /proc/devices 文件中显示设备的名字。

函数执行成功时,会返回0,否则返回一个负数错误码。

1.2 设备信息的描述

#include <linux/cdev.h>struct cdev {struct kobject kobj;           // 对象,用于实现 sysfs 文件系统接口struct module *owner;         // 指向模块对象的指针,用于记录模块的引用计数const struct file_operations *ops;  // 指向字符设备驱动程序提供的操作函数集合的指针struct list_head list;        // 用于链接同一设备号下的所有设备结构体的链表dev_t dev;                    // 设备的主、次设备号unsigned int count;           // 要注册的设备号的数量
};

1.3 设备行为的描述

#include <linux/fs.h>struct file_operations {struct module *owner;   // 指向模块对象的指针,用于记录模块的引用计数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 *);  // 实现写操作函数int (*open) (struct inode *, struct file *);  // 实现设备打开函数int (*release) (struct inode *, struct file *);  // 实现设备关闭函数long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);  // 实现设备控制函数int (*mmap) (struct file *, struct vm_area_struct *);  // 实现设备内存映射函数int (*flush) (struct file *, fl_owner_t id);  // 实现设备缓冲刷新函数int (*fsync) (struct file *, loff_t, loff_t, int datasync);  // 实现设备同步函数
};

struct file_operations 中的函数指针成员包括:

  • llseek:实现文件偏移量设置函数。
  • read:实现读操作函数。
  • write:实现写操作函数。
  • open:实现设备打开函数。
  • release:实现设备关闭函数。
  • unlocked_ioctl:实现设备控制函数。
  • mmap:实现设备内存映射函数。
  • flush:实现设备缓冲刷新函数。
  • fsync:实现设备同步函数。

这些函数指针中的大部分都是可选的,根据实际需求进行选择实现。

二、驱动的使用

insmod和rmmod是Linux系统中用于安装和卸载内核模块的命令,而mknod是用于创建设备节点的命令。

2.1 insmod

  1. 使用gcc编译内核模块源代码,生成.ko文件。

  2. 在终端中输入insmod  **.ko 命令,加载.ko文件。

  3. 内核检查模块符号表,如果符号表正确,则会调用模块中的init函数进行初始化。

  4. init函数会进行设备的初始化,包括申请设备号、创建字符设备结构体、初始化设备、注册设备等。

  5. 通过调用cdev_add函数将字符设备添加到系统中。

  6. 创建设备节点,以便应用程序可以使用设备。

2.2 rmmod

  1. 在终端中输入rmmod  **.ko 命令,卸载模块。

  2. 内核会调用模块中的exit函数,对设备进行资源的释放,如注销字符设备、释放设备号等。

  3. 调用cdev_del函数将字符设备从系统中删除。

  4. 删除设备节点,以便应用程序无法使用设备。

2.3 mknod

//mknod [选项] <文件名> <文件类型> <主设备号> <次设备号>mknod /dev/testchar       c        232        0
  1. 在终端中输入mknod命令,创建设备节点。

  2. 使用mknod函数创建设备节点,需要指定设备的名称、设备类型、主设备号、次设备号等参数。

  3. 创建成功后,应用程序可以通过打开设备节点访问设备。

2.4 流程图

来源:05-字符设备驱动(三):驱动的测试以及驱动的Makefile_哔哩哔哩_bilibili

2.5 Makfile文件

ifneq ($(KERNELRELEASE),)   如果KERNELRELEASE变量已定义,即当前为内核构建过程,执行以下代码
obj-m := charDev.o   编译模块charDev为目标文件obj文件,即生成charDev.o
else 
PWD := $(shell pwd)   定义变量PWD为当前目录的绝对路径
KDIR:=  / lib/ modules/ 'uname -r' / build  定义变量KDIR为内核源码目录路径
all:   
          make -c$(KDIR)][M=$(PWD)   在内核源码目录下执行make,参数为M=$(PWD),表示生成的目标文件存放在当前目录
clean:  
          rm -rf *.o *.ko *.mod.c *.symvers .c~s~ 
 删除编译生成的目标文件
endif 

三、通过驱动ioctl 点灯

ioctl函数用于在用户态和内核态之间传递控制命令,通常用于设备驱动中实现设备的特定操作,也可以用于进程间通信和内核调试等场景。

int ioctl(int fd, unsigned long request, ...);

fd表示要进行控制命令的文件描述符;request表示控制命令的编号,需要设备驱动实现者自己定义,通常使用宏定义的方式定义在头文件中;最后一个可选参数则是控制命令需要的参数,如果没有参数则传递NULL

可通过访问 cat /proc/ioports 来获取设备当前的IO端口号