title: devtmpfs
categories:
- linux
- drivers
- base
tags: - linux
- drivers
- base
abbrlink: '5e321190'
date: 2025-10-03 09:01:49

文章目录
- drivers/base/devtmpfs.c
-
- [handle_create 创建一个设备节点(device node)](#handle_create 创建一个设备节点(device node))
- [handle_remove 删除一个设备节点(device node)](#handle_remove 删除一个设备节点(device node))
- [devtmpfs_work_loop devtmpfs 的事件处理主循环](#devtmpfs_work_loop devtmpfs 的事件处理主循环)
- [devtmpfs_setup devtmpfs 的隔离环境设置](#devtmpfs_setup devtmpfs 的隔离环境设置)
- [devtmpfsd devtmpfs 守护线程](#devtmpfsd devtmpfs 守护线程)
- [devtmpfs_init devtmpfs 文件系统的初始化](#devtmpfs_init devtmpfs 文件系统的初始化)
drivers/base/devtmpfs.c
handle_create 创建一个设备节点(device node)
c
// 在内核空间创建一个目录。
// name: 要创建的目录的路径字符串。
// mode: 目录的访问权限模式 (例如 0755)。
static int dev_mkdir(const char *name, umode_t mode)
{
// dentry: 目录项(directory entry)的缩写,是VFS中用于将文件名链接到inode的对象。
struct dentry *dentry;
// path: VFS中用于表示一个完整路径的对象,包含挂载点和dentry。
struct path path;
// 调用VFS核心函数,尝试创建一个路径。
// AT_FDCWD: 表示路径`name`是相对于当前工作目录。在内核线程中,这通常是根目录 "/"。
// &path: 用于返回查找到的路径信息。
// LOOKUP_DIRECTORY: 标志,指示VFS我们期望创建一个目录。
// 此函数会解析路径,找到父目录,并为新目录准备好dentry。
dentry = kern_path_create(AT_FDCWD, name, &path, LOOKUP_DIRECTORY);
// IS_ERR是一个宏,用于检查返回的指针是否编码了一个错误。
if (IS_ERR(dentry))
// PTR_ERR宏从错误指针中提取出标准的负数错误码(如 -ENOENT)。
return PTR_ERR(dentry);
// 调用VFS层核心的mkdir函数,在指定的父目录(path.dentry)下创建新的目录(dentry)。
// &nop_mnt_idmap: 一个默认的ID映射,在没有复杂用户命名空间的嵌入式系统上,这表示直接使用传入的UID/GID。
// d_inode(path.dentry): 获取父目录的inode。
dentry = vfs_mkdir(&nop_mnt_idmap, d_inode(path.dentry), dentry, mode);
if (!IS_ERR(dentry))
/*
* 将此inode的i_private字段指向一个静态的内核变量(&thread)。
* 这是一个标记机制,用于标识这个inode是由内核的这个特定机制创建的,
* 以便将来可以识别和管理它。
*/
d_inode(dentry)->i_private = &thread;
// 释放由kern_path_create获取的路径引用,完成VFS操作。
done_path_create(&path, dentry);
// 如果dentry是指向有效的dentry,则返回0(成功);如果是错误指针,则返回错误码。
return PTR_ERR_OR_ZERO(dentry);
}
// 确保一个完整路径中的所有目录都存在,如果不存在则创建它们。
// nodepath: 目标节点的完整路径,例如 "/dev/bus/usb"。
static int create_path(const char *nodepath)
{
char *path;
char *s;
int err = 0;
// 使用kmalloc复制一份路径字符串,因为后续需要对其进行修改。
// GFP_KERNEL表示在内存分配时,如果需要可以睡眠等待。
path = kstrdup(nodepath, GFP_KERNEL);
if (!path)
// 如果内存分配失败,返回-ENOMEM。
return -ENOMEM;
// s 指向复制的路径字符串的开头。
s = path;
// 一个无限循环,用于逐级解析和创建目录。
for (;;) {
// 寻找下一个'/'字符。
s = strchr(s, '/');
if (!s)
// 如果找不到更多的'/',说明已经处理完所有子目录,退出循环。
break;
// 暂时将'/'替换为字符串结束符'\0'。
// 例如,对于"/dev/bus/usb",第一次循环时,path会变成"dev"。
s[0] = '\0';
// 调用dev_mkdir尝试创建当前级别的目录(例如"dev")。
err = dev_mkdir(path, 0755); // 使用0755权限。
// 如果创建失败,并且错误不是"文件已存在"(-EEXIST),
// 那么这是一个真正的错误,终止操作。
if (err && err != -EEXIST)
break;
// 将'\0'恢复为'/',以便进行下一轮的查找。
s[0] = '/';
// 指针s移动到'/'之后,准备处理下一个路径组件。
s++;
}
// 释放之前分配的路径字符串副本。
kfree(path);
// 返回最终的错误码,如果一切顺利,err将是0或-EEXIST,最终被视为成功。
return err;
}
// 定义处理设备节点创建的主函数。
// `nodename`: 要创建的设备节点的完整路径,例如 "/dev/mydevice"。
// `mode`: 节点的权限和类型(例如 S_IFCHR 表示字符设备)。
// `uid`, `gid`: 节点的用户ID和组ID。
// `dev`: 指向该设备节点的内核`device`结构体。
static int handle_create(const char *nodename, umode_t mode, kuid_t uid,
kgid_t gid, struct device *dev)
{
struct dentry *dentry;
struct path path;
int err;
// 第一次尝试创建路径。这里的0标志表示查找父目录并为最终组件创建dentry。
dentry = kern_path_create(AT_FDCWD, nodename, &path, 0);
// 如果返回的错误是-ENOENT (No such file or directory),
// 这通常意味着父目录不存在。
if (dentry == ERR_PTR(-ENOENT)) {
// 调用 create_path 来递归创建所有不存在的父目录。
create_path(nodename);
// 再次尝试创建路径,此时父目录应该已经存在了。
dentry = kern_path_create(AT_FDCWD, nodename, &path, 0);
}
// 如果在创建父目录后仍然出错,则返回错误。
if (IS_ERR(dentry))
return PTR_ERR(dentry);
// 调用核心的VFS mknod函数,以创建设备节点。
// `d_inode(path.dentry)`: 父目录的inode。
// `dentry`: 新设备节点的dentry。
// `mode`: 节点类型(字符/块设备)和权限。
// `dev->devt`: 最关键的参数,包含了设备的主/次设备号(major/minor)。
err = vfs_mknod(&nop_mnt_idmap, d_inode(path.dentry), dentry, mode,
dev->devt);
// 如果节点创建成功...
if (!err) {
// 定义一个iattr结构体,用于之后修改inode的属性。
struct iattr newattrs;
newattrs.ia_mode = mode; // 设置权限
newattrs.ia_uid = uid; // 设置用户ID
newattrs.ia_gid = gid; // 设置组ID
// `ia_valid` 是一个掩码,指明了哪些属性需要被更改。
newattrs.ia_valid = ATTR_MODE|ATTR_UID|ATTR_GID;
// 在修改inode之前获取其锁,以防止竞态条件。
// 在单核系统上,这主要用于防止中断上下文的干扰。
inode_lock(d_inode(dentry));
// 调用notify_change,它会应用新的属性并通知其他子系统(如inotify)。
notify_change(&nop_mnt_idmap, dentry, &newattrs, NULL);
// 释放inode锁。
inode_unlock(d_inode(dentry));
// 再次"标记"这个inode是由内核创建的。
d_inode(dentry)->i_private = &thread;
}
// 释放路径查找资源。
done_path_create(&path, dentry);
// 返回操作的最终错误码。
return err;
}
handle_remove 删除一个设备节点(device node)
c
// 定义一个用于移除目录的函数。
static int dev_rmdir(const char *name)
{
// `parent`: 用于存储找到的目录的父目录路径信息。
struct path parent;
// `dentry`: 指向要删除的目录的目录项(directory entry)。
struct dentry *dentry;
int err;
// 在VFS中查找名为`name`的路径,并锁定其父目录的inode以防止竞争。
// 返回`name`对应的dentry。
dentry = kern_path_locked(name, &parent);
// 检查查找操作是否出错。
if (IS_ERR(dentry))
return PTR_ERR(dentry);
// 这是一个核心安全检查:检查此目录的inode私有指针是否指向`thread`。
// 这确保我们只删除由本模块的`dev_mkdir`函数创建的目录。
if (d_inode(dentry)->i_private == &thread)
// 如果检查通过,则调用VFS核心函数来移除目录。
err = vfs_rmdir(&nop_mnt_idmap, d_inode(parent.dentry),
dentry);
else
// 如果该目录不是由我们创建的,则返回"操作不允许"错误,防止误删。
err = -EPERM;
// 递减`dentry`的引用计数,如果降为0,则释放该dentry。
dput(dentry);
// 解锁父目录的inode。
inode_unlock(d_inode(parent.dentry));
// 递减父目录路径的引用计数。
path_put(&parent);
// 返回操作结果。
return err;
}
// 定义一个用于删除路径中父目录的函数。
static int delete_path(const char *nodepath)
{
char *path;
int err = 0;
// 为路径字符串创建一个可修改的副本。
path = kstrdup(nodepath, GFP_KERNEL);
if (!path)
return -ENOMEM;
// 循环尝试删除父目录。
for (;;) {
char *base;
// `strrchr`查找路径中最后一个斜杠'/'。这是从深到浅处理的关键。
base = strrchr(path, '/');
// 如果找不到斜杠,意味着已经到达路径顶部(或路径无效),循环终止。
if (!base)
break;
// 临时将斜杠替换为字符串结束符,从而截断路径。
// 例如,"/dev/bus/usb"会变成"/dev/bus"。
base[0] = '\0';
// 尝试删除被截断出来的路径。
err = dev_rmdir(path);
// 如果删除失败(例如目录非空,或权限不足),则停止向上删除。
if (err)
break;
}
// 释放字符串副本的内存。
kfree(path);
// 返回最后一次删除操作的结果。
return err;
}
// 判断一个inode是否是本模块为特定设备所创建的节点。
static int dev_mynode(struct device *dev, struct inode *inode)
{
/* 首先,检查inode的私有指针,判断它是否由我们创建。 */
if (inode->i_private != &thread)
return 0; // 不是我们创建的,返回false。
/* 其次,检查设备类型是否匹配。 */
if (is_blockdev(dev)) { // 如果内核设备对象是块设备...
if (!S_ISBLK(inode->i_mode)) // ...但inode不是块设备文件...
return 0; // ...则不匹配,返回false。
} else { // 如果内核设备对象是字符设备...
if (!S_ISCHR(inode->i_mode)) // ...但inode不是字符设备文件...
return 0; // ...则不匹配,返回false。
}
/* 再次,检查设备号是否完全匹配。 */
// `inode->i_rdev` 存储了设备文件的设备号(主/次)。
// `dev->devt` 是内核设备对象的设备号。
if (inode->i_rdev != dev->devt)
return 0; // 设备号不匹配,返回false。
/* 所有检查都通过,确认这是我们要找的节点。 */
return 1; // 返回true。
}
// 处理设备节点移除的主函数。
static int handle_remove(const char *nodename, struct device *dev)
{
struct path parent;
struct dentry *dentry;
struct inode *inode;
int deleted = 0; // 标记节点是否已成功删除。
int err = 0;
// 查找要移除的节点,并锁定其父目录。
dentry = kern_path_locked(nodename, &parent);
if (IS_ERR(dentry))
return PTR_ERR(dentry);
inode = d_inode(dentry);
// 调用验证函数,确认这确实是我们要移除的设备节点。
if (dev_mynode(dev, inode)) {
struct iattr newattrs;
/*
* 在取消链接此节点之前,重置可能的引用(如硬链接)的权限。
* 这是一项安全措施。
*/
newattrs.ia_uid = GLOBAL_ROOT_UID; // 设置所有者为root
newattrs.ia_gid = GLOBAL_ROOT_GID; // 设置所属组为root
newattrs.ia_mode = inode->i_mode & ~0777; // 清除所有用户、组和其他人的权限位。
newattrs.ia_valid = ATTR_UID|ATTR_GID|ATTR_MODE; // 标记要修改的属性。
inode_lock(d_inode(dentry)); // 锁定inode。
// 应用属性变更。
notify_change(&nop_mnt_idmap, dentry, &newattrs, NULL);
inode_unlock(d_inode(dentry)); // 解锁inode。
// 调用VFS核心函数来取消链接(即删除)文件节点。
err = vfs_unlink(&nop_mnt_idmap, d_inode(parent.dentry),
dentry, NULL);
// 如果删除成功(err==0)或文件本就不存在(err==-ENOENT),则标记为已删除。
if (!err || err == -ENOENT)
deleted = 1;
}
dput(dentry); // 释放dentry引用。
inode_unlock(d_inode(parent.dentry)); // 释放父目录锁。
path_put(&parent); // 释放父路径引用。
// 如果节点被成功删除,并且其路径中包含'/'(意味着它在子目录中)...
if (deleted && strchr(nodename, '/'))
// ...则调用delete_path尝试清理空的父目录。
delete_path(nodename);
return err;
}
devtmpfs_work_loop devtmpfs 的事件处理主循环
- devtmpfs_work_loop 是 devtmpfsd 内核线程的无限工作循环,也是 devtmpfs 机制的引擎。一旦 devtmpfs 初始化完成,线程就会进入这个函数并且永不返回(由 __noreturn 属性指明)。其核心原理是作为一个高效的、事件驱动的消费者:在没有设备事件时,它会深度睡眠,不消耗任何 CPU 资源;当内核的其他部分(如驱动核心)需要创建或删除设备节点时,它会被唤醒,批量处理所有待办请求,然后再次进入睡眠。
c
static struct req {
struct req *next;
struct completion done;
int err;
const char *name;
umode_t mode; /* 0 => delete */
kuid_t uid;
kgid_t gid;
struct device *dev;
} *requests;
static int handle(const char *name, umode_t mode, kuid_t uid, kgid_t gid,
struct device *dev)
{
if (mode)
return handle_create(name, mode, uid, gid, dev);
else
return handle_remove(name, dev);
}
/*
* __noreturn: 这是一个函数属性,用于告知编译器此函数永远不会返回。
* 这有助于编译器进行某些优化,并抑制关于缺少返回值的警告。
* devtmpfs_work_loop: 函数名。它作为 devtmpfsd 守护线程的主体,无限期运行。
*/
static void __noreturn devtmpfs_work_loop(void)
{
/*
* 启动一个无限循环,这是守护线程的标准模式。线程将永远停留在此循环中,
* 等待并处理工作。
*/
while (1) {
/*
* 获取 req_lock 锁。这是一个自旋锁,用于保护对共享数据"requests"链表的访问。
* 在单核可抢占的系统中(如运行在 STM32H750 上的可抢占内核),
* spin_lock 会禁止内核抢占,确保在访问 requests 链表时,当前线程不会被其他任务中断,
* 从而防止数据竞争。如果内核是不可抢占的,它可能主要用来防止来自中断上下文的访问。
*/
spin_lock(&req_lock);
/*
* 检查全局的"requests"链表是否为空。requests 是一个指向请求链表头部的指针。
* 如果它不为 NULL,说明有待处理的设备节点创建/删除请求。
*/
while (requests) {
/*
* 将全局的 requests 链表头指针赋值给一个局部的 req 指针。
* 这是为了"接管"整个待处理请求链表。
*/
struct req *req = requests;
/*
* 将全局的 requests 链表头指针设置为 NULL。
* 这个操作与上面的赋值一起,构成了一个原子性的"抓取并清空"操作。
* 这样做的好处是,可以快速释放锁,让其他代码能够继续提交新的请求,
* 而处理请求的耗时操作则在锁之外进行,极大地减小了锁的粒度和持有时间。
*/
requests = NULL;
/*
* 释放 req_lock 锁。从此刻起,其他内核线程或中断就可以安全地向
* 全局的 requests 链表中添加新的请求了。
*/
spin_unlock(&req_lock);
/*
* 现在,在没有持有锁的情况下,开始遍历并处理刚刚抓取到的本地请求链表 (req)。
*/
while (req) {
/*
* 在处理当前请求之前,先将其下一个请求的地址保存在 next 指针中。
* 这是遍历链表的标准做法。
*/
struct req *next = req->next;
/*
* 调用 handle() 函数,这是实际执行工作的函数。
* 它根据请求结构体 req 中携带的信息(设备名、模式、用户ID、组ID、设备号),
* 在 devtmpfs 文件系统中执行创建或删除设备节点的操作。
* 操作的结果(错误码)被存储回请求结构体的 err 成员中。
*/
req->err = handle(req->name, req->mode,
req->uid, req->gid, req->dev);
/*
* 调用 complete() 函数,并传入请求结构体中的 done 成员。
* req->done 是一个 completion 结构体。提交请求的内核代码
* 可能会调用 wait_for_completion(&req->done) 来同步等待此操作完成。
* complete() 会唤醒那个等待的线程。
*/
complete(&req->done);
/*
* 将 req 指针移动到下一个请求,继续循环。
*/
req = next;
}
/*
* 当处理完一批请求后,重新获取锁,准备再次检查是否有新的请求或进入睡眠。
*/
spin_lock(&req_lock);
}
/*
* 如果执行到这里,说明 requests 链表为空。
* 调用 __set_current_state() 将当前线程的状态设置为 TASK_INTERRUPTIBLE。
* 这告诉内核调度器,该线程现在没有工作可做,可以被安全地换出,进入睡眠状态。
* 它只会被明确的唤醒信号(wakeup)所唤醒。
* 这一步必须在持有 req_lock 的情况下完成,以防止"丢失的唤醒"竞争条件。
*/
__set_current_state(TASK_INTERRUPTIBLE);
/*
* 释放锁。此时线程状态已设置为睡眠,可以安全地让出 CPU。
*/
spin_unlock(&req_lock);
/*
* 调用 schedule() 函数,主动放弃 CPU。内核调度器将会运行,
* 看到当前线程状态为 TASK_INTERRUPTIBLE,就会选择另一个可运行的线程来执行,
* 并将当前线程放入等待队列,直到它被唤醒。
*/
schedule();
}
}
devtmpfs_setup devtmpfs 的隔离环境设置
- devtmpfs_setup 是一个在 devtmpfsd 内核线程上下文中执行的初始化函数。其核心目标是在一个受控的、隔离的环境中,将 devtmpfs 文件系统实例精确地挂载到系统初始命名空间的 /dev 目录上。
c
/*
* noinline: 告知编译器不要将此函数内联。这有助于在调试和分析时保持函数调用栈的清晰。
* __init: 标记此函数为内核初始化代码。在内核启动过程结束后,该函数所占用的内存会被释放。
* devtmpfs_setup: 函数名。
* void *p: 接收一个通用指针参数。根据 devtmpfsd 的调用,我们知道它实际是一个指向整型变量的指针(int *p),
* 用于将函数内部的错误码传回给调用者 devtmpfsd。
*/
static noinline int __init devtmpfs_setup(void *p)
{
/*
* 声明一个整型变量 err,用于存储后续函数调用的返回值(通常是错误码)。
*/
int err;
/*
* 调用 ksys_unshare(),并传入 CLONE_NEWNS 标志,为当前线程创建一个新的、独立的挂载命名空间。
* 这是本函数最关键的一步。它将 devtmpfsd 线程的挂载视图与系统其他部分隔离开来。
* 这样做允许该线程在自己的环境中自由地进行挂载操作,而不会立即影响到全局的初始挂载命名空间。
* 即使在没有 MMU 的 STM32H750 系统上,内核的虚拟文件系统(VFS)层依然支持挂载命名空间这一逻辑隔离机制。
*/
err = ksys_unshare(CLONE_NEWNS);
if (err)
/*
* 如果创建新的挂载命名空间失败,则跳转到 out 标签处进行统一的错误处理和返回。
*/
goto out;
/*
* 在刚刚创建的、属于自己的新挂载命名空间内,执行挂载操作。
* err = init_mount("devtmpfs", "/", "devtmpfs", DEVTMPFS_MFLAGS, NULL);
* "devtmpfs": 要挂载的文件系统的名称(源)。
* "/": 挂载点。这里指的是在新命名空间内的根目录。此操作将 devtmpfs 挂载到了该线程私有的根目录上。
* "devtmpfs": 文件系统类型。
* DEVTMPFS_MFLAGS: 挂载标志,定义了挂载的行为。
* NULL: 传递给文件系统的额外数据,此处为无。
*/
err = init_mount("devtmpfs", "/", "devtmpfs", DEVTMPFS_MFLAGS, NULL);
if (err)
/*
* 如果挂载失败,则跳转到 out 标签处。
*/
goto out;
/*
* 这是一个精巧的技巧,用于"逃离"刚刚完成的挂载。
* init_chdir("/..");
* 在执行完上一步挂载后,当前线程的根目录 "/" 已经是指向新挂载的 devtmpfs。
* 执行 chdir("..") 会切换到当前目录的父目录。对于根目录来说,它的父目录就是它自身。
* 但是,由于这个 devtmpfs 是"覆盖"在原始根文件系统之上的,这里的 ".." 操作会穿越挂载点,
* 回到被覆盖的那个目录,也就是系统初始命名空间的根目录。
*/
init_chdir("/.."); /* 将会穿越被覆盖的根目录 */
/*
* 将当前线程的根目录(root)切换到当前所在目录(".")。
* init_chroot(".");
* 经过上一步的 chdir,当前目录已经变成了系统初始命名空间的根目录。
* 执行 chroot(".") 后,devtmpfsd 线程的根目录就从它自己的私有根目录,变回了系统初始的根目录。
* 这一系列操作的最终效果是:devtmpfs 实例被成功挂载到了初始命名空间的 /dev 目录,
* 同时 devtmpfsd 线程也恢复了对初始命名空间的视图,确保其后续操作(在 devtmpfs_work_loop 中)
* 是在正确的全局上下文中进行的。
*/
init_chroot(".");
out:
/*
* out 标签:一个统一的出口点。
* 将 void 指针 p 强制类型转换为 int 指针,并解引用,将最终的错误码 err 赋值给它。
* 这样,调用者 devtmpfsd 函数就可以通过检查传递进来的指针所指向的值,来获知 setup 是否成功。
*/
*(int *)p = err;
/*
* 将错误码作为函数返回值返回。
*/
return err;
}
devtmpfsd devtmpfs 守护线程
- devtmpfsd 函数是 devtmpfs 文件系统的核心,它作为一个内核线程运行,负责处理所有与设备节点创建和删除相关的异步事件。这个线程由 devtmpfs_init 函数在初始化过程中创建并启动。
c
/*
* 此处的 __ref 是一个特殊的宏,用于告诉编译器,虽然 devtmpfsd 函数本身
* 没有被标记为 __init(因为它需要持续运行),但它会引用一些 __init
* 代码(即 devtmpfs_setup 函数)。devtmpfs_setup 之所以需要是 __init,
* 是因为它调用了只能在初始化阶段使用的函数。
* 这个调用是在 devtmpfs_init(一个 __init 函数)同步等待 devtmpfsd
* 完成其初始设置时发生的。这种机制确保了初始化代码在完成其使命后可以被安全地释放,
* 同时允许 devtmpfsd 线程本身继续存在于内核内存中。
*/
static int __ref devtmpfsd(void *p)
{
/*
* 声明一个整型变量 err,用于接收 devtmpfs_setup 函数的返回值。
* 调用 devtmpfs_setup 函数,执行 devtmpfs 的核心设置。
* 这包括在根文件系统的 /dev 目录挂载 devtmpfs。
* p 是一个指向 err 变量的指针,在 devtmpfs_init 中传递进来,
* 允许 devtmpfs_setup 在内部报告错误。
* 在 STM32H750 系统上,此步骤将建立起实际的 /dev 目录,使其成为
* 一个基于 RAM 的动态设备文件系统。
*/
int err = devtmpfs_setup(p);
/*
* 调用 complete() 函数,唤醒正在等待的进程。
* &setup_done 是一个全局的 completion 结构体。在 devtmpfs_init 函数中,
* 内核线程启动后会调用 wait_for_completion(&setup_done) 进行等待。
* 此处的 complete() 调用通知 devtmpfs_init,初始设置(主要是挂载到 /dev)
* 已经完成,devtmpfs_init 可以继续执行后续的初始化步骤了。
* 这是一个典型的内核同步机制。
*/
complete(&setup_done);
/*
* 检查 devtmpfs_setup 函数的执行结果。
*/
if (err)
/*
* 如果 devtmpfs_setup 返回了错误码,则该线程直接返回错误码并退出,
* 不会进入主循环。
*/
return err;
/*
* 调用 devtmpfs_work_loop() 函数,进入一个无限循环。
* 这个循环是 devtmpfsd 线程的主要工作区,它会持续等待并处理来自
* 内核(主要是驱动核心)的 uevent 事件,这些事件通知线程需要
* 创建或删除设备节点。
*/
devtmpfs_work_loop();
/*
* devtmpfs_work_loop() 是一个无限循环,所以理论上不会执行到这里。
* 返回 0 仅作为函数声明的完整性要求。
*/
return 0;
}
devtmpfs_init devtmpfs 文件系统的初始化
- devtmpfs 是一个基于 tmpfs 的文件系统,在 Linux 内核启动期间创建,用于存放设备节点。这确保了设备节点在设备被发现时即可用,而无需等待 udev 等用户空间工具来创建它们。
c
/* Ops are filled in during init depending on underlying shmem or ramfs type */
struct fs_context_operations devtmpfs_context_ops = {};
/* Call the underlying initialization and set to our ops */
static int devtmpfs_init_fs_context(struct fs_context *fc)
{
int ret;
// #ifdef CONFIG_TMPFS
// ret = shmem_init_fs_context(fc);
// #else
ret = ramfs_init_fs_context(fc);
// #endif
if (ret < 0)
return ret;
fc->ops = &devtmpfs_context_ops;
return 0;
}
static struct file_system_type dev_fs_type = {
.name = "devtmpfs",
.init_fs_context = devtmpfs_init_fs_context,
};
static struct file_system_type internal_fs_type = {
.name = "devtmpfs",
// #ifdef CONFIG_TMPFS
// .init_fs_context = shmem_init_fs_context,
// #else
.init_fs_context = ramfs_init_fs_context,
// #endif
.kill_sb = kill_litter_super,
};
/*
* 创建 devtmpfs 实例,驱动核心设备将在此处添加其设备节点。
* 此函数负责初始化一个临时的、基于内存的文件系统来管理设备文件,这对于
* 动态设备发现至关重要。
*/
int __init devtmpfs_init(void)
{
/*
* 定义一个字符串数组,用于存储传递给 devtmpfs 文件系统的挂载选项。
* "mode=0755" 设置了 devtmpfs 根目录的默认权限为 0755 (rwxr-xr-x)。
*/
char opts[] = "mode=0755";
/*
* 声明一个整型变量 err,用于存储函数调用的返回值,以便进行错误检查。
*/
int err;
/*
* 调用 vfs_kern_mount 函数挂载一个新的文件系统实例。
* &internal_fs_type: 指向一个静态定义的 file_system_type 结构体,
* 对于 devtmpfs 来说,这通常是 ramfs 或 shmem_fs。
* 0: 挂载标志,此处为 0,表示使用默认标志。
* "devtmpfs": 文件系统的名称,将显示在 /proc/mounts 中。
* opts: 传递给文件系统的挂载选项,即 "mode=0755"。
* mnt: 是一个全局的 vfsmount 结构体指针,用于保存新挂载的文件系统实例的引用。
*/
mnt = vfs_kern_mount(&internal_fs_type, 0, "devtmpfs", opts);
/*
* 检查 vfs_kern_mount 的返回值,判断挂载是否成功。
* IS_ERR(mnt) 是一个宏,用于检查指针是否包含一个错误码。
* 在单核 STM32H750 系统上,如果内存分配失败,此步骤可能会失败。
*/
if (IS_ERR(mnt)) {
/*
* 如果挂载失败,使用 pr_err 打印错误信息。
* PTR_ERR(mnt) 从指针中提取错误码。
*/
pr_err("unable to create devtmpfs %ld\n", PTR_ERR(mnt));
/*
* 返回从 mnt 指针中提取的错误码。
*/
return PTR_ERR(mnt);
}
/*
* 调用 devtmpfs_configure_context 函数配置 SELinux 上下文。
* 在不使用 SELinux 的系统中,此函数可能为空操作。
*/
err = devtmpfs_configure_context();
/*
* 检查 devtmpfs_configure_context 的返回值。
*/
if (err) {
/*
* 如果配置失败,打印错误信息。
*/
pr_err("unable to configure devtmpfs type %d\n", err);
/*
* 返回错误码。
*/
return err;
}
/*
* 调用 register_filesystem 函数向内核注册一个新的文件系统类型。
* &dev_fs_type: 指向 devtmpfs 的 file_system_type 结构体。
* 这使得用户空间可以通过 "mount -t devtmpfs" 命令来挂载 devtmpfs。
*/
err = register_filesystem(&dev_fs_type);
/*
* 检查 register_filesystem 的返回值。
*/
if (err) {
/*
* 如果注册失败,打印错误信息。
*/
pr_err("unable to register devtmpfs type %d\n", err);
/*
* 返回错误码。
*/
return err;
}
/*
* 调用 kthread_run 创建并启动一个新的内核线程。
* devtmpfsd: 是新线程要执行的函数,即 devtmpfs 的守护进程。
* &err: 传递给 devtmpfsd 函数的参数,用于在线程内部报告错误。
* "kdevtmpfs": 内核线程的名称。
* thread: 是一个全局的 task_struct 指针,用于保存新创建线程的引用。
* 在单核 STM32H750 系统上,此线程将与其他内核任务一起被调度器分时执行。
*/
thread = kthread_run(devtmpfsd, &err, "kdevtmpfs");
/*
* 检查 kthread_run 的返回值,判断线程是否创建成功。
*/
if (!IS_ERR(thread)) {
/*
* 如果线程创建成功,调用 wait_for_completion 等待 devtmpfsd 守护进程
* 完成其初始化设置。
* &setup_done: 是一个 completion 结构体,devtmpfsd 函数在完成
* 初始挂载后会调用 complete(&setup_done) 来唤醒此处的等待。
*/
wait_for_completion(&setup_done);
} else {
/*
* 如果线程创建失败,从 thread 指针中提取错误码。
*/
err = PTR_ERR(thread);
/*
* 将全局的 thread 指针设置为 NULL,表示没有正在运行的 devtmpfsd 线程。
*/
thread = NULL;
}
/*
* 检查线程创建或其初始化的过程中是否发生错误。
*/
if (err) {
/*
* 如果发生错误,打印错误信息。
*/
pr_err("unable to create devtmpfs %d\n", err);
/*
* 调用 unregister_filesystem 注销之前注册的 devtmpfs 文件系统类型。
*/
unregister_filesystem(&dev_fs_type);
/*
* 将全局的 thread 指针设置为 NULL。
*/
thread = NULL;
/*
* 返回错误码。
*/
return err;
}
/*
* 打印初始化成功的信息。
*/
pr_info("initialized\n");
/*
* 返回 0,表示 devtmpfs 初始化成功。
*/
return 0;
}