当前位置: 首页 > news >正文

七、Linux创建自己的proc文件

文章目录

  • 一、前言
  • 二、实现代码
    • 1.包含必要的头文件
    • 2.定义序列操作函数
      • 2.1.代码逐行解析
      • 2.2.总结
    • 3.绑定序列操作函数和文件操作函数
      • 3.1.代码逐行解析
      • 3.2.总结与关系图
    • 4.创建与销毁/proc文件
      • 4.1.代码逐行解析
      • 4.2.总结与调用时机
  • 三、编译和加载
    • 1.Makefile文件
    • 2.scull_load和scull_unload文件
    • 3.编译和加载模块
  • 四、验证模块
    • 1.确认模块已成功加载
    • 2.确认`/proc/scullseq`文件是否创建
    • 3.确认`/proc/scullseq`文件读取功能正常

一、前言

​ 内核模块通过 proc 文件系统向用户空间提供信息或接口是一种非常常见且相对简单的方法

可以实现以下需求

  • 提供运行时信息:让用户空间程序能够方便地读取模块的内部状态、统计信息、调试数据等。例如,一个驱动可以输出其中断次数、数据包计数等

  • 接收用户空间控制命令:允许用户空间程序通过写入文件来改变模块的行为、设置参数或触发特定操作。例如,开启/关闭调试模式、重置计数器等

二、实现代码

​ 下列代码基于博客 https://blog.csdn.net/weixin_51019352/article/details/151870691 的代码,可以参考其详细解释,在原先scull,c代码文件中新添加如下代码,在vim中粘贴文件记得使用粘贴模式:set paste

1.包含必要的头文件

#include <linux/proc_fs.h>
#include <linux/seq_file.h> // 用于安全地输出大量数据

2.定义序列操作函数

#ifdef SCULL_DEBUG
static void *scull_seq_start(struct seq_file *s, loff_t *pos)
{if (*pos >= scull_nr_devs)return NULL;return scull_devices + *pos;
}static void *scull_seq_next(struct seq_file *s, void *v, loff_t *pos)
{(*pos)++;if (*pos >= scull_nr_devs)return NULL;return scull_devices + *pos;
}static void scull_seq_stop(struct seq_file *s, void *v)
{}static int scull_seq_show(struct seq_file *s, void *v)
{struct scull_dev *dev = (struct scull_dev *) v;struct scull_qset *d;int i;seq_printf(s, "\nDevice %i: qset %i, q %i, sz %li\n",(int) (dev - scull_devices), dev->qset,dev->quantum, dev->size);for (d = dev->data; d; d = d->next) {seq_printf(s, "  item at %p, qset at %p\n", d, d->data);if (d->data && !d->next)for (i = 0; i < dev->qset; i++) {if (d->data[i]) {seq_printf(s, "    % 4i: %8p\n",i, d->data[i]);}}}return 0;
}

2.1.代码逐行解析

#ifdef SCULL_DEBUG

解释

​ 这是一个预处理器指令。它检查是否定义了宏 SCULL_DEBUG。只有在编译模块时定义了此宏(例如,通过 gcc -DSCULL_DEBUG 或在源文件开头使用 #define SCULL_DEBUG),#ifdef#endif 之间的代码才会被包含在编译过程中

static void *scull_seq_start(struct seq_file *s, loff_t *pos)

解释

  • static:限定该函数的作用域仅在当前文件内,避免与其他内核文件中的同名函数发生命名冲突
  • void *:该函数的返回类型是一个无类型指针(泛型指针)。它将返回一个指向要迭代的序列中某个元素的指针
  • struct seq_file *s:指向 seq_file 结构的指针。这个结构由内核管理,代表正在被读取的序列文件,内部包含了缓冲区、状态等信息
  • loff_t *pos:指向“长偏移量(long long)”类型的指针。它表示序列中的当前位置(一个整数索引)。函数会读取 *pos 的值来决定从哪里开始,也可能会修改 *pos 的值
	if (*pos >= scull_nr_devs)

解释

  • *pos:解引用 pos 指针,获取当前的迭代位置值
  • scull_nr_devs:一个全局变量,表示系统中共有多少个 scull 设备。
  • 整体:检查当前请求的位置是否已经超出或等于设备的总数。如果是,说明已经越界,没有更多设备可遍历
		return NULL;

解释

  • 整体:如果位置越界,则向 seq_file 框架返回 NULL,表示迭代已经结束,没有更多的数据项了
	return scull_devices + *pos;

解释

  • scull_devices:这是一个全局数组的起始地址,该数组包含了所有 scull_dev 设备结构
  • + *pos:这是一个指针运算。scull_devices + *pos 等价于 &scull_devices[*pos]
  • 整体:计算并返回指向第 *posscull 设备结构的指针。这个指针将被传递给后续的 _show_next 函数
static void *scull_seq_next(struct seq_file *s, void *v, loff_t *pos)

解释

  • static void *:同上,静态函数,返回一个泛型指针
  • struct seq_file *s:同上,指向序列文件结构的指针
  • void *v:指向前一个由 _start_next 返回的数据项指针(在这个例子中,就是指向 scull_dev 的指针)
  • loff_t *pos:指向当前位置的指针。这个函数必须修改这个值
	(*pos)++;

解释

  • (*pos):解引用 pos 指针,获取当前的位置值
  • ++:自增运算符。它将位置值加一,移动到序列中的下一个项目
  • 整体:将迭代位置推进到下一个设备索引
	if (*pos >= scull_nr_devs)

解释:与 _start 函数中的检查相同。判断新的位置是否已经越界

		return NULL;

解释:如果越界,返回 NULL 表示迭代结束

	return scull_devices + *pos;

解释:返回指向下一个(第 *pos 个)scull 设备结构的指针

static void scull_seq_stop(struct seq_file *s, void *v)

解释

  • void *v:指向最后被迭代的数据项的指针(通常是在 _start_next 中返回的最后一个有效指针,或者是 NULL
  • 整体:这个函数在迭代序列结束(无论是否完成)时被调用,用于执行任何必要的清理工作,例如释放锁或资源

解释:一个空行,没有任何操作。这表明对于 scull 设备,在迭代结束时没有任何需要特别清理的资源。锁的获取和释放在 _show 函数中完成

static int scull_seq_show(struct seq_file *s, void *v)

解释

  • struct seq_file *s:指向序列文件结构,所有输出都将通过它提供的函数(如 seq_printf)写入到这个结构内部的缓冲区中
  • void *v:指向当前需要被“展示”或格式化的数据项的指针。这个指针是之前由 _start_next 返回的(在这里是指向 scull_dev 的指针)
  • 整体:该函数的核心职责是将 v 所指向的数据内容,以人类可读的文本形式,输出到序列文件 s
	struct scull_dev *dev = (struct scull_dev *) v;

解释

  • struct scull_dev *dev:声明一个指向 scull_dev 结构的指针变量 dev
  • (struct scull_dev *) v:这是一个类型转换(强制类型转换)。它将泛型指针 v 转换为我们已知的、具体的 scull_dev 结构指针类型
	struct scull_qset *d;

解释:声明一个指向 scull_qset 结构的指针变量 dscull_qsetscull 设备用来管理内存数据的链表节点结构

	int i;

解释:声明一个整型变量 i,将在后面的循环中用作计数器

	seq_printf(s, "\nDevice %i: qset %i, q %i, sz %li\n", (int) (dev - scull_devices), dev->qset, dev->quantum, dev->size);

解释

  • seq_printf:这是 seq_file 接口提供的函数,类似于用户空间的 printf。它将格式化后的字符串输出到 seq_file 缓冲区 s 中,而不是标准输出

  • s:目标序列文件。

  • "\nDevice %i: qset %i, q %i, sz %li\n":格式字符串

    • \n:换行符
    • %i:整数格式化占位符(用于设备索引)
    • %i:整数格式化占位符(用于 qset 值,即每个量子集有多少个指针)
    • %i:整数格式化占位符(用于 quantum 值,即每个量子的大小)
    • %li:长整数格式化占位符(用于 size 值,即设备数据的总大小)
  • (int) (dev - scull_devices)

    • dev - scull_devices:这是一个指针减法运算。它计算当前设备指针 dev 与设备数组起始指针 scull_devices 之间的元素个数差,结果就是当前设备的索引号
    • (int):将结果强制转换为 int 类型
  • dev->qset:访问 dev 结构体的 qset 成员(每个量子集的指针数量)

  • dev->quantum:访问 dev 结构体的 quantum 成员(每个量子的大小)

  • dev->size:访问 dev 结构体的 size 成员(设备当前存储的数据总大小)

	for (d = dev->data; d; d = d->next) {

解释:用于遍历设备的量子集链表。

  • d = dev->data:初始化。将指针 d 指向设备的数据链表的头节点(dev->data
  • d:循环条件。只要 d 不是 NULL,循环就继续
  • d = d->next:循环步进。在每次迭代后,将 d 指向链表中的下一个节点(d->next
		seq_printf(s, "  item at %p, qset at %p\n", d, d->data);

解释:为每个量子集节点输出一行信息。

  • seq_printf(s, ...):再次使用格式化输出函数。
  • " item at %p, qset at %p\n":格式字符串。
    • %p:指针格式化占位符,用于输出地址。
  • d:第一个参数,对应第一个 %p,是当前量子集节点本身的地址
  • d->data:第二个参数,对应第二个 %p,是该节点中“量子指针数组”的起始地址
		if (d->data && !d->next)

解释

  • d->data:检查当前量子集节点的数据指针数组是否存在(非 NULL
  • !d->next:检查当前节点是否是链表中的最后一个节点(d->nextNULL! 取反后为真)
  • 整体:这个条件的意思是:“如果当前节点有数据数组,并且它是链表的最后一个节点,那么……”
			for (i = 0; i < dev->qset; i++) {

解释:一个内嵌的 for 循环。只有在满足外层 if 条件时才会执行

  • i = 0:初始化计数器 i 为 0
  • i < dev->qset:循环条件。i 要小于一个量子集中量子的数量(dev->qset
  • i++:每次迭代后计数器加一
				if (d->data[i])

解释

  • d->data[i]:访问当前量子集节点的量子指针数组的第 i 个元素
  • 整体:检查第 i 个指针是否有效(非 NULL)。只有有效的指针才会打印
					seq_printf(s, "    % 4i: %8p\n", i, d->data[i]);

解释

  • seq_printf(s, ...):再次输出

  • " % 4i: %8p\n":格式字符串

    • % 4i:一个宽度为4的整数格式化占位符。前面的空格表示正数前面用空格填充,负数用负号,这保证了数字的对齐
    • %8p:一个宽度为8的指针格式化占位符,用于输出指针地址,同样是为了对齐
  • i:第一个参数,对应 % 4i,是当前指针在数组中的索引

  • d->data[i]:第二个参数,对应 %8p,是第 i 个指针本身的值(它指向的内存地址)


2.2.总结

​ 这段代码定义了四个函数(start, next, stop, show),它们共同实现了 seq_file 接口。这个接口允许内核高效、安全地将一个复杂的数据结构(这里是 scull 设备的链表)逐步输出到用户空间(通过 /proc 文件)

  • _start_next 定义了如何遍历数据集合(按设备索引遍历设备数组)

  • _show 定义了如何格式化输出一个数据项(一个 scull 设备的所有信息),包括其内部链表

  • _stop 是一个清理钩子,这里什么也没有做

  • 所有输出都使用安全的 seq_printf 函数,由内核管理缓冲区,避免了传统 read_proc 方法中复杂的缓冲区管理和溢出问题

3.绑定序列操作函数和文件操作函数

static struct seq_operations scull_seq_ops = {.start = scull_seq_start,.next  = scull_seq_next,.stop  = scull_seq_stop,.show  = scull_seq_show
};
static int scull_proc_open(struct inode *inode, struct file *file)
{return seq_open(file, &scull_seq_ops);
}
static struct file_operations scull_proc_ops = {.owner   = THIS_MODULE,.open    = scull_proc_open,.read    = seq_read,.llseek  = seq_lseek,.release = seq_release
};

3.1.代码逐行解析

static struct seq_operations scull_seq_ops = 

解释

  • static:限定这个结构体变量的作用域仅在当前文件内
  • struct seq_operations:这是一个内核定义的结构体类型(在 <linux/seq_file.h> 中)。它充当一个“接口”或“契约”,包含了驱动必须实现的四个函数指针,用于控制序列的迭代和显示
static int scull_proc_open(struct inode *inode, struct file *file)

解释

  • static int:静态函数,返回一个整数(通常成功为0,失败为负的错误码)
  • struct inode *inode:指向内核内部表示文件的inode结构的指针。包含文件的元数据(如设备号、权限)
  • struct file *file:指向内核文件结构的指针,代表一个打开的文件实例。这个结构将被后续的读、写、定位等操作使用
  • 整体:这是为 /proc 文件定义的文件打开操作函数。当用户空间程序执行 open(“/proc/scullseq”, O_RDONLY) 时,内核最终会调用这个函数
	return seq_open(file, &scull_seq_ops);

解释

  • seq_open(file, &scull_seq_ops):这是一个内核提供的通用函数(在 seq_file.c 中实现)

    • &scull_seq_ops:取地址符,获取我们上面定义的 scull_seq_ops 结构体的地址
  • 整体seq_open() 函数执行以下关键操作:

    • 它分配并设置一个 struct seq_file 结构体,该结构体将管理与序列操作相关的所有状态信息

    • 它将我们提供的 scull_seq_ops 结构体存储在这个新分配的 seq_file

    • 它将 file->private_data 字段指向这个 seq_file 结构体。这样,后续的 seq_read, seq_lseek 等函数就能通过 file->private_data 找到 seq_file,进而找到scull_seq_ops

static struct file_operations scull_proc_ops = {

解释

  • struct file_operations:这是内核中极其重要的一个结构体类型(在 <linux/fs.h> 中定义)。它包含了一组函数指针,这些指针定义了可以对文件执行的所有操作(VFS 接口)。驱动或内核模块通过实现这个结构体的成员来定义其文件的行为
	.owner   = THIS_MODULE,c

解释

  • .ownerfile_operations 的成员。这是一个指向模块拥有者的指针
  • THIS_MODULE:一个内核宏,它扩展为一个指向当前正在编译的模块结构(struct module)的指针
  • 重要性:设置 .owner 字段是至关重要的。它确保了只要文件仍然打开着(即有用户空间程序在使用这个 /proc 文件),内核就不会允许卸载这个模块。这防止了在文件操作还在进行时模块被意外移除,从而避免内核崩溃
	.open    = scull_proc_open,

解释

  • .openfile_operations 的成员。函数指针,指向文件打开操作
  • scull_proc_open:我们上面定义的函数的地址。这是我们提供的唯一一个自定义的文件操作函数
	.read    = seq_read,

解释

  • .readfile_operations 的成员。函数指针,指向文件读取操作
  • seq_read:这是 seq_file 框架提供的通用读取函数的地址。我们不需要自己实现 read
  • 工作原理:当用户空间调用 read() 时,seq_read 会被调用。它通过 file->private_data 找到 seq_file 结构体和我们的 scull_seq_ops。然后它自动调用我们的 _start, _next, _show, _stop 函数来逐步生成数据,并处理所有复杂的缓冲区管理和分页逻辑
	.llseek  = seq_lseek,

解释

  • .llseekfile_operations 的成员。函数指针,指向文件定位(lseek)操作
  • seq_lseek:这是 seq_file 框架提供的通用定位函数的地址。我们不需要自己实现 llseek
  • 功能:它实现了对序列文件的随机访问。用户空间程序可以使用 lseek() 来跳转到文件的不同位置,seq_lseek 会高效地处理这些请求,可能通过调用我们的 _start_next 函数来快速跳过前面的项
	.release = seq_release

解释

  • .release:当文件描述符的最后一个引用被关闭时调用
  • seq_release:这是 seq_file 框架提供的通用释放函数的地址。我们不需要自己实现 release
  • 功能:它负责释放在 scull_proc_open 中由 seq_open 分配的所有资源(主要是那个 seq_file 结构体)

3.2.总结与关系图

​ 这段代码完成了从“序列操作”到“文件操作”的桥接:

  • struct seq_operations:定义了数据如何被遍历和显示(驱动实现逻辑)

  • seq_open:在打开文件时,将 seq_operationsfile 关联起来(内核提供的粘合函数)

  • struct file_operations:定义了文件的行为.open, .read, .llseek, .release)。除了 .open 是我们自定义的(用于调用 seq_open),其他所有操作都直接使用 seq_file 框架提供的现成、强大的通用函数

关系流程如下:

用户调用 open() -> 内核调用 scull_proc_open()|vscull_proc_open() 调用 seq_open()|| (关联)vfile->private_data -> struct seq_file -> struct seq_operations -> (scull_seq_start, ...)||
用户调用 read() -> 内核调用 seq_read()|vseq_read() 使用 file->private_data 找到 seq_file 和 seq_operations|v循环调用: start -> show -> next -> show -> next -> ... -> stop|v数据通过 seq_printf 输出到用户缓冲区

4.创建与销毁/proc文件

static void scull_create_proc(void)
{struct proc_dir_entry *entry;entry = create_proc_entry("scullseq", 0, NULL);if (entry)entry->proc_fops = &scull_proc_ops;
}static void scull_remove_proc(void)
{remove_proc_entry("scullseq", NULL);
}
#endif

4.1.代码逐行解析

static void scull_create_proc(void)

解释

  • static:限定该函数的作用域仅在当前文件内。
  • void:该函数没有返回值。
  • (void):明确表示该函数不接受任何参数。
  • 整体:这个函数的唯一职责就是在内核的 proc 文件系统中注册并创建文件
	struct proc_dir_entry *entry;

解释

  • struct proc_dir_entry *entry;:声明一个指向 proc_dir_entry 结构的指针变量 entry
  • struct proc_dir_entry:这是内核中用于表示 /proc 文件系统中一个条目(文件或目录)的核心数据结构。它包含了条目的名称、权限、父目录、以及最重要的——指向文件操作函数集 (proc_fops) 的指针
  • 作用:这个指针将用于存储 create_proc_entry 函数的返回值,成功后它指向新创建的 proc 条目
	entry = create_proc_entry("scullseq", 0, NULL);

解释:这是创建 proc 条目的核心函数调用

  • entry =:将函数的返回值赋值给刚才声明的指针 entry
  • create_proc_entry(...):一个内核函数,用于在 /proc 文件系统中动态创建一个新条目
  • "scullseq":第一个参数,是一个字符串,指定要在 /proc 中创建的文件名。用户空间将通过 /proc/scullseq 路径访问这个文件
  • 0:第二个参数,是文件的权限模式(mode)。0 表示使用内核的默认权限
  • NULL:第三个参数,是一个指向 struct proc_dir_entry 的指针,指定新文件的父目录。NULL 表示父目录是 /proc 的根目录本身。如果你想在 /proc 下创建子目录(例如 /proc/mydriver/scullseq),你需要先用 proc_mkdir 创建子目录,然后在这里传递子目录对应的 proc_dir_entry 指针
  • 返回值:如果创建成功,函数返回一个指向新条目的指针;如果失败(例如内存不足),则返回 NULL
	if (entry)

解释

  • entry:检查 entry 指针的值。
  • 整体:这是一个至关重要的错误检查。它确保只有在 create_proc_entry 成功返回一个有效指针(非 NULL)的情况下,才执行后续操作。如果创建失败,直接跳过,避免内核解引用空指针导致崩溃
		entry->proc_fops = &scull_proc_ops;

解释:这是将我们定义的文件操作连接到 proc 条目的关键步骤

  • entry->proc_fops:访问 entry 所指向的 proc_dir_entry 结构体的 proc_fops 成员。这是一个 struct file_operations 类型的指针。
  • &scull_proc_ops:取地址符,获取我们之前定义并初始化的 scull_proc_ops 结构体的地址
  • 整体:这行代码建立了最终的连接。它告诉内核:“当用户空间程序对 /proc/scullseq 文件执行任何操作(如 open, read, seek)时,请使用 scull_proc_ops 中定义的函数来响应这些操作。” 至此,用户对 /proc/scullseq 的访问终于和我们模块的实现逻辑联系起来了
static void scull_remove_proc(void)

解释

  • 整体:这个函数是 scull_create_proc 的逆操作,其职责是在模块卸载时清理 /proc 文件系统中的条目,防止留下“僵尸”文
	remove_proc_entry("scullseq", NULL);

解释:这是删除 proc 条目的核心函数调用

  • remove_proc_entry(...):一个内核函数,用于移除之前通过 create_proc_entry 创建的条目

  • "scullseq":第一个参数,是要移除的条目的名称。必须与创建时使用的名称完全一致

  • NULL:第二个参数,是条目的父目录。必须与创建时指定的父目录一致。因为创建时父目录是 NULL/proc 根目录),所以这里也传递 NULL

  • 重要性:在模块的退出函数中调用此函数是强制性的。如果不清理,当模块被卸载后:

    • /proc/scullseq 文件依然存在

    • 但该文件背后的操作函数 (scull_proc_ops) 所在的模块已经被移除,代码内存已被释放

    • 如果用户空间尝试访问这个文件,内核会尝试调用一个不存在的内存地址,导致内核崩溃

4.2.总结与调用时机

​ 这两个函数通常会在模块的初始化和退出流程中被调用

清除函数新增删除/proc文件逻辑

void scull_cleanup_module(void)
{int i;dev_t devno = MKDEV(scull_major, scull_minor);if (scull_devices) {for (i = 0; i < scull_nr_devs; i++) {scull_trim(scull_devices + i);cdev_del(&scull_devices[i].cdev);}kfree(scull_devices);}#ifdef SCULL_DEBUGscull_remove_proc();
#endifunregister_chrdev_region(devno, scull_nr_devs);
}

初始化函数新增创建/proc文件逻辑

int scull_init_module(void)
{int result, i;dev_t dev = 0;if (scull_major) {dev = MKDEV(scull_major, scull_minor);result = register_chrdev_region(dev, scull_nr_devs, "scull");} else {result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,"scull");scull_major = MAJOR(dev);}if (result < 0) {printk(KERN_WARNING "scull: can't get major %d\n", scull_major);return result;}scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);if (!scull_devices) {result = -ENOMEM;goto fail;}memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));for (i = 0; i < scull_nr_devs; i++) {scull_devices[i].quantum = scull_quantum;scull_devices[i].qset = scull_qset;scull_setup_cdev(&scull_devices[i], i);}#ifdef SCULL_DEBUGscull_create_proc();
#endifreturn 0;fail:scull_cleanup_module();return result;
}

三、编译和加载

1.Makefile文件

​ 详细解释,请参考博客 https://blog.csdn.net/weixin_51019352/article/details/151835068 模块编译 一节

DEBUG = yifeq ($(DEBUG),y)DEBFLAGS = -O -g -DSCULL_DEBUG
elseDEBFLAGS = -O2
endifCFLAGS += $(DEBFLAGS)ifneq ($(KERNELRELEASE),)obj-m := scull.o
elseKERNELDIR ?= /lib/modules/$(shell uname -r)/buildPWD := $(shell pwd)default:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesendifclean:$(MAKE) -C $(KERNELDIR) M=$(PWD) cleanrm -f *.ko *.mod.c *.mod.o *.o .tmp_versions

2.scull_load和scull_unload文件

​ 详细解释,请参考博客 https://blog.csdn.net/weixin_51019352/article/details/151870691 模块加载和卸载脚本 一节

scull_load文件

#!/bin/sh
module="scull"
device="scull"
mode="666"if grep -q '^staff:' /etc/group; thengroup="staff"
elsegroup="wheel"
fi/sbin/insmod ./$module.ko $* || exit 1major=$(awk "\$2==\"$module\" {print \$1}" /proc/devices)rm -f /dev/${device}[0-3]
mknod /dev/${device}0 c $major 0
mknod /dev/${device}1 c $major 1
mknod /dev/${device}2 c $major 2
mknod /dev/${device}3 c $major 3
ln -sf ${device}0 /dev/${device}
chgrp $group /dev/${device}[0-3] 
chmod $mode  /dev/${device}[0-3]

scull_unload文件

#!/bin/sh
module="scull"
device="scull"/sbin/rmmod $module $* || exit 1rm -f /dev/${device} /dev/${device}[0-3] 

3.编译和加载模块

# 编译模块,默认DEBUG = y
make
# 赋予加载脚本执行权限
sudo chmod 744 scull_load
# 执行脚本
sudo ./scull_load

卸载模块

sudo chmod 744 scull_unload
sudo ./scull_unload

四、验证模块

1.确认模块已成功加载

# 确认模块是否已被加载
lsmod | grep scull
# scull                   7308  0  预期输出# 确认内核是否已经有该设备
cat /proc/devices | grep scull
# 252 scull 预期输出# 确认用户空间是否已经创建相关设备文件
ls /dev/scull*
# /dev/scull  /dev/scull0  /dev/scull1  /dev/scull2  /dev/scull3  预期输出

2.确认/proc/scullseq文件是否创建

ls /proc/scullseq

3.确认/proc/scullseq文件读取功能正常

echo "test" > /dev/scull0
echo "test" > /dev/scull1
echo "test" > /dev/scull2
echo "test" > /dev/scull3cat /proc/scullseq

预期输出

Device 0: qset 1000, q 4000, sz 5item at eabb6e34, qset at e95930000: e98b8000Device 1: qset 1000, q 4000, sz 5item at e9b1ac24, qset at e97330000: e9828000Device 2: qset 1000, q 4000, sz 5item at f163f59c, qset at e95380000: e959f000Device 3: qset 1000, q 4000, sz 5item at e8f57728, qset at e94fa0000: e9891000
http://www.dtcms.com/a/393545.html

相关文章:

  • 理解CSS中的100%和100vh
  • [特殊字符] Chrome浏览器证书导入指南
  • 15-用户登录案例
  • Kurt-Blender零基础教程:第3章:材质篇——第1节:材质基础~原理化BSDF,添加有纹理材质与用蒙版做纹理叠加
  • 南京大学 - 复杂结构数据挖掘(一)
  • 嵌入式系统、手机与电脑:一场技术演化的“三角关系”
  • Go语言常用的第三方开发包教程合集
  • 鸿蒙Next ArkTS卡片进程模型解析:安全高效的UI组件隔离之道
  • ubuntu linux 控制wifi功能 dbus控制
  • `TensorBoard`、`PyTorchViz` 和 `HiddenLayer` 深度学习中三个重要的可视化工具
  • 本地设备ipv6默认网关和路由器ipv6默认网关的区别
  • 云原生docker在线yum安装
  • LeetCode 384 打乱数组 Swift 题解:从洗牌算法到实际应用
  • 计算机网络-因特网
  • HDFS和MapReduce——Hadoop的两大核心技
  • 【华为OD】石头剪刀布游戏
  • LinuxC++项目开发日志——基于正倒排索引的boost搜索引擎(1——项目框架)
  • Photoshop - Photoshop 非破坏性编辑
  • C++入门小馆:C++11第三弹(可变参数模板)
  • 常用设计模式中的工厂模式,责任链模式,策略模式和单例模式的简单说明
  • aave v3 合约解析1 存款
  • autosar中自旋锁和互斥锁的应用
  • 建筑可视化告别“假”渲染:用Photoshop+Twinmotion打造照片级场景
  • 一键生成linux服务器健康巡检html报告
  • 数据结构(C语言篇):(十八)交换排序
  • Ubuntu20.04下跑通ORB-SLAM2
  • C++二进制转十进制
  • WordPress用户系统 + JWT认证:打造统一的应用登录解决方案
  • PortSwigger靶场之将反射型 XSS 注入到带有尖括号和双引号的 JavaScript 字符串中,并使用 HTML 编码和单引号进行转义通关秘籍
  • win11电脑按键失灵,提供几个可能恢复的方法