Linux系统

[Linux]

[1] 目录

net                    网络协议栈及socket

fs                     文件系统实现(vfs、具体文件系统)及编程接口(系统调用)

init                   系统初始化核心代码,不能裁剪

kernel                 系统核心代码, 进程管理,不能裁剪

ipc                    进程间通信实现(消息队列、信号量和共享内存)

mm                     内存管理

arch(经常需要添加代码) 架构相关代码

boot                 内启动前的准备(分离内核和ramdisk、自解压)代码

kernel               cpu需要的核心代码,内核真正的启动代码在head.S

mm                   cpu相关的内存管理代码

lib                  cpu相关的标准库代码

configs              支持的开发板的默认配置文件

include              cpu相关的代码的头文件

plat-VENDOR          某个厂家芯片需要用到的公共代码

include

plat-SOC系列         芯片厂家的一个系列SOC的公共代码

include

mach-SOC             某个SOC用到的源代码

include

drivers(经常需要添加代码)通用设备驱动

include                 头文件

scripts                 编译配置脚本

Docutmentation          作者写的文档

[2] 文件

源文件

*.c/*.S/*.h            源代码

Makefile               编译规则

Kconfig                配置脚本

目标文件

.config                内核当前配置结果(选取哪些源代码)

*.o                    目标文件

System.map             符号表

vmlinux                ELF格式的可执行程序(带调试信息)

arch/arm/boot/Image    Binaray格式可执行程序

arch/arm/boot/zImage   压缩的内核的二进制文件(带自解压算法)

[3] 特点

1. 支持多种CPU

2. 支持多种硬件驱动

任何一款嵌入式产品,都不会用到所有的源代码,所以用源码生成目标文件的步骤:

1. 选取需要的源代码 -- 配置

2. 编译选取的源代码 -- 编译

[4] 编译

1. 配置

$ cp arch/arm/configs/s5pc100 .config

$ make menuconfig

2. 编译

make

[5] 编译原理

1. 过程

见《内核编译流程》

2. 结果

见《[2] 文件》

3. 模块的Makefile

obj-y := 加入这个变量的*.o文件,被静态编译到内核(链接到zImage中)

obj-m := 加入这个变量的*.o文件, 被动态编译到内核(链接到*.ko中)

[6] 配置原理

1. 控制目录/文件是否编译

(1) 目录

例: CONFIG_SPI

在.config里面查看CONFIG_SPI配置项值:

CONFIG_SPI=y                        静态选中

# CONFIG_SPI ...                    没有选中

在drivers/Makefile中,查看控制目录编译的方法:

obj-$(CONFIG_SPI) += spi/

(2) 文件

例: CONFIG_SPI_MASTER

在.config里面查看CONFIG_SPI_MASTER配置项值:

CONFIG_SPI_MASTER=y                 静态选中

CONFIG_SPI_MASTER=m                 动态选中

# CONFIG_SPI_MASTER ...             没有选中

在drivers/spi/Makefile中,查看控制目录编译的方法:

obj-$(CONFIG_SPI_MASTER) += spi.o

2. 选中文件中的一部分代码(条件编译)

例:

#define CONFIG_DEBUG_LL (通过是否有这个宏定义来控制代码是否被编译到内核)

#ifdef CONFIG_DEBUG_LL (arch/arm/kernel/head.S)

....

#endif

在.config里面查看CONFIG_DEBUG_LL配置项值:

CONFIG_DEBUG_LL=y                   静态选中

编译时,会被转义成:

#define CONFIG_DEBUG_LL 1 (include/generated/autoconf.h)

# CONFIG_DEBUG_LL=y                 不选中

编译时,会被转义成:

// #define CONFIG_DEBUG_LL 1 (include/generated/autoconf.h)

3. 提供数据给源代码(宏替换)

例: CONFIG_CMDLINE

在.config中,查看CONFIG_CMDLINE配置项值:

CONFIG_CMDLINE="root=/dev/mtdblock2 rootfstype=cramfs init=/linuxrc console=ttySAC2,115200"

编译时,被转义成:

#define CONFIG_CMDLINE "root=/dev/mtdblock2 rootfstype=cramfs init=/linuxrc console=ttySAC2,115200"

4. Kconfig语法原理

1. 配置项值

<>     tristate

y         静态选中

m         动态选中

n         不选中

[]     bool

y         静态选中

n         不选中

()     string

2. 语法解释及例子

见《Kconfig》

[7] 如何添加模块到工程中?(必须掌握)

1. 添加源代码到对应目录

例: drivers/hello/module_hello.c

2. 给子目录添加Makefile(或者直接修改)

obj-y += module_hello.o                         表示静态编译到内核

obj-m += module_hello.o                         表示动态编译到*.ko

obj-$(CONFIG_HELLO) += module_hello.o           根据图形菜单的配置决定静态/动态编译到内核

3. 修改上一级目录下的Makefile

obj-y       += hello/

4. 添加Kconfig配置脚本

config HELLO

tristate "hello config"

help

hello config help

5. 修改上一级目录下的Kconfig

source "drivers/hello/Kconfig"

[9] 系统启动流程

[kernel]

1. 检查内核是否支持正在运行的CPU(A8)

[方法]

(1) 读取CPU内部的id

(2) 调用__lookup_processor_type子程序检查内核是否支持该CPU

(3) 如果内核不支持该CPU,打印‘P‘类型错误(Error: unrecognized/unsupported processor variant(当前运行的CPU ID))

(4) 如果支持该CPU,继续运行

错误原因: 操作系统不支持当前CPU所致,一般不会出现

解决办法: 修改操作系统(ARM公司或操作系统厂家)

2. 检查内核是否支持正在运行的主板

(1) 主板id(arch number / mach type id)存储在R1(BootLoader放置)

(2) 调用__lookup_mach_type子程序检查内核是否支持该主板

(3) 如果内核不支持该CPU,打印‘A‘类型错误

(Error: unrecognized/unsupported machine ID (r1 = BootLoader传输的主板id).

Available machine support:\n\nID (hex)\tNAME

Please check your kernel config and/or bootloader.)

(4) 如果内核支持该主板,继续运行

错误原因: 1. 操作系统确实不支持改主板,交给芯片厂家完成(概率非常小)

2. bootloader传输的id错误

解决办法: 检查BootLoader传入内核的arch number是否正确

(1) 根据主板名字到include/generated/mach-types.h文件中查出当前主板ID

(2) 对比当前主板ID和A错误中打印的R1寄存器的值

3. 循环调用驱动模块(静态加载)的初始化函数

错误原因: 驱动的初始化函数有问题,导致内核崩溃(OOPS), 崩溃驱动代码检测方法

解决办法: 见《内核调试方法》

4. 打开console终端,并且把它设置成内核的标准输入、标准输出和标准错误

错误现象:  unable to open an intial console.

错误原因: /dev/console不存在(文件系统制作时必须创建)

5. 挂载根文件系统

错误现象:

Root-NFS: No NFS server available, giving up.

VFS: Unable to mount root fs via NFS, trying floppy.

VFS: Cannot open root device "mtdblock0" or unknown-block(2,0)

Please append a correct "root=" boot option; here are the available partitions:

Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(2,0)

错误原因: (1) root=参数指定的存放文件系统的设备不存在

(2) 设备上没有存储文件系统(或文件系统类型操作系统不支持 或文件系统错误)

解决办法: (1) 检查root=参数设置的设备是否正确

(2) 设备上存储的文件系统是否正确

6. 启动init进程

错误现象: (1) Failed to execute /xxxy.  Attempting defaults...

(2) NO init found. .......

错误原因: (1) init=参数指定的程序无法启动

(2) init=参数指定的程序有问题

&& /sbin/init不存在

&& /etc/init不存在

&& /bin/init不存在

&& /bin/sh不存在

解决办法: (1) 检查init=参数指定的程序是正确

(2) 检查init=参数指定的程序是正确

[rootfs]

7. /etc/inittab(init进程解析执行)

终端::动作:程序或脚本

[终端]

省略名字/dev/, 脚本之前不能有任何字符(包括空格):

例:

console::sysinit:/etc/init.d/rcS(console前面不能有任何字符,包括空格)

[动作]

sysinit                     指定系统初始化阶段执行的程序或脚本

askfirst(respawn)           指定系统正常运行阶段,需要一直执行的程序或脚本

ctrlaltdel                  指定在按Ctrl + Alt +Del组合键时运行的程序(只能在标准终端上起作用)

restart                     指定系统重启前运行的脚本或脚本

shutdown                    指定系统关闭前运行的程序或脚本

[程序或脚本]

应用程序或shell脚本

8. /etc/init.d/rcS

#!/bin/sh                   脚本解析器

/bin/mount  -a              挂载/etc/fstab指定的文件系统

告诉内核mdev程序的路径,系统有热插拔设备时,内核会调用mdev程序新建或删除对应设备文件节点

echo /sbin/mdev > /proc/sys/kernel/hotplug

启动mdev程序,扫描系统识别的设备,并且新建设备文件节点

/sbin/mdev  -s

/etc/fstab内容

存放文件系统的设备       目录    文件系统该类型

proc                     /proc   proc             defaults        0        0

tmpfs                    /tmp    tmpfs            defaults        0        0

sysfs                    /sys    sysfs            defaults        0        0

tmpfs                    /dev    tmpfs            defaults        0        0

tmpfs挂载时,文件系统为空,当向文件系统写入数据时,所有数据文件都存储到内存

9. /etc/profile

#!/bin/sh                                               脚本解析器

export HOSTNAME=farsight                                机器名

export USER=root                                        用户名

export HOME=root                                        用户主目录

export PS1="[[email protected]$HOSTNAME \W]\# "                    命令提示符

PATH=/bin:/sbin:/usr/bin:/usr/sbin                      PATH环境变量

LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH          动态库的搜索路径

export PATH  LD_LIBRARY_PATH

注意:本脚本被登录程序调用,但是嵌入式设备中,很多没有配置登录程序,这时希望shell脚本直接执行

本脚本(需要在inittab中,启动shell程序时,在前面加上‘-‘)

10. 必须要的设备文件节点

# sudo mknod /dev/console c(字符设备) 5(主设备号 Documentation/devices.txt) 1(次设备号)

# sudo mknod /dev/null c 1 3

[10] 移植

BSP?

描述主板硬件配置的数据结构集合,也就是说它的主要功能告诉操作系统准备的硬件配置

[11] Linux应用程序调试

1. gdb远程调试

(1) 利用开发板的交叉编译器,编译要调试的程序(编译过程中要加-g参数,-O0)

# arm-cortex_a8-linux-gnueabi-gcc hello.c -o hello -g -O0

(2) 拷贝编译出来的可执行代码到开发板的root目录

# sudo cp hello /source/rootfs/root

(3) 在交叉编译器的目录下寻找gdbserver(在开发板上能运行的程序)程序

# find ~/toolchain -depth -name gdbserver

(4) 拷贝gdbserver到开发板

# sudo cp ~/toolchain/arm-cortex_a8-linux-gnueabi/debug-root/usr/bin/gdbserver /source/rootfs/root/

(5) 在开发板上运行gdbserver程序加载要调试的程序

$ ./gdbserver 192.168.0.7(开发板ip地址):1234(端口号) hello(调试的可执行程序)

(6) 在PC上,启动gdb(在PC上运行的gdb程序,也是有交叉编译器提供)并且加载要调试的可执行程序

# arm-cortex_a8-linux-gnueabi-gdb hello(要调试的程序)

(7) 在gdb程序中,远程连接开发板gdbserver:

(gdb) target remote 192.168.0.7:1234

(8) 下断点,然后让开发板调试程序继续运行

2. 段错误调试(非常有用)

(1) 利用开发板的交叉编译器,编译要调试的程序(编译过程中要加-g参数,-O0)

# arm-cortex_a8-linux-gnueabi-gcc hello.c -o hello -g -O0

(2) 拷贝编译出来的可执行代码到开发板的root目录

# sudo cp hello /source/rootfs/root

(3) 在开发板上打开core文件限制,然后运行程序

$ ulimit -c unlimited

$ ./hello

出现段错误,产生core文件

(4) 在交叉编译器的目录下寻找gdb(在开发板上能运行的程序)程序

# find ~/toolchain -depth -name gdb

# file gdb(确保是开发板上可以运行的gdb程序)

(5) 拷贝gdb到开发板

# sudo cp ~/toolchain/arm-cortex_a8-linux-gnueabi/debug-root/usr/bin/gdb /source/rootfs/root/

(6) 在开发板上运行gdb程序解析core文件

$ ./gdb hello(带调试信息的执行程序) core(产生的core文件)

$ bt(查看详细的代码出错点)

3. 内存操作错误(有用)

核心用法: memwatch.c监控工程中,内存操作错误,没有发生段错误退出

见《实验十二》

[12] 内核调试

1. 内核汇编启动代码调试(汇编阶段)

(1) 打开MMU之前

led调试 -- 用led灯的亮灭来显示代码运行到哪个位置了

直流蜂鸣器 -- 用蜂鸣器是否响,显示代码运行到哪个位置了

UART调试(BootLoader使用的console) -- 通过打印字符来代码运行到哪个位置了

(2) 打开MMU之后,进入C程序之前的汇编调试,只能用UART

内核启动初期,只建立了1M的内存映射

注意:汇编启动代码基本不会出错

2. C代码部分

[printk](必须掌握)

1. 用法

printk("<级别>""hello\n");

[级别]

0 - 7 数字越小级别越高

[注意]

(1) 不支持浮点

(2) 打印到__log_buf

2. 相关级别

int console_printk[4] = {

DEFAULT_CONSOLE_LOGLEVEL,          // console_printk[0] console级别

DEFAULT_MESSAGE_LOGLEVEL,          // console_printk[1] 消息默认级别,printk没有指定级别时

MINIMUM_CONSOLE_LOGLEVEL,          // console_printk[2] console最高级别

DEFAULT_CONSOLE_LOGLEVEL,          // console_printk[3] 默认console级别

};

四个级别可以通过应用程序空间的/proc/sys/kernel/printk文件查看或者修改

查看命令:cat /proc/sys/kernel/printk

修改命令:echo "6 5" > /proc/sys/kernel/printk

3. 消息的级别高于console级别,从console输出

实验:

1. printk_test动态编译到内核

(1) 拷贝printk_test.c 到内核的drivers/char/

(2) 在drivers/char/Makefile前面加入, obj-m += printk_test.o

(3) # make

(4) # cp arch/arm/zImage /tftpboot

(5) # cp drivers/char/printk_test.ko /source/rootfs/lib/modules/2.6.35

2. 重启开发板

3. 加载printk_test.ko模块,观察哪些消息从console输出,然后卸载printk_test.ko

加载命令:insmod /lib/modules/2.6.35/printk_test.ko

卸载命令:rmmod printk_test

4. 修改console口级别为6,然后再次加载printk_test.ko模块,观察哪些消息从console输出

4.  消息从/proc/kmsg输出实验

1. 关闭klogd程序,重启开发板

在/etc/init.d/rcS中注释以下两行:

# klogd&

# syslogd&

2. 加载printk_test.ko模块

$ insmod /lib/modules/2.6.25/printk_test.ko

3. cat /proc/kmsg 打印内核消息

4. cat /proc/kmsg 再次打印内核消息,观察结果

5.  消息被klogd和syslogd程序读到/var/log/messeges

1. 在linux启动脚本(/etc/init.d/rcS)中,启动klogd和syslogd

脚本如下:

klogd&

syslogd&

2. 加载printk_test.ko模块

$ insmod /lib/modules/2.6.25/printk_test.ko

3. 查看/var/log/messeges内核消息

cat /var/log/messeges

tail /var/log/messeges

[oops](内核发生段错误)(必须掌握)

(1) 产生oops的方法,见《实验十三》

(2) oops分析

// 原因

Unable to handle kernel NULL pointer dereference at virtual address 00000000

// 页目录表及其内容

pgd = c0004000

[00000000] *pgd=00000000

// oops内部编号

Internal error: Oops: 805 [#1]

last sysfs file:

Modules linked in:

// cpu现场(寄存器值)

CPU: 0    Not tainted  (2.6.35 #15)

PC is at dm9000_probe+0x20/0x8c8

LR is at platform_drv_probe+0x18/0x1c

pc : [<c020ef74>]    lr : [<c015f874>]    psr: a0000013

sp : cfc29ef0  ip : 00000064  fp : 00000000

r10: 00000000  r9 : 00000000  r8 : 00000000

r7 : c02c5fa8  r6 : c02c5ec8  r5 : c02c5ed0  r4 : c02c5ed0

r3 : 000000ff  r2 : 00000001  r1 : 00000001  r0 : 000000ec

Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment kernel

Control: 10c5387d  Table: 20004019  DAC: 00000017

Process swapper (pid: 1, stack limit = 0xcfc28268)

// kernel_init内核线程栈里面的数据

Stack: (0xcfc29ef0 to 0xcfc2a000)

9ee0:                                     00000000 cfc29f00 cfc472d0 00000000

9f00: c02c5f04 c02c5ed0 c02c5ed0 c02deeec c02deeec 00000000 00000000 00000000

9f20: 00000000 c015f874 c02c5ed0 c015e99c c02c5ed0 c02c5f04 c02deeec 00000000

9f40: 00000000 c015eaac c02deeec cfc29f58 c015ea4c c015e234 cfc01d08 cfc46bc0

9f60: c02deeec c02deeec cfce1200 c02deb60 00000000 c015db28 c02768b0 c02768b0

9f80: cfc24000 c02deeec c001f128 00000000 00000013 00000000 00000000 c015ed7c

9fa0: c001973c c001f128 00000000 00000013 00000000 c0023380 c001973c c00223a0

9fc0: c02e53c0 c02e53c0 c001f0ac c001f128 c0024e1c 00000000 00000000 00000000

9fe0: 00000000 c000857c 00000000 c00084e0 c0024e1c c0024e1c fadafb7d 7bffafdd

// 函数调用历史

[<c020ef74>] (dm9000_probe+0x20/0x8c8) from [<c015f874>] (platform_drv_probe+0x18/0x1c)

[<c015f874>] (platform_drv_probe+0x18/0x1c) from [<c015e99c>] (driver_probe_device+0xa8/0x158)

[<c015e99c>] (driver_probe_device+0xa8/0x158) from [<c015eaac>] (__driver_attach+0x60/0x84)

[<c015eaac>] (__driver_attach+0x60/0x84) from [<c015e234>] (bus_for_each_dev+0x48/0x84)

[<c015e234>] (bus_for_each_dev+0x48/0x84) from [<c015db28>] (bus_add_driver+0x98/0x214)

[<c015db28>] (bus_add_driver+0x98/0x214) from [<c015ed7c>] (driver_register+0xac/0x13c)

[<c015ed7c>] (driver_register+0xac/0x13c) from [<c0023380>] (do_one_initcall+0x58/0x1b0)

[<c0023380>] (do_one_initcall+0x58/0x1b0) from [<c000857c>] (kernel_init+0x9c/0x150)

[<c000857c>] (kernel_init+0x9c/0x150) from [<c0024e1c>] (kernel_thread_exit+0x0/0x8)

(3) 根据错误信息,找到促发oops的源码

arm-cortex_a8-linux-gnueabi-addr2line 0xc020ef74(PC值,地址) -e vmlinux(带调试信息的ELF可执行文件) -f

(4) 分析错误

(1) 促发oops的源代码不一定是产生错误的源代码

(2) 如果不是,可以参考函数调用历史,回溯查找前面的函数看代码(特别是自己写的代码)是否有问题

(3) 更改的代码和促发错误的代码的关系

(4) 认真查看更改代码

[panic]

panic()函数打印消息,并挂死

时间: 2024-11-03 14:54:33

Linux系统的相关文章

查看Linux系统版本信息

一.查看Linux内核版本命令(两种方法): 1.cat /proc/version [[email protected]CentOS home]# cat /proc/versionLinux version 2.6.32-431.el6.x86_64 ([email protected]) (gcc version 4.4.7 20120313 (Red Hat 4.4.7-4) (GCC) ) #1 SMP Fri Nov 22 03:15:09 UTC 2013 2.uname -a [

制作SD(8G)卡Linux镜像,使得ZC706开发板可以从SD卡启动进入Linux系统

转自网络,供学习记录使用,红色部分是我实验时,这篇文章和网站稍有出入的地方. 目的:制作SD(8G)卡Linux镜像,使得ZC706开发板可以从SD卡启动进入Linux系统 在http://wiki.analog.com/resources/eval/user-guides/ad-fmcomms2-ebz/quickstart/zynq(姑且把这个链接成为链接1吧)链接中找到 图1 点击绿色字体的链接,下载镜像原始文件.这里有不同时期的版本,本说明中选择 图2 下载的原始文件为:2014_R2-

Linux系统基础.作业

要求以root用户登录系统,右击桌面打开终端,查看当前登陆Linux系统所使用的用户名 查看哪些用户在系统上工作 修改当前时间为2018年8月26号11:28 查看2015年10月份日历 使用两种方法查看ls命令的使用说明 清除屏幕 ctrl+L 使用"useradd tom"命令新建tom用户,为tom用户设置密码"123" 切换当前用户为tom 查看当前登陆Linux系统所使用的用户名

小白文科生眼中的Linux系统

我是一个正统的文科生,基本上在win的基础上长大,最开始接触的是Windows98(95虽然知道,但那时候太小了)然后是me,2000,再到XP,然后是vista,再然后就是使用量最大的7,然后是8,8.1,10,...基本上每个版本都用(玩)过,对win也可以说是有了基础的了解,13年暑假的时候,Android迅速崛起,从那个时候,刚刚知道Linux.在然后就开始疯狂的搜索Linux 的资料,那时候刚刚高中毕业,网上的资料基本都看不懂,虽然看不懂但还是找的津津有味. 记得2013年最先开始了解

老男孩教育每日一题-2017年5月11-基础知识点: linux系统中监听端口概念是什么?

1.题目 老男孩教育每日一题-2017年5月11-基础知识点:linux系统中监听端口概念是什么? 2.参考答案 监听端口的概念涉及到网络概念与TCP状态集转化概念,可能比较复杂不便理解,可以按照下图简单进行理解? 将整个服务器操作系统比喻作为一个别墅 服务器上的每一个网卡比作是别墅中每间房间 服务器网卡上配置的IP地址比喻作为房间中每个人 而房间里面人的耳朵就好比是监听的端口 当默认采用监听0.0.0.0地址时,表示房间中的每个人都竖起耳朵等待别墅外面的人呼唤当别墅外面的用户向房间1的人呼喊时

&#8203;查看Linux系统的所有配置命令

查看Linux系统的所有配置命令     1.查看主板的序列号: dmidecode | grep -i 'serial number'     2.查看CPU信息: cat /proc/cpuinfo dmesg | grep -i 'cpu' dmidecode -t processor     3.查看内存信息: cat /proc/meminfo free -m vmstat     5.查看网卡信息: dmesg | grep -i 'eth' cat /etc/sysconfig/h

Linux系统下的shutdown命令用于安全的关闭/重启计算机

Linux系统下的shutdown命令用于安全的关闭/重启计算机,它不仅可以方便的实现定时关机,还可以由用户决定关机时的相关参数.在执行shutdown命令时,系统会给每个终端(用户)发送一条屏显,提示关机操作.定时关机只需要一个简单的参数,既可以是倒计时,也可以是确切的时间. 命令格式 1 shutdown [选项] [时间] [消息] 并有如下选项: - k 不执行任何关机操作,只发出警告信息给所有用户 - r 重新启动计算机 - h 关机并彻底断电 - f 快速关机且重启动时跳过fsck

自动调整linux系统时间和时区与Internet时间同步

调整linux系统时间和时区与Internet时间同步 一.修改时区:# cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime修改为中国的东八区# vi /etc/sysconfig/clockZONE="Asia/Shanghai"UTC=falseARC=false 二.配置新的时间日期设定:# date -s 2008/05/06 时间设定:# date -s 18:40:00 查看硬件时间(BIOS的):    hwclock

Linux系统时间管理

一.时区配置 显示时区 [[email protected] ~]# date -R Mon, 19 Dec 2016 14:02:47 +0800 [[email protected] ~]# [[email protected] ~]# date +%z +0800 [[email protected] ~]# 主要就是后面的+0800,东八区 修改时区 [[email protected] ~]# vim /etc/sysconfig/clock [[email protected] ~]

VMware 中linux系统上搭载ftp服务器

本文讲述最基本vsftp搭载方法,另有yum命令快速安装另外深究 1.下载安装VMWare,安装linux系统 2.获取vsftpd安装包 安装盘的packages包里面找到所需vsftp组件,复制到linux目录下 使用命令:rpm -vih rpm文件名 安装vsftpd 3.使用su 命令进入root用户,使用adduser test,passwd test添加同户名密码 4.在windows dos 下使用ftp ip 访问linux下的ftp服务会发现无法访问ftp 这是因为Linux