文章目录
- 1. 前言
- 2. 问题
- 3. 分析和解决
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 问题
由于开发的嵌入式板的 NOR FLASH
剩余空间过小,仅剩 65
MB,也没有接口外挂存储设备,且无法通过删减来增大空间,于是打算通过 NFS
远程挂载,于是执行下面命令进行挂载:
# mount -t nfs -o nolock 192.168.0.36:/home/XXX/nfs-share /test/nfs-remote
mount: /test/nfs-remote: bad option; for several filesystems (e.g. nfs, cifs) you might need a /sbin/mount.<type> helper program.
可以看到,mount
程序报错了。笔者有点懵逼,因为这条挂载命令,在一款 Linux 4.19
内核的设备上,曾使用无数次,一直没有任何问题。现在测试的环境,内核版本为 Linux 5.10
。
3. 分析和解决
mount
命令本身,不是笔者的第一怀疑对象,毕竟是个老牌应用,在常规操作上出错的可能性不高;更大的可能是 NFS
在高版本内核上的实现差异,先用 strace
大概跟了一下,看有没有什么地方出错了:
# strace mount -t nfs -o nolock 192.168.0.36:/home/XXX/nfs-share /test/nfs-remote
[...]
mount("192.168.0.36:/home/XXX/nfs-share", "/test/nfs-remote", "nfs", 0, "nolock") = -1 EINVAL (Invalid argument)
[...]
可以看到,mount()
系统调用报错,错误码为 EINVAL
。再用 ftrace
跟一下 do_mount()
函数(系统调用 mount()
调用了 do_mount()
),看哪里出错了(追踪内容有删减,只展示了出错调用的主干流程
):
# cd /sys/kernel/debug/tracing
# echo 0 > tracing_on
# echo > trace
# echo function_graph > current_tracer
# echo do_mount > set_graph_function
# echo 1 > tracing_on
# mount -t nfs -o nolock 192.168.0.36:/home/XXX/nfs-share /test/nfs-remote
# cat trace
# tracer: function_graph
#
# CPU DURATION FUNCTION CALLS
# | | | | | | |2) | do_mount() {2) | user_path_at_empty() {2) | getname_flags() {2) | kmem_cache_alloc() {2) 4.667 us | should_failslab();2) + 14.875 us | }2) + 23.917 us | }2) | filename_lookup() {2) 4.375 us | set_nameidata();2) | path_lookupat() {2) | path_init() {2) 5.250 us | __rcu_read_lock();2) | nd_jump_root() {2) 4.375 us | set_root();2) + 14.000 us | }2) + 32.667 us | }2) | link_path_walk() {2) | inode_permission() {2) 4.375 us | generic_permission();2) + 14.000 us | }2) | walk_component() {2) | lookup_fast() {2) 5.833 us | __d_lookup_rcu();2) + 14.291 us | }2) 4.667 us | step_into();2) + 31.791 us | }2) | inode_permission() {2) 4.083 us | generic_permission();2) + 14.292 us | }2) + 77.875 us | }2) | walk_component() {2) | lookup_fast() {2) 5.833 us | __d_lookup_rcu();2) + 14.292 us | }2) 4.375 us | step_into();2) + 35.583 us | }2) | complete_walk() {2) | try_to_unlazy() {2) 4.375 us | legitimize_links();2) | __legitimize_path() {2) 5.834 us | __legitimize_mnt();2) + 14.584 us | }2) 4.375 us | legitimize_root();2) 4.083 us | __rcu_read_unlock();2) + 52.500 us | }2) 4.083 us | success_walk_trace();2) + 71.750 us | }2) | terminate_walk() {2) 4.084 us | drop_links();2) | path_put() {2) 4.375 us | dput();2) 4.375 us | mntput();2) + 23.625 us | }2) + 40.834 us | }2) ! 285.833 us | }2) 4.375 us | restore_nameidata();2) | putname() {2) 4.958 us | kmem_cache_free();2) + 14.875 us | }2) ! 331.041 us | }2) ! 368.084 us | }2) | path_mount() {2) | ns_capable() {2) | ns_capable_common() {2) 4.375 us | cap_capable();2) + 13.125 us | }2) + 23.333 us | }2) | get_fs_type() {2) | __get_fs_type() {2) | _raw_read_lock() {2) 4.375 us | preempt_count_add();2) + 14.583 us | }2) + 18.083 us | find_filesystem();2) 4.958 us | try_module_get();2) | _raw_read_unlock() {2) 4.375 us | preempt_count_sub();2) + 13.125 us | }2) + 73.208 us | }2) + 84.292 us | }2) | fs_context_for_mount() {2) | alloc_fs_context() {2) | kmem_cache_alloc_trace() {2) 4.084 us | should_failslab();2) + 15.167 us | }2) | get_filesystem() {2) ==========> |2) | gic_handle_irq() {2) | __handle_domain_irq() {2) 1.750 us | irq_find_mapping();2) 5.833 us | irq_to_desc();2) | irq_enter() {2) | irq_enter_rcu() {2) 1.750 us | preempt_count_add();2) 5.250 us | }2) 8.167 us | }2) | handle_percpu_devid_irq() {2) | arch_timer_handler_phys() {2) | hrtimer_interrupt() {2) | _raw_spin_lock_irqsave() {2) 1.750 us | preempt_count_add();2) 4.666 us | }2) | ktime_get_update_offsets_now() {2) 1.459 us | arch_counter_read();2) 4.958 us | }2) | __hrtimer_run_queues() {2) 1.750 us | __remove_hrtimer();2) | _raw_spin_unlock_irqrestore() {2) 1.459 us | preempt_count_sub();2) 4.667 us | }2) | tick_sched_timer() {2) | ktime_get() {2) 1.459 us | arch_counter_read();2) 4.667 us | }2) | tick_sched_do_timer() {2) | tick_do_update_jiffies64() {2) | _raw_spin_lock() {2) 1.459 us | preempt_count_add();2) 4.667 us | }2) | do_timer() {2) 2.042 us | calc_global_load();2) 4.667 us | }2) | _raw_spin_unlock() {2) 1.458 us | preempt_count_sub();2) 4.666 us | }2) | update_wall_time() {2) | timekeeping_advance() {2) | _raw_spin_lock_irqsave() {2) 1.750 us | preempt_count_add();2) 4.667 us | }2) 1.750 us | arch_counter_read();2) 1.458 us | ntp_tick_length();2) 1.458 us | ntp_tick_length();2) | timekeeping_update() {2) 1.750 us | ntp_get_next_leap();2) 1.750 us | update_vsyscall();2) | raw_notifier_call_chain() {2) 1.750 us | notifier_call_chain();2) 4.667 us | }2) 1.750 us | update_fast_timekeeper();2) 1.459 us | update_fast_timekeeper();2) + 21.000 us | }2) | _raw_spin_unlock_irqrestore() {2) 1.458 us | preempt_count_sub();2) 4.666 us | }2) + 46.083 us | }2) + 49.000 us | }2) + 70.875 us | }2) + 73.792 us | }2) | tick_sched_handle() {2) | update_process_times() {2) | account_process_tick() {2) | account_system_time() {2) | account_system_index_time() {2) 1.458 us | __rcu_read_lock();2) 1.750 us | __rcu_read_unlock();2) | cpufreq_acct_update_power() {2) | _raw_spin_lock_irqsave() {2) 1.459 us | preempt_count_add();2) 4.667 us | }2) | _raw_spin_unlock_irqrestore() {2) 1.459 us | preempt_count_sub();2) 4.375 us | }2) + 14.292 us | }2) + 24.500 us | }2) + 27.417 us | }2) + 30.625 us | }2) | run_local_timers() {2) 1.750 us | hrtimer_run_queues();2) 4.958 us | }2) | rcu_sched_clock_irq() {2) 1.750 us | rcu_is_cpu_rrupt_from_idle();2) 1.458 us | rcu_preempt_need_deferred_qs();2) 1.459 us | rcu_qs();2) 2.042 us | rcu_stall_kick_kthreads();2) 1.750 us | rcu_is_cpu_rrupt_from_idle();2) 3.208 us | rcu_segcblist_ready_cbs();2) + 23.333 us | }2) | scheduler_tick() {2) 1.750 us | topology_scale_freq_tick();2) | _raw_spin_lock() {2) 1.750 us | preempt_count_add();2) 4.666 us | }2) 1.750 us | update_rq_clock();2) | update_thermal_load_avg() {2) 1.750 us | decay_load();2) 1.750 us | decay_load();2) 1.458 us | decay_load();2) + 11.083 us | }2) | task_tick_fair() {2) | update_curr() {2) 1.750 us | update_min_vruntime();2) 1.459 us | __rcu_read_lock();2) 1.458 us | __rcu_read_unlock();2) + 10.792 us | }2) | __update_load_avg_se() {2) 1.458 us | decay_load();2) 1.750 us | decay_load();2) 1.750 us | decay_load();2) | __accumulate_pelt_segments() {2) 1.750 us | decay_load();2) 1.459 us | decay_load();2) 7.583 us | }2) + 20.125 us | }2) | __update_load_avg_cfs_rq() {2) 1.750 us | decay_load();2) 1.458 us | decay_load();2) 1.750 us | decay_load();2) | __accumulate_pelt_segments() {2) 1.458 us | decay_load();2) 1.750 us | decay_load();2) 7.875 us | }2) + 20.417 us | }2) 1.750 us | update_cfs_group();2) 1.750 us | hrtimer_active();2) | update_curr() {2) 2.042 us | __calc_delta();2) 1.459 us | update_min_vruntime();2) 7.875 us | }2) | __update_load_avg_se() {2) 1.459 us | decay_load();2) 1.458 us | decay_load();2) 1.750 us | decay_load();2) | __accumulate_pelt_segments() {2) 1.459 us | decay_load();2) 1.750 us | decay_load();2) 7.583 us | }2) + 19.834 us | }2) | __update_load_avg_cfs_rq() {2) 1.750 us | decay_load();2) 1.458 us | decay_load();2) 1.459 us | decay_load();2) | __accumulate_pelt_segments() {2) 1.459 us | decay_load();2) 1.750 us | decay_load();2) 7.292 us | }2) + 19.542 us | }2) | update_cfs_group() {2) | reweight_entity() {2) 1.458 us | update_curr();2) 4.958 us | }2) 7.584 us | }2) 1.750 us | hrtimer_active();2) 1.750 us | capacity_of();2) ! 131.250 us | }2) 1.458 us | calc_global_load_tick();2) | _raw_spin_unlock() {2) 1.750 us | preempt_count_sub();2) 4.667 us | }2) 1.459 us | idle_cpu();2) | trigger_load_balance() {2) 1.458 us | nohz_balance_exit_idle();2) 1.458 us | __rcu_read_lock();2) 1.458 us | __rcu_read_unlock();2) + 11.375 us | }2) ! 185.792 us | }2) 1.750 us | run_posix_cpu_timers();2) ! 255.500 us | }2) 1.458 us | profile_tick();2) ! 261.625 us | }2) | hrtimer_forward() {2) 1.458 us | ktime_add_safe();2) 1.459 us | ktime_add_safe();2) 7.875 us | }2) ! 356.125 us | }2) | _raw_spin_lock_irq() {2) 1.458 us | preempt_count_add();2) 4.666 us | }2) 1.459 us | enqueue_hrtimer();2) ! 378.000 us | }2) | hrtimer_update_next_event() {2) | __hrtimer_get_next_event() {2) 1.750 us | __hrtimer_next_event_base();2) 4.667 us | }2) | __hrtimer_get_next_event() {2) 1.750 us | __hrtimer_next_event_base();2) 4.375 us | }2) + 13.709 us | }2) | _raw_spin_unlock_irqrestore() {2) 1.458 us | preempt_count_sub();2) 4.667 us | }2) | tick_program_event() {2) | clockevents_program_event() {2) | ktime_get() {2) 1.459 us | arch_counter_read();2) 4.667 us | }2) 1.458 us | arch_timer_set_next_event_phys();2) + 10.792 us | }2) + 14.000 us | }2) ! 431.083 us | }2) ! 434.292 us | }2) 1.750 us | gic_eoimode1_eoi_irq();2) ! 441.000 us | }2) | irq_exit() {2) 1.750 us | preempt_count_sub();2) 1.750 us | idle_cpu();2) 8.458 us | }2) ! 475.125 us | }2) ! 478.625 us | }2) <========== |2) 5.250 us | __module_get();2) ! 498.458 us | }2) 4.375 us | __mutex_init();2) | nfs_init_fs_context() {2) | kmem_cache_alloc_trace() {2) 5.542 us | should_failslab();2) + 16.625 us | }2) | nfs_alloc_fhandle() {2) | kmem_cache_alloc_trace() {2) 4.083 us | should_failslab();2) + 14.584 us | }2) + 22.750 us | }2) + 53.083 us | }2) ! 598.208 us | }2) ! 606.958 us | }2) | put_filesystem() {2) 4.375 us | module_put();2) + 13.125 us | }2) | vfs_parse_fs_string() {2) | kmemdup_nul() {2) | __kmalloc_track_caller() {2) 4.083 us | kmalloc_slab();2) 4.375 us | should_failslab();2) + 21.875 us | }2) + 30.625 us | }2) | vfs_parse_fs_param() {2) 6.125 us | lookup_constant();2) 5.250 us | lookup_constant();2) | nfs_fs_context_parse_param() {2) | __fs_parse() {2) 4.375 us | fs_param_is_string();2) + 19.833 us | }2) + 30.917 us | }2) + 62.417 us | }2) 5.250 us | kfree();2) ! 116.959 us | }2) | parse_monolithic_mount_data() {2) | nfs_fs_context_parse_monolithic() {2) | generic_parse_monolithic() {2) | vfs_parse_fs_string() {2) | vfs_parse_fs_param() {2) 4.375 us | lookup_constant();2) 4.375 us | lookup_constant();2) | nfs_fs_context_parse_param() {2) + 10.209 us | __fs_parse();2) + 19.250 us | }2) + 48.125 us | }2) 4.667 us | kfree();2) + 66.500 us | }2) + 77.875 us | }2) + 87.209 us | }2) + 95.667 us | }2) | mount_capable() {2) | capable() {2) | ns_capable() {2) | ns_capable_common() {2) 5.541 us | cap_capable();2) + 14.000 us | }2) + 22.750 us | }2) + 31.209 us | }2) + 41.125 us | }2) | vfs_get_tree() { // error2) | nfs_get_tree() {2) 4.375 us | nfs_verify_server_address();2) + 14.000 us | }2) + 22.750 us | }2) | put_fs_context() {2) | }
结合 mount()
系统调用代码路径:
sys_mount()do_mount()path_mount()do_new_mount()static int do_new_mount(struct path *path, const char *fstype, int sb_flags,int mnt_flags, const char *name, void *data)
{...type = get_fs_type(fstype);...fc = fs_context_for_mount(type, sb_flags);put_filesystem(type);...if (!err && name)err = vfs_parse_fs_string(fc, "source", name, strlen(name));if (!err)err = parse_monolithic_mount_data(fc, data);...if (!err && !mount_capable(fc))err = -EPERM;if (!err)err = vfs_get_tree(fc);if (!err)err = do_new_mount_fc(fc, path, mnt_flags);put_fs_context(fc);return err;
}
从 mount()
系统调用代码路径了解到,函数 vfs_get_tree()
调用后,没出错的情况应该调用 do_new_mount_fc()
,但 ftrace
跟踪到的流程是在 vfs_get_tree()
后接着调用了 put_fs_context()
,这表示 vfs_get_tree()
调用出错了。为什么?看一下 vfs_get_tree()
的实现:
int vfs_get_tree(struct fs_context *fc)
{...error = fc->ops->get_tree(fc); /* NFS: nfs_get_tree() */...
}
static int nfs_get_tree(struct fs_context *fc)
{struct nfs_fs_context *ctx = nfs_fc2context(fc);int err = nfs_fs_context_validate(fc);...
}static int nfs_fs_context_validate(struct fs_context *fc)
{struct nfs_fs_context *ctx = nfs_fc2context(fc);...struct sockaddr *sap = (struct sockaddr *)&ctx->nfs_server.address;...if (!nfs_verify_server_address(sap))goto out_no_address;...
out_no_address:return nfs_invalf(fc, "NFS: mount program didn't pass remote address"); // 返回 EINVAL 错误码...
}static int nfs_verify_server_address(struct sockaddr *addr)
{switch (addr->sa_family) {case AF_INET: {struct sockaddr_in *sa = (struct sockaddr_in *)addr;return sa->sin_addr.s_addr != htonl(INADDR_ANY);}case AF_INET6: {struct in6_addr *sa = &((struct sockaddr_in6 *)addr)->sin6_addr;return !ipv6_addr_any(sa);}}dfprintk(MOUNT, "NFS: Invalid IP address specified\n");return 0;
}
原因是 nfs_verify_server_address()
没有检查到合法的地址协议簇,导致出错,返回错误码 EINVAL
。那谁设置了 addr->sa_family
?在挂载期间,nfs_init_fs_context()
将 addr->sa_family
的初始值为 0
:
sys_mount()do_mount()path_mount()do_new_mount()fs_context_for_mount()alloc_fs_context()init_fs_context = fc->fs_type->init_fs_context; /* NFS: nfs_init_fs_context() */nfs_init_fs_context()
static int nfs_init_fs_context(struct fs_context *fc)
{struct nfs_fs_context *ctx;/** 整个 nfs_fs_context 初始为 0,包括* nfs_fs_context::nfs_server::address::sa_family,* 即 nfs_verify_server_address() 调用中的 addr->sa_family 。*/ctx = kzalloc(sizeof(struct nfs_fs_context), GFP_KERNEL);...
}
mount
时想设置 addr->sa_family
参数,可以通过挂载选项 addr=<IP地址>
来完成:
static int nfs_fs_context_parse_param(struct fs_context *fc,struct fs_parameter *param)
{...opt = fs_parse(fc, nfs_fs_parameters, param, &result);...switch (opt) {...case Opt_addr:len = rpc_pton(fc->net_ns, param->string, param->size,&ctx->nfs_server.address,sizeof(ctx->nfs_server._address));if (len == 0)goto out_invalid_address;ctx->nfs_server.addrlen = len;break;...}...
}
按代码分析,按如下重新调整命令行选项:
# mount -t nfs -o addr=192.168.0.36,nolock 192.168.0.36:/home/XXX/nfs-share /test/nfs-remote
挂载成功。对比前面的挂载命令,这里增加了 addr=192.168.0.36
挂载选项,来显式的指定 NFS 服务端
的 IPv4
地址。当然这个也可通过 addr=
选项指定 IPv6
风格地址。
最后,值得一提的是,可以通过内核配置项 CONFIG_SUNRPC_DEBUG
,启用 NFS
的调试信息。需要通过操作 /proc/sys/sunrpc/nfs_debug
来指定输出的调试信息类型,如通过下面命令启用 NFS
挂载相关的调试信息:
echo 0x0400 > /proc/sys/sunrpc/nfs_debug
0x0400
即 NFSDBG_MOUNT
,这些值定义在 include/uapi/linux/nfs_fs.h
中:
/** NFS debug flags*/
#define NFSDBG_VFS 0x0001
#define NFSDBG_DIRCACHE 0x0002
#define NFSDBG_LOOKUPCACHE 0x0004
#define NFSDBG_PAGECACHE 0x0008
#define NFSDBG_PROC 0x0010
#define NFSDBG_XDR 0x0020
#define NFSDBG_FILE 0x0040
#define NFSDBG_ROOT 0x0080
#define NFSDBG_CALLBACK 0x0100
#define NFSDBG_CLIENT 0x0200
#define NFSDBG_MOUNT 0x0400
#define NFSDBG_FSCACHE 0x0800
#define NFSDBG_PNFS 0x1000
#define NFSDBG_PNFS_LD 0x2000
#define NFSDBG_STATE 0x4000
#define NFSDBG_ALL 0xFFFF
当然,自然也少不了 NFS
定义的 tracepoint
等一些其他调试接口,对这些细节感兴趣的读者可自行查阅相关资料和源码。