[Linux]学习笔记系列 --[drivers][base]devtmpfs


title: devtmpfs

categories:

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

https://github.com/wdfk-prog/linux-study

文章目录

  • 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;
}
相关推荐
optimistic_chen1 小时前
【Docker入门】Docker原理和安装
linux·运维·服务器·docker·容器·命令行
花姐夫Jun1 小时前
cesium基础学习-坐标系统相互转换及相应的场景
学习·webgl
渣渣灰95871 小时前
Windows11安装WSL2(Windows Subsystem for Linux)
linux·运维·windows
DS随心转小程序1 小时前
【技术前瞻】Edge 浏览器深度集成 DS随心转:AI 搜索与笔记流转的一站式生产力革命
人工智能·笔记·edge·deepseek·ds随心转
June bug1 小时前
【实习笔记】埋点测试
笔记
南山二毛1 小时前
ubuntu开机自启动脚本
linux·运维·ubuntu
来两个炸鸡腿1 小时前
【Datawhale组队学习202601】Base-NLP task03 深入大模型架构
人工智能·学习·自然语言处理
培小新2 小时前
运维高级课笔记(RHCSA复习)
笔记
汤姆yu2 小时前
基于android的云笔记系统
笔记