之前移植的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版本的内核使用下面的这个命令行参数,也是有读写权限的。
这个问题暂时没时间,就不深入分析了。