【Linux驱动】Linux杂项设备驱动

本文介绍了Linux设备分类,重点讲解了字符设备和杂项设备的驱动,包括它们的抽象结构(如cdev和file_operations),以及如何通过miscdevice结构体进行设备驱动的注册和注销。文章还提供了Linux杂项设备驱动的示例,展示了如何创建和管理这些设备。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

00. 目录

01. Linux设备分类

Linux是文件型系统,所有硬件都会在对应的目录(/dev)下面用相应的文件表示。 在windows系统中,设备大家很好理解,像硬盘,磁盘指的是实实在在硬件。 而在文件系统的linux下面,都有对于文件与这些设备关联的,访问这些文件就可以访问实际硬件。 像访问文件那样去操作硬件设备,一切都会简单很多,不需要再调用以前com,prt等接口了。 直接读文件,写文件就可以向设备发送、接收数据。 按照读写存储数据方式,我们可以把设备分为以下几种:字符设备、块设备和网络设备。

字符设备:指应用程序按字节/字符来读写数据的设备。 这些设备节点通常为传真、虚拟终端和串口调制解调器、键盘之类设备提供流通信服务, 它通常不支持随机存取数据。字符设备在实现时,大多不使用缓存器。系统直接从设备读取/写入每一个字符。 例如,键盘这种设备提供的就是一个数据流,当你敲入“cnblogs”这个字 符串时, 键盘驱动程序会按照和输入完全相同的顺序返回这个由七个字符组成的数据流。它们是顺序的,先返回c,最后是s。

块设备:通常支持随机存取和寻址,并使用缓存器。 操作系统为输入输出分配了缓存以存储一块数据。当程序向设备发送了读取或者写入数据的请求时, 系统把数据中的每一个字符存储在适当的缓存中。当缓存被填满时,会采取适当的操作(把数据传走), 而后系统清空缓存。它与字符设备不同之处就是,是否支持随机存储。字符型是流形式,逐一存储。 典型的块设备有硬盘、SD卡、闪存等,应用程序可以寻址磁盘上的任何位置,并由此读取数据。 此外,数据的读写只能以块的倍数进行。

网络设备:是一种特殊设备,它并不存在于/dev下面,主要用于网络数据的收发。

Linux内核中处处体现面向对象的设计思想,为了统一形形色色的设备,Linux系统将设备分别抽象为struct cdev, struct block_device,struct net_devce三个对象,具体的设备都可以包含着三种对象从而继承和三种对象属性和操作, 并通过各自的对象添加到相应的驱动模型中,从而进行统一的管理和操作

字符设备驱动程序适合于大多数简单的硬件设备,而且比起块设备或网络驱动更加容易理解, 因此我们选择从字符设备开始,从最初的模仿,到慢慢熟悉,最终成长为驱动界的高手。

02. 字符设备抽象

Linux内核中将字符设备抽象成一个具体的数据结构(struct cdev),我们可以理解为字符设备对象, cdev记录了字符设备的相关信息(设备号、内核对象),字符设备的打开、读写、关闭等操作接口(file_operations), 在我们想要添加一个字符设备时,就是将这个对象注册到内核中,通过创建一个文件(设备节点)绑定对象的cdev, 当我们对这个文件进行读写操作时,就可以通过虚拟文件系统,在内核中找到这个对象及其操作接口,从而控制设备。

C语言中没有面向对象语言的继承的语法,但是我们可以通过结构体的包含来实现继承,这种抽象提取了设备的共性, 为上层提供了统一接口,使得管理和操作设备变得很容易。

在这里插入图片描述

在硬件层,我们可以通过查看硬件的原理图、芯片的数据手册,确定底层需要配置的寄存器,这类似于裸机开发。 将对底层寄存器的配置,读写操作放在文件操作接口里面,也就是实现file_operations结构体。

其次在驱动层,我们将文件操作接口注册到内核,内核通过内部散列表来登记记录主次设备号。

在文件系统层,新建一个文件绑定该文件操作接口,应用程序通过操作指定文件的文件操作接口来设置底层寄存器

实际上,在Linux上写驱动程序,都是做一些“填空题”。因为Linux给我们提供了一个基本的框架, 我们只需要按照这个框架来写驱动,内核就能很好的接收并且按我们所要求的那样工作。有句成语工欲善其事,必先利其器, 在理解这个框架之前我们得花点时间来学习字符设备驱动相关概念及数据结构。

03. Linux杂项设备驱动

杂项设备驱动是属于我们 linux 三大设备驱动的哪一项呢?由于linux 驱动倾向于分层设计,所以每个具体的设备都可以找到它归属的类型,从而可以套到它相应的架构里面去,我们只需要实现它最底层的那部分。但是也有部分字符设备,确实不知道它属于哪种类型,我们一般推荐大家采用 miscdevice 的框架结构。misc 的意思是混合的杂项的,所以 misc 设备驱动也叫做杂项设备驱动,当我们板子上的某个设备没有办法分类时,就可以用 misc 设备驱动。它的注册跟使用比较的简单,所以比较适用于功能简单的设备。正因为简单,所以它通常嵌套在 platform 总线驱动中,配合总线驱动达到更复杂,多功能的效果。杂项设备是字符设备的一种,杂项设备可以自动生成设备节点。

在学习 misc 设备驱动之前,先来了解几个基础概念。

概念1 设备节点

我们可以启动我们的开发板,进入到 dev 目录下,dev 目录下全部都是生成的设备节点,如下图所示:

deng@local:~/a72/x3399/kernel$ ls /dev/
autofs           disk       hwrng    loop3         mqueue  rtc0      stdin   tty17  tty28  tty39  tty5   tty60      ttyS12  ttyS23  ttyS6        vcs2   vcsu         vmci
block            dma_heap   initctl  loop4         net     sda       stdout  tty18  tty29  tty4   tty50  tty61      ttyS13  ttyS24  ttyS7        vcs3   vcsu1        vsock
bsg              dmmidi     input    loop5         null    sda1      tty     tty19  tty3   tty40  tty51  tty62      ttyS14  ttyS25  ttyS8        vcs4   vcsu2        zero
btrfs-control    dri        kmsg     loop6         nvram   sda2      tty0    tty2   tty30  tty41  tty52  tty63      ttyS15  ttyS26  ttyS9        vcs5   vcsu3        zfs
bus              ecryptfs   log      loop7         port    sda3      tty1    tty20  tty31  tty42  tty53  tty7       ttyS16  ttyS27  udmabuf      vcs6   vcsu4
cdrom            fb0        loop0    loop8         ppp     sg0       tty10   tty21  tty32  tty43  tty54  tty8       ttyS17  ttyS28  uhid         vcsa   vcsu5
char             fd         loop1    loop9         psaux   sg1       tty11   tty22  tty33  tty44  tty55  tty9       ttyS18  ttyS29  uinput       vcsa1  vcsu6
console          full       loop10   loop-control  ptmx    shm       tty12   tty23  tty34  tty45  tty56  ttyprintk  ttyS19  ttyS3   urandom      vcsa2  vfio
core             fuse       loop11   mapper        pts     snapshot  tty13   tty24  tty35  tty46  tty57  ttyS0      ttyS2   ttyS30  userfaultfd  vcsa3  vga_arbiter
cpu              hidraw0    loop12   mcelog        random  snd       tty14   tty25  tty36  tty47  tty58  ttyS1      ttyS20  ttyS31  userio       vcsa4  vhci
cpu_dma_latency  hpet       loop13   mem           rfkill  sr0       tty15   tty26  tty37  tty48  tty59  ttyS10     ttyS21  ttyS4   vcs          vcsa5  vhost-net
cuse             hugepages  loop2    midi          rtc     stderr    tty16   tty27  tty38  tty49  tty6   ttyS11     ttyS22  ttyS5   vcs1         vcsa6  vhost-vsock
deng@local:~/a72/x3399/kernel$ 

我们的系统里面有很多杂项设备。我们可以输入以下命令来查看,如下图所示:

deng@local:~/a72/x3399/kernel$ cat /proc/misc 
121 vsock
122 vmci
235 autofs
123 cpu_dma_latency
227 mcelog
236 device-mapper
223 uinput
  1 psaux
200 tun
124 udmabuf
237 loop-control
228 hpet
229 fuse
125 ecryptfs
126 userfaultfd
231 snapshot
183 hw_random
127 vga_arbiter
242 rfkill
deng@local:~/a72/x3399/kernel$ 

概念2 杂项设备的优点

杂项设备除了比字符设备代码简单,还有别的区别吗?所有的 misc 设备驱动的主设备号都为 10,不同的设备使用不同的从设备号。主设设备号相同就可以节省内核的资源,在内核中大概可以找到 200 多处使用 miscdevice 框架结构的驱动。

概念3 主设备号和次设备号的概念

设备号包含主设备号和次设备号,设备号是计算机识别设备的一种方式,主设备号相同的就被视为同一类设备,主设备号在 Linux 系统里面是唯一的,次设备号不一定唯一。主设备号可以比做成电话号码的区号。比如北京的区号是 010,次设备号可以比作成电话号码。

主设备号可以通过以下命令来查看,前面的数字就是主设备号,如下图所示:

deng@local:~/a72/x3399/kernel$ cat /proc/devices 
Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  5 ttyprintk
  6 lp
  7 vcs
 10 misc
 13 input
 14 sound/midi
 14 sound/dmmidi
 21 sg
 29 fb
 89 i2c
 99 ppdev
108 ppp
116 alsa
128 ptm
136 pts
180 usb
189 usb_device
202 cpu/msr
204 ttyMAX
226 drm
241 hidraw
242 ttyDBC
243 bsg
244 watchdog
245 remoteproc
246 ptp
247 pps
248 rtc
249 dma_heap
250 dax
251 dimmctl
252 ndctl
253 tpm
254 gpiochip
261 accel

Block devices:
  7 loop
  8 sd
  9 md
 11 sr
 65 sd
 66 sd
 67 sd
 68 sd
 69 sd
 70 sd
 71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
252 device-mapper
253 virtblk
254 mdp
259 blkext
deng@local:~/a72/x3399/kernel$ 

04. Linux杂项设备相关类型和API

4.1 miscdevice 结构体

misc 设备用 miscdevice 结构体表示,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;
};

当我们创建一个 misc 设备的 miscdevice 结构体时,需要我们指定 minor、name 和 fops 这三个成员变量。minor 表示次设备号,需要用户设置,在 Linux 内核中有一些预定义的 misc 设备的次设备号,定义在 include/linux/miscdevice.h 文件中,如下所示:

/*
 *  These allocations are managed by device@lanana.org. If you use an
 *  entry that is not in assigned your entry may well be moved and
 *  reassigned, or set dynamic if a fixed value is not justified.
 */

#define PSMOUSE_MINOR       1
#define MS_BUSMOUSE_MINOR   2   /* unused */
#define ATIXL_BUSMOUSE_MINOR    3   /* unused */
/*#define AMIGAMOUSE_MINOR  4   FIXME OBSOLETE */
#define ATARIMOUSE_MINOR    5   /* unused */
#define SUN_MOUSE_MINOR     6   /* unused */
#define APOLLO_MOUSE_MINOR  7   /* unused */
#define PC110PAD_MINOR      9   /* unused */
/*#define ADB_MOUSE_MINOR   10  FIXME OBSOLETE */
#define WATCHDOG_MINOR      130 /* Watchdog timer     */
#define TEMP_MINOR      131 /* Temperature Sensor */
#define RTC_MINOR       135
#define EFI_RTC_MINOR       136 /* EFI Time services */
#define VHCI_MINOR      137
#define SUN_OPENPROM_MINOR  139
#define DMAPI_MINOR     140 /* unused */
#define NVRAM_MINOR     144
#define SGI_MMTIMER     153
#define STORE_QUEUE_MINOR   155 /* unused */
#define I2O_MINOR       166
#define MICROCODE_MINOR     184
#define VFIO_MINOR      196
#define TUN_MINOR       200
#define CUSE_MINOR      203
#define MWAVE_MINOR     219 /* ACP/Mwave Modem */
#define MPT_MINOR       220
#define MPT2SAS_MINOR       221
#define MPT3SAS_MINOR       222
#define UINPUT_MINOR        223
#define MISC_MCELOG_MINOR   227
#define HPET_MINOR      228
#define FUSE_MINOR      229
#define KVM_MINOR       232
#define BTRFS_MINOR     234
#define AUTOFS_MINOR        235
#define MAPPER_CTRL_MINOR   236
#define LOOP_CTRL_MINOR     237
#define VHOST_NET_MINOR     238
#define UHID_MINOR      239
#define USERIO_MINOR        240
#define MISC_DYNAMIC_MINOR  255

我们设置子设备号时要注意不要重复使用其他设备的子设备号。可以从这些预定义的子设备号中选择一个,也可以自定义。

name 就是这个 misc 设备的名字,当设备注册成功后,会在/dev 目录下自动生成一个名为 name 的设备文件。fops 就是这个 misc 设备的操作集合。

4.2 misc_register

/**
 *  misc_register   -   register a miscellaneous device
 *  @misc: device structure
 *
 *  Register a miscellaneous device with the kernel. If the minor
 *  number is set to %MISC_DYNAMIC_MINOR a minor number is assigned
 *  and placed in the minor field of the structure. For other cases
 *  the minor number requested is used.
 *
 *  The structure passed is linked into the kernel and may not be
 *  destroyed until it has been unregistered. By default, an open()
 *  syscall to the device sets file->private_data to point to the
 *  structure. Drivers don't need open in fops for this.
 *
 *  A zero is returned on success and a negative errno code for
 *  failure.
 */
    
int misc_register(struct miscdevice * misc)
功能:
    注册misc设备
参数:
    misc 之前创建好的miscdevice结构体
返回值:
    成功返回0
    失败返回负数
    

4.3 misc_deregister

/**
 *  misc_deregister - unregister a miscellaneous device
 *  @misc: device to unregister
 *
 *  Unregister a miscellaneous device that was previously
 *  successfully registered with misc_register().
 */

void misc_deregister(struct miscdevice *misc)
功能:
    注销misc设备
参数:
    misc 之前创建好的miscdevice结构体
返回值:

4.4 file_operations

在 miscdevice 结构体的第四行,它指向了一个 file_operation 的结构体。file_operations 文件操作集在定义在 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 *);
    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
    int (*iterate) (struct file *, struct dir_context *);
    unsigned int (*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);
    int (*mmap) (struct file *, struct vm_area_struct *);
    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 (*aio_fsync) (struct kiocb *, 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 (*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
};
  • llseek: 用于修改文件的当前读写位置,并返回偏移后的位置。参数file传入了对应的文件指针,我们可以看到以上代码中所有的函数都有该形参,通常用于读取文件的信息,如文件类型、读写权限;参数loff_t指定偏移量的大小;参数int是用于指定新位置指定成从文件的某个位置进行偏移,SEEK_SET表示从文件起始处开始偏移;SEEK_CUR表示从当前位置开始偏移;SEEK_END表示从文件结尾开始偏移。
  • read: 用于读取设备中的数据,并返回成功读取的字节数。该函数指针被设置为NULL时,会导致系统调用read函数报错,提示“非法参数”。该函数有三个参数:file类型指针变量,charuser类型的数据缓冲区,user用于修饰变量,表明该变量所在的地址空间是用户空间的。内核模块不能直接使用该数据,需要使用copy_to_user函数来进行操作。size_t类型变量指定读取的数据大小。
  • write: 用于向设备写入数据,并返回成功写入的字节数,write函数的参数用法与read函数类似,不过在访问__user修饰的数据缓冲区,需要使用copy_from_user函数。
  • unlocked_ioctl: 提供设备执行相关控制命令的实现方法,它对应于应用程序的fcntl函数以及ioctl函数。在 kernel 3.0 中已经完全删除了 struct file_operations 中的 ioctl 函数指针。
  • open: 设备驱动第一个被执行的函数,一般用于硬件的初始化。如果该成员被设置为NULL,则表示这个设备的打开操作永远成功。
  • release: 当file结构体被释放时,将会调用该函数。与open函数相反,该函数可以用于释放

05. Linux杂项设备驱动示例

注册杂项设备有一个通用的思路和方法,这里给大家总结为三个步骤:

 填充 miscdevice 这个结构体

 填充 file_operations 这个结构体

 注册杂项设备并生生成设备节点。

test.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>

static struct file_operations misc_fops = {
    .owner = THIS_MODULE,
};

static struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "misc_dev",
    .fops = &misc_fops,
};

static int __init test_init(void)
{
    int ret = 0;

    printk("test_init.....\n");

    //注册杂项设备
    ret = misc_register(&misc_dev);
    if (0 != ret)
    {
        printk("misc_register failed.....\n");
        return 1;
    }
    printk("misc_register OK....\n");


    return 0;
}


static void __exit test_exit(void)
{
    //注销杂项设备
    misc_deregister(&misc_dev);

    printk("test_exit......\n");
}


module_init(test_init);
module_exit(test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jin.Deng");
MODULE_INFO(intree, "Y");

运行结果

[root@rk3399:/mnt/kernel/5th/1misc]# insmod test.ko
[10301.761151] test_init.....
[10301.764711] misc_register OK....
[root@rk3399:/mnt/kernel/5th/1misc]# ls /dev/misc_dev -l
crw------- 1 root root 10, 53 Apr 28 06:37 /dev/misc_dev
[root@rk3399:/mnt/kernel/5th/1misc]# 
[root@rk3399:/mnt/kernel/5th/1misc]# rmmod test
[10308.881889] test_exit......
[root@rk3399:/mnt/kernel/5th/1misc]# 
[root@rk3399:/mnt/kernel/5th/1misc]# ls -l /dev/misc_dev
ls: cannot access '/dev/misc_dev': No such file or directory
[root@rk3399:/mnt/kernel/5th/1misc]# 

06. 附录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沧海一笑-dj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值