字符设备或字符特殊文件如何工作?


22

我正在尝试了解字符特殊文件。从Wikipedia,我了解到这些文件为为一次传输一个字符的设备提供了“接口”。我的理解是系统以某种方式调用了字符设备,而不是直接调用设备驱动程序。但是文件如何提供此接口?它是翻译系统调用的可执行文件吗?有人可以解释怎么回事。

Answers:


19

它们实际上就是接口。通过“大”和“小”数字进行编码,它们提供了与内核的挂钩。

它们有两种形式(嗯,三种,但是命名管道目前不在此说明的范围之内):字符设备和块设备。

块设备往往是存储设备,能够缓冲输出并存储数据以供以后检索。

字符设备是诸如音频或图形卡之类的东西,或诸如键盘和鼠标之类的输入设备。

在每种情况下,当内核(在引导时或通过诸如udev的程序)加载正确的驱动程序时,它都会扫描各种总线,以查看该驱动程序处理的设备是否确实存在于系统上。如果是这样,它将设置一个设备来“监听”适当的主/副号码。

(例如,系统找到的第一个声卡的数字信号处理器的主/副号码对为14/3;第二对为14,35,依此类推。)

由udev决定创建一个/dev名称dsp为字符设备的条目,将其标记为major 14 minor 3。

(在明显较旧或最小占用空间的Linux版本中,/dev/可能不会动态加载,而仅静态包含所有可能的设备文件。)

然后,当用户空间程序尝试访问带有适当的主/次编号的标记为“字符特殊文件”的文件时(例如,您的音频播放器尝试将数字音频发送到/dev/dsp),内核知道该数据需要通过附加了主要/次要号码的驾驶员进行传输;据推测,驾驶员知道该如何处理。


1
1.那么主要/次要数字类似于端口?
bernie2436 2012年

2.因此,当程序访问任何文件时,内核读取这些特殊接口以了解程序是否应从特定设备获得中断?例如:如果程序打开了一个字文件,它会读取字符设备专用文件,以知道该程序应响应键盘输入吗?
bernie2436 2012年

1)有点。这是一个穷人的比喻,但它会做到的。
沙杜尔

2
2)您在那里缺少大约三,四层抽象。您打开的文本文件程序既不知道也不关心键盘设备是什么。与底层硬件的通信是通过终端仿真器(如果您处于控制台模式)或通过X事件层(如果您处于图形模式)进行的,这两者将侦听键盘和其他驱动器并决定什么(如果有的话)传递给程序。我在这里总结了一个相当复杂的多层系统。通常,您可以很好地阅读X Window系统。
沙杜尔

1
还请注意,在某些UN * X版本中,存在用于存储设备的特殊字符文件;对特殊文件的读取或写入将变成对设备上一系列块的读取或写入。(在新版本的FreeBSD,这些都是为存储设备特殊文件;还有无块特殊文件。)

10

每个文件,设备或其他文件,都在VFS中支持6种基本操作:

  1. 打开
  2. 寻求
  3. 告诉

此外,设备文件支持I / O控制,它允许前6个未涵盖的其他杂项操作。

对于特殊字符,由于它们支持流接口,因此未实现查找和告诉。也就是说,直接读取或写入内容(例如在外壳中使用重定向完成):

echo 'foo' > /dev/some/char
sed ... < /dev/some/char

6

最小的可运行file_operations示例

一旦看到一个最小的示例,一切就变得显而易见。

关键思想是:

  • file_operations 包含每个与文件相关的syscall的回调
  • mknod <path> c <major> <minor> 创建一个使用那些字符设备 file_operations
  • 对于动态分配设备编号(避免冲突的规范)的字符设备,请使用 cat /proc/devices

character_device.ko 内核模块:

#include <asm/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/errno.h> /* EFAULT */
#include <linux/fs.h> /* register_chrdev, unregister_chrdev */
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/printk.h> /* printk */
#include <uapi/linux/stat.h> /* S_IRUSR */

#define NAME "lkmc_character_device"

MODULE_LICENSE("GPL");

static int major;

static ssize_t read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
    size_t ret;
    char kbuf[] = {'a', 'b', 'c', 'd'};

    ret = 0;
    if (*off == 0) {
        if (copy_to_user(buf, kbuf, sizeof(kbuf))) {
            ret = -EFAULT;
        } else {
            ret = sizeof(kbuf);
            *off = 1;
        }
    }
    return ret;
}

static const struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = read,
};

static int myinit(void)
{
    major = register_chrdev(0, NAME, &fops);
    return 0;
}

static void myexit(void)
{
    unregister_chrdev(major, NAME);
}

module_init(myinit)
module_exit(myexit)

Userland测试程序:

insmod /character_device.ko
dev="lkmc_character_device"
major="$(grep "$dev" /proc/devices | cut -d ' ' -f 1)"
mknod "/dev/$dev" c "$major" 0
cat /dev/lkmc_character_device
# => abcd
rm /dev/lkmc_character_device
rmmod character_device

GitHub QEMU + Buildroot上游并带有样板以实际运行它:

更复杂的示例:


超级有帮助,谢谢!只是一个问题,这到底是做什么的*off = 1;,为什么要设置为1
SilverSlash

1
@SilverSlash该值通过多次read调用传递给同一open(文件描述符。驾驶员可以用它做任何想做的事情。通常的语义是包含读取的字节数。但是,在此示例中,我们只有一个更简单的语义:0对于第一次读取,1在第一次读取之后。尝试运行它,并放置一个printk或GDB步骤对其进行调试。
Ciro Santilli新疆改造中心法轮功六四事件

4

“一次使用字符”是一个用词不当的说法(字符设备不一定支持搜寻和讲述的想法也是如此)。实际上,“一次阻止”(即严格面向记录的磁带,例如磁带驱动器*)设备必须是字符设备。因此,字符设备必须一定是不可搜索的想法-字符设备驱动程序定义了一个完整的file_operations结构,该结构可以根据设备是否支持该操作自由定义。大多数人认为的字符设备是null,urandom,TTY设备,声卡,鼠标等,由于这些设备的具体特征,它们都是无法找到的,但是/ dev / vcs,/ dev / fb0和/ dev / kmem也是字符设备,它们都是可搜索的。

正如我所提到的,字符设备驱动程序定义了一个file_operations结构,该结构具有某人可能要在文件上调用的所有操作(查找,读取,写入,ioctl等)的函数指针,并且在相应的系统调用时,每个调用一次在打开该设备文件的情况下执行。因此,读写操作可以使用其参数来做任何想要的事情-它可以拒绝接受太大的写操作,或者只写适合的写操作。它只能读取对应于一条记录的数据,而不能读取整个请求的字节数。

那么,什么是块设备?基本上,块设备是磁盘驱动器。没有其他类型的设备(虚拟磁盘驱动器,如ramdisk和Loopback除外)是块设备。它们以字符设备没有的方式集成到I / O请求系统,文件系统层,缓冲区/缓存系统和虚拟内存系统中,即使您正在从用户进程访问例如/ dev / sda时。甚至页面提到的“原始设备” 也是字符设备

*某些UNIX系统实现了现在所说的“固定块模式”-使内核组和拆分I / O请求以与磁盘驱动器大致相同的方式适应已配置的块边界-作为块设备。“可变块模式”需要一种字符设备,当单个write(2)调用写入一个块,而单个read(2)调用返回一个块时,该字符设备保留了用户程序中的块边界。由于现在将模式切换实现为ioctl而不是单独的设备文件,因此将使用字符设备。可变记录磁带驱动器大多是“不可搜索的”,因为查找涉及计数许多记录而不是字节数,并且本机查找操作被实现为一个ioctl。


1

字符设备可以由内核模块(或内核本身)创建。创建设备后,创建者会提供指向实现处理标准调用(如打开,读取等)的函数的指针。然后,Linux内核会将这些函数与字符设备相关联,例如,当用户模式应用程序调用read()时字符设备文件上的函数,将导致系统调用,然后内核会将此调用路由到创建驱动程序时指定的读取函数。有一个一步一步如何创建一个字符设备在这里,您可以创建一个示例项目,并通过它使用调试器来了解处理器如何在设备对象被创建并调用时的步骤。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.