从零开始之驱动开发、linux驱动(七十、4.19内核nfs挂载只读问题的延伸)

之前移植的3.16.57版本的内核,使用2018年7月的busybox自己构建的这个根文件系统。

使用3.16.57版本的内核用nfs挂载,这个根文件系统是一个可读可写的内核。

但后面为了学习设备树,移植了4.19版本的内核,发现文件系统成了只读的文件系统。

开始是怀疑我pc上了nfs服务器那里设置的有问题,后面用3.16.57版本内核验证了发现是ok的。

那么只能怀疑4.19版本内核的问题了。

既然上面红框里圈起来的内容打印了一些信息,那么我们就根据打印的信息进行反推。

VFS: Mounted root (nfs filesystem) readonly on device 0:11.

通过搜索VFS: Mounted root字符串找到,这传信息的打印位置

static int __init do_mount_root(char *name, char *fs, int flags, void *data)
{
	struct super_block *s;
	int err = ksys_mount(name, "/root", fs, flags, data);
	if (err)
		return err;

	ksys_chdir("/root");
	s = current->fs->pwd.dentry->d_sb;
	ROOT_DEV = s->s_dev;
	printk(KERN_INFO
	       "VFS: Mounted root (%s filesystem)%s on device %u:%u.\n",
	       s->s_type->name,
	       sb_rdonly(s) ? " readonly" : "",
	       MAJOR(ROOT_DEV), MINOR(ROOT_DEV));
	return 0;
}

而打印readonly是需要super_block 中的s_flags的SB_RDONLY位为1。

static inline bool sb_rdonly(const struct super_block *sb) { return sb->s_flags & SB_RDONLY; }

这里我们搜索反查do_mount_root函数的调用。

有多个,但是我们是nfs挂载,所以找到nfs的就可以。

 int __init mount_nfs_root(void)
{
	char *root_dev, *root_data;
	unsigned int timeout;
	int try, err;

	err = nfs_root_data(&root_dev, &root_data);
	if (err != 0)
		return 0;

	/*
	 * The server or network may not be ready, so try several
	 * times.  Stop after a few tries in case the client wants
	 * to fall back to other boot methods.
	 */
	timeout = NFSROOT_TIMEOUT_MIN;
	for (try = 1; ; try++) {
		err = do_mount_root(root_dev, "nfs",
					root_mountflags, root_data);
		if (err == 0)
			return 1;
		if (try > NFSROOT_RETRY_MAX)
			break;

		/* Wait, in case the server refused us immediately */
		ssleep(timeout);
		timeout <<= 1;
		if (timeout > NFSROOT_TIMEOUT_MAX)
			timeout = NFSROOT_TIMEOUT_MAX;
	}
	return 0;
}

可以发现挂载标志 root_mountflags,找到初始值。发现默认是只读的。

int root_mountflags = MS_RDONLY | MS_SILENT;

搜索这个root_mountflags标志,可以在那赋值。

发现只有两个位置给赋值过。

static int __init readonly(char *str)
{
	if (*str)
		return 0;
	root_mountflags |= MS_RDONLY;
	return 1;
}

static int __init readwrite(char *str)
{
	if (*str)
		return 0;m
	root_mountflags &= ~MS_RDONLY;
	return 1;
}

__setup("ro", readonly);
__setup("rw", readwrite);

上面两个,大家应该很熟悉,cmdline传参的命令。

如果文件系统传的是ro,那就是只读,如果是rw,那就是可读写。

这里看一下我们传的命令行参数。

Kernel command line: root=/dev/nfs nfsroot=192.168.0.101:/home/run/work/rootfs/rootfs_3.16.57 ip=192.168.0.20:192.168.0.101:192.168.0.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC2,115200

这里我们的命令命令行没有读写。

在修改设备树文件,在nfs后面增加rw

重新用新的设备树,启动内核。发现cmdline竟然没有变!!!!!!

what fuck!

在内核中查看设原始的设备树文件,发现也是没变的。

而且可以发现,两个的命令行参数很多地方都是不一样的。

问题好奇葩,导致和朋友去深圳湾玩,脑子都在想怎么会这样!

从深圳湾回来的路上突然想到,内核中的这个参数好像是之前给3.16.57内核使用过。

难道cmdline和之前的rootfs还能有关系?

这个想法很快就否定了。

后面又想到,难道是内核启动,既有设备树的chosen传参,又有tag传参,然后内核使用了tag?

这个想了一下也否定了,因为bootloader给内核传参第二个寄存器指定参数地址,不可能指定两个。而前面4.19内核,我们的设备树其它驱动也是可以正常使用的。

公交快到家了,突然想到,难道uboot中既有环境变量中的bootargs,又有设备树chosen中bootargs时,会用环境变量中的把chosen中的替换掉?

回到家,打开uboot,搜索bootargs发现果然是。

int fdt_chosen(void *fdt)
{
	int   nodeoffset;
	int   err;
	char  *str;		/* used to set string properties */

	err = fdt_check_header(fdt);
	if (err < 0) {
		printf("fdt_chosen: %s\n", fdt_strerror(err));
		return err;
	}

	/* find or create "/chosen" node. */
	nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen");
	if (nodeoffset < 0)
		return nodeoffset;

	str = getenv("bootargs");
	if (str) {
		err = fdt_setprop(fdt, nodeoffset, "bootargs", str,
				  strlen(str) + 1);
		if (err < 0) {
			printf("WARNING: could not set bootargs %s.\n",
			       fdt_strerror(err));
			return err;
		}
	}

	return fdt_fixup_stdout(fdt, nodeoffset);
}

可以发现,如果设备树文件中,如果没有chosen,uboot会创建一个chosen节点,并用自己的bootargs填充。

如果设备树文件中有chosen,也会用自己的bootargs填充。如果自己没bootargs时,那就不填充。

看到之前几个月通过设备树chosen传参都没传过去,尴尬。

我们这里想使用设备树的chosen,那么就去掉uboot环境变量里面的bootargs。

搜索找到,我们只需要去掉CONFIG_BOOTARGS宏就可以,去掉环境变量里的bootargs。

这次我们在uboot中去掉这个宏,重新编译uboot,启动看一下。

命令行参数ok了。

这里挂载的就不是只读了。

验证一下,发现,写功能也是ok的。

当然这里的疑问就是3.16.57版本的内核使用下面的这个命令行参数,也是有读写权限的。

这个问题暂时没时间,就不深入分析了。

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/89740600
今日推荐