Linux:NFS 无法挂载异常案例 (1)
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()
),看哪里出错了(追踪内容有删减,只展示了出错调用的主干流程
):
# 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() { // error
2) | 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_VFS0x0001
#define NFSDBG_DIRCACHE0x0002
#define NFSDBG_LOOKUPCACHE0x0004
#define NFSDBG_PAGECACHE0x0008
#define NFSDBG_PROC0x0010
#define NFSDBG_XDR0x0020
#define NFSDBG_FILE0x0040
#define NFSDBG_ROOT0x0080
#define NFSDBG_CALLBACK0x0100
#define NFSDBG_CLIENT0x0200
#define NFSDBG_MOUNT0x0400
#define NFSDBG_FSCACHE0x0800
#define NFSDBG_PNFS0x1000
#define NFSDBG_PNFS_LD0x2000
#define NFSDBG_STATE0x4000
#define NFSDBG_ALL0xFFFF
当然,自然也少不了 NFS
定义的 tracepoint
等一些其他调试接口,对这些细节感兴趣的读者可自行查阅相关资料和源码。
原文地址:https://blog.csdn.net/JiMoKuangXiangQu/article/details/143489460
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!