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()),看哪里出错了(追踪内容有删减,只展示了出错调用的主干流程):

# 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() { // 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

0x0400NFSDBG_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 等一些其他调试接口,对这些细节感兴趣的读者可自行查阅相关资料和源码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值