2.5 Linux启动

来源:互联网 发布:淘宝联盟登录 编辑:程序博客网 时间:2024/06/14 09:13

1、内核组成

Linux的内核源码经过编译、链接以后,最原始的内核映像为vmlinux。而在实际使用的过程中为了启动的需要,会使用压缩过的内核zImage、bzImage,zImage对应小内核(内核启动地址为0x10000,大小不超过512K,即地址范围0x10000 – 0x8ffff),bzImage对应大内核(内核启动地址为0x100000),一般使用的都是大内核bzImage。

首先我们来看看内核文件是怎样生成的,我们使用2.4内核i386的makefile来解析内核文件的生成过程。2.6内核的生成方法原理上和2.4内核是一样的,只是makefile的组织方法不一样。2.4的makefile比较容易懂,所以用2.4的来说明。

1.1、vmlinux

$(TOPDIR)/makefile :vmlinux: include/linux/version.h $(CONFIGURATION) init/main.o init/version.o init/do_mounts.o linuxsubdirs    $(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o init/do_mounts.o \        --start-group \        $(CORE_FILES) \        $(DRIVERS) \        $(NETWORKS) \        $(LIBS) \        --end-group \        -o vmlinux    $(NM) vmlinux | grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | sort > System.map$(TOPDIR)/ arch/i386/makefile :HEAD := arch/i386/kernel/head.o arch/i386/kernel/init_task.o    

由此可以看到vmlinux由arch/i386/kernel/head.o arch/i386/kernel/init_task.o和其他内核的各相关部分连接到一起。

1.2、bzImage

  • step1 :
$(TOPDIR)/ arch/i386/boot/ makefile :bzImage: $(CONFIGURE) bbootsect bsetup compressed/bvmlinux tools/build    $(OBJCOPY) compressed/bvmlinux compressed/bvmlinux.out    tools/build -b bbootsect bsetup compressed/bvmlinux.out $(ROOT_DEV) > bzImage

使用objcopy将compressed/bvmlinux转换成bin文件compressed/bvmlinux.out。

使用tools/build工具将bbootsect、bsetup、compressed/bvmlinux.out链接成bzImage。bbootsect来至于i386/boot/bootsect.s,bsetup来至于i386/boot/setup.s。

  • Step2 :
$(TOPDIR)/ arch/i386/boot/compressed/makefile :bvmlinux: piggy.o $(OBJECTS)    $(LD) $(BZLINKFLAGS) -o bvmlinux $(OBJECTS) piggy.oHEAD = head.oSYSTEM = $(TOPDIR)/vmlinuxOBJECTS = $(HEAD) misc.o

compressed/bvmlinux的生成方法为将compressed/head.o、compressed/misc.o、compressed/piggy.o链接到一起。

piggy.o:    $(SYSTEM)    tmppiggy=_tmp_$$$$piggy; \    rm -f $$tmppiggy $$tmppiggy.gz $$tmppiggy.lnk; \$(OBJCOPY) $(SYSTEM) $$tmppiggy; \    gzip -f -9 < $$tmppiggy > $$tmppiggy.gz; \    echo "SECTIONS { .data : { input_len = .; LONG(input_data_end - input_data) input_data = .; *(.data) input_data_end = .; }}" > $$tmppiggy.lnk; \$(LD) -r -o piggy.o -b binary $$tmppiggy.gz -b elf32-i386 -T $$tmppiggy.lnk; \rm -f $$tmppiggy $$tmppiggy.gz $$tmppiggy.lnk

compressed/piggy.o文件的生成方法:

  • 使用objcopy工具将vmlinux文件转换成bin文件;
  • 将bin文件压缩成.gz文件;
  • 将.gz文件链接成piggy.o文件。

综上所述,bzImage的生成过程如下:

  • a、 将vmlinux转换成bin文件;
  • b、 将bin文件压缩成.gz文件;
  • c、 将.gz文件链接成piggy.o文件;
  • d、 将piggy.o和boot/compressed/head.s、boot/compressed/misc.c链接成boot/compressed/bvmlinux文件;
  • e、 将boot/compressed/bvmlinux文件转换成boot/compressed/bvmlinux.out bin文件;
  • f、 将boot/compressed/bvmlinux.out和boot/bootsect.s、boot/setup.s链接成bzImage文件。

2、启动过程

2.1、boot阶段

从上电开始到进入内核的start_kernel()函数之前,系统的启动流程如下:

  • 1、 系统上电,x86 cpu从0xFFFF0(FFFF :0000)地址开始执行,bios开始执行;
  • 2、 Bios完成上电自检、参数设置等一系列工作以后,将引导磁盘的MBR拷贝到0x07C00(0000:7C00)地址,并跳转到0x07C00处开始执行;
  • 3、 这里引导磁盘的MBR对应bzImage中的boot/bootsect.s。bootsect.S是生成引导扇区的汇编源码,它首先将自己拷贝到0x90000上,然后将紧接其后的setup部分(第二扇区)拷贝到0x90200,将真正的内核代码拷贝到0x100000。以上这些拷贝动作都是以bootsect.S、setup.S以及vmlinux在磁盘上连续存放为前提的,也就是说,我们的bzImage文件或者zImage文件是按照bootsect,setup, vmlinux这样的顺序组织,并存放于始于引导分区的首扇区的连续磁盘扇区之中。
  • 4、 bootsect.S完成加载动作后,就直接跳转到0x90200,这里正是setup.S的程序入口。 setup.S的主要功能就是将系统参数(包括内存、磁盘等,由BIOS返回)拷贝到 0x90000-0x901FF内存中,这个地方正是bootsect.S存放的地方,这时它将被系统参数覆盖。以后这些参数将由保护模式下的代码来读取。除此之外,setup.S还将video.S中的代码包含进来,检测和设置显示器和显示模式。最后,setup.S将系统转换到保护模式,并跳转到0x100000(对于bzImage格式的大内核是 0x100000,对于zImage格式的是0x1000)的内核引导代码。
  • 5、 对压缩的bzimage映像来说,0x100000这时存放着boot/compressed/head.s,然后它调用misc.c中定义的decompress_kernel()函数,使用 “lib/inflate.c”中定义的gunzip()将内核解压到0x100000,再转到其上执行 “arch/i386/kernel/head.S”中的startup_32代码。
  • 6、 arch/i386/kernel/head.S是真正的32位启动代码,其完成的主要工作有:

a、段地址准备
b、内核启动参数准备
c、初始化页面表
d、设置idt
e、检查cpu类型
f、跳转到start_kernel

2.2、start_kernel()

跳转到start_kernel以后,正式进入了linux内核的初始化工作,start_kernel()存在于init/main.c文件。

1
2
3

start_kernel()首先会调用架构相关的初始化函数setup_arch(),然后初始化中断、内存管理、设备管理、进程管理的一系列内部数据结构,最后调用rest_init()函数启动内核的init进程。

4

2.3、内核init进程

在内核init进程中,主要做这几件事:

  • 1、 执行内核模块的初始化函数(通过module_init()声明的函数);
  • 2、 加载ramdisk,并执行启动的初始化函数(如果是cpio-initrd,执行/init;如果是image-initrd,执行/linuxrc);
  • 3、 根据“root=xxx”启动参数加载实际的根文件系统,执行其中的用户态init程序(/sbin/init)。

    5
    6
    7

3、内存初始化

8
9
10
11

3.1、page_address_init()

3.2、machine_specific_memory_setup()

在系统boot的时候,kernel通过0x15中断获得机器内存容量。有三种参数88H(只能探测最大64MB的内存),E801H(得到大小),E802H(获得memory map)。这个memory map称为E820图,在kernel的初始化代码中会将这个memory map复制到一个kernel中的数据结构e820map里,kernel需要通过这个结构来计算可用的内存容量。

12
13
14
15

3.3、print_memory_map()

16

类似如:

17

3.4、setup_memory()

setup_memory ()的主要功能是计算出系统的低端内存、高端内存的相关变量。并初始化bootmem内存分配器。

18
19
20
21
22
23
24
25
26
27
28
29
30
31

3.5、paging_init()

paging_init ()的主要功能是初始化页表。

32
33
34
35

3.6、zone_sizes_init()

NUMA翻译成中文就叫“非对称内存访问体系”,其目的是为多CPU,或大型计算机集群提供一个分布式内存访问环境,而每一个分布式节点就叫做NODE。我们单机系统通常是UMA,即“对称内存访问体系”,其实就是只有一个NODE的环境。

而每个NODE下物理内存分成几个Zone(区域),Zone再对物理页面进行管理。所以,不管是我们的PC,还是大型集群服务器,只要安装了Linux操作系统,就是一个NODE->Zone->Page这样一个三层物理内存管理体系。

zone_sizes_init ()是对伙伴系统所使用的NODE->Zone->Page结构的实际变量node_data,所做的初始化。
再到mem_init()阶段,加入伙伴系统所能使用的内存区域。

36
37
38
39
40
41

3.7、register_memory()

42
43

3.8、mem_init()

该函数作用在zone_sizes_init ()中已经描述,添加内存区域到伙伴内存分配系统中。

44
45
46
47
48
49
50

3.9、kmem_cache_init()

该函数初始化伙伴系统之上的slab分配器。

3.10、alloc_pages()

从伙伴系统中分配内存页,系统返回指向页的数据结构struct page。

51
52

4、启动参数解析

Linux内核中有三种情况可以接受启动参数:

  • 1、内核固有和架构相关的参数例如:”mem=”、”memmap=”、”acpi=off”等等;
  • 2、使用“__setup(str, fn)”宏定义的赋值函数;
  • 3、使用“module_param(name, type, perm)”定义的模块参数。
  • 4.1 “__setup(str, fn)”宏定义
    __setup(str, fn)宏定义一个函数fn,在启动参数中包含字符串str时,fn函数得到调用。例如定义的宏是__setup(“aaa=”, fn),如果启动参数字符串中包含 “aaa=arg1,arg2”,即会调用fn(“arg1,arg2”) ;

下面是一个具体的“__setup(str, fn)”宏的例子,利用 “ad1816=xxx”传入的参数,调用setup_ad1816()函数给四个变量赋值。

53

我们来看看“__setup(str, fn)”的实现原理是怎么样的。

54
55

可以看到宏“__setup(str, fn)”和类似的宏“early_param(str,fn)“,都是在section “.init.setup”中定义一个obs_kernel_param类型的变量__setup_fn,变量的成员值为{“str”,fn,early};

56

section “.init.setup”的起始是__setup_start,结束是__setup_end,在arch/i386/kernel/vmlinux.lds.s中有定义:

在内核启动时,do_early_param ()函数和obsolete_checksetup()会搜索__setup_start和__setup_end之间的obs_kernel_param类型变量,判断是否有对应的内核参数命令字是否有对应参数传入,有则调用相应赋值函数。

57
58

值得注意的一点使用宏early_param(str, fn)定义是str中不能带’=’号,使用宏__setup(str, fn)定义是str中必须带’=’符号:

59
60

4.2、“module_param(name, type, perm)”宏定义

“module_param(name, type, perm)”宏用来定义模块中的变量,该变量名为name,能够被启动参数命令行所更改。

61
62

从module_param()宏的定义中可以看到,module_param()宏在section“__param”中定义了一个kernel_param类型的变量,变量的值为{“name”, perm, param_set_type,param_get_type ,&name}。

param_set_type,param_get_type函数为给变量赋值或获取变量值的函数,对应的函数有以下等等:

63

section “__param”的起始是__start___param,结束是__stop___param,在include/asm-generic/vmlinux.lds.h中有定义:

64

内核启动在start_kernel()函数中,解析内核启动命令行,根据传递的参数给相应module_param()宏定义的变量赋值。

65

4.3、启动参数处理流程

内核的启动入口在start_kernel()函数。

66

4.3.1、parse_cmdline_early()

67
68
69
70
71
72
73

从上可以看到i386的原始启动参数命令行保存在saved_command_line中,而saved_command_line又是怎么赋值的呢?在arch/i386/kernel/head.s中,可以看到将bootloader传递过来的启动参数拷贝给saved_command_line:

74

4.3.2、parse_ early_param()

75
76
77
77-0

4.3.3、parse_args (__start___param)

78
79
80
81
82
83

5、内核模块初始化

在使用“make menuconfig”配置内核时,可以选择驱动是和内核编译到一起(x)还是编译成外部模块(m)。在内核启动过程中,会统一初始化编译到内核中的模块。

5.1、module_init()宏定义

84

我们在写驱动模块时,使用module_init()宏来声明我们模块的初始化函数。从上面module_init()宏的定义可以看到,在驱动被编译成内核模块时,module_init()宏的意思是在段section(“.initcall” level “.init”)中定义了一个函数指针变量_initcall##fn,它的值等于我们声明的函数。

section(“.initcall” level “.init”)在链接脚本vmlinux.lds.S中定义:

85

5.2、do_initcalls ()

在内核启动的过程中,内核init进程会调用do_initcalls()函数逐个执行__initcall_start段中的所有函数指针。

86

6、ramdisk挂载

6.1、Init()

87
88
89

6.2、populate_rootfs()

90
91
92
93

6.3、prepare_namespace()

94
95
96

6.4、initrd_load()

97
98
99

6.5、mount_root()

100
101
102

6.6、default_rootfs()

在2.6.16.54内核中,在加载cpio-initrd失败后,会调用prepare_namespace。prepare_namespace的第一句mount_devfs(),mount_devfs的作用mount devfs到 “/dev”路径,这个时候根目录是rootfs,但是这个时候的“/dev”路径是怎么产生的呢??

这个应该是2.6.16.54内核的一个bug,再更高的版本中,我们看到了default_rootfs来解决这个问题。default_rootfs中创建了rootfs的“/dev”。

103

7、用户态启动流程

7.1、init

7.1.1、Linux的脚本启动顺序

Linux在内核态启动完成后,调用用户态的“init”程序开始布置整个用户态的应用环境,init在随后根据配置文件调用文件系统中的初始化脚本。在这里,唯一可以肯定的是任何linux发行版本第一个应用程序都是会去调用init程序,且init程序解析配置文件的方法都是一致的。而关于启动脚本的组织形式和风格,在多个发行版本之间是各不相同、多种多样的。

所以如果需要修改启动脚本以用来加入一些自定义的模块,需要先理解该linux文件系统的脚本架构。

Linux脚本的启动顺序大概如下图所示:

104

7.1.2、/sbin/init配置文件/etc/inittab

Linux下为什么会要有个init?用过windows 9.x的人应该知道有个批处理文件autoexec.bat,用过windows NT/2000系统的人应该在控制面板中见过system service工具,它们的目的是相同的。只是比较起来windows下的这些东西功能太弱(当然用法也更简单)。

init是Linux启动的最后一步,它帮助用户完成每次启动系统都必须完成的一些重复性任务,如加载文件系统、各类网络服务等等程序;它还有一个重要用途,让用户自定义系统运行环境,只启动需要的进程,关闭不用的进程,释放内存和处理器资源,让系统运行得更快更稳。

常见的init用户程序有两种:一种完整版的init程序sysvinit,sysvinit软件包提供了一系列开关机的命令,常见的有:hutdown、reboot、halt、poweroff、telinit、init。它们都可以达到关机或重启的目的,但是每个命令的工作流程并不一样。

另一种是busybox提供的精简版init :

105

init会按任务表执行我们下的命令,这个任务表就是/etc/inittab文件。下面查看一个inittab文件的例子:

106

“/etc/inittab”文件每一行定义一个指令,其基本格式为:“id:runlevels:action:command”。各字段的详解如下:

  • id:是任意一个名称(具体是什么并不重要);
  • runlevels:是一个数字串(代表运行等级);
一条命令可以是一个等级下执行,也可以是多个等级下执行,例如:“1:2345:respawn:/sbin/getty 38400 tty1 “,该命令在等级2345下都会被执行。根据Linux的定义,Init 可以启动到8个不同的运行级别上:0-6 S 或 s。运行级别可以由超级用户通过 telinit 命令来转换,此命令可以将转换信号传递给 init,告诉它切换到哪个运行级别。运行级别01,和 6为系统保留的专用运行级别。运行级别 0 用来关机,运行级别 6 用来重启,运行级别 1 用来使计算机进入单用户模式。运行级别 S 不是给我们直接使用的,更多是为进入运行级别 1 时运行某些可执行脚本时被调用。下面是几个运行级的简单介绍:# 0 - 关机(千万不要把initdefault 设置为0 )# 1 - 单用户模式# 2 - 多用户,但是没有 NFS# 3 - 完全多用户模式# 4 - 没有用到# 5 - X11# 6 - 重启(千万不要把initdefault 设置为6 )
  • action:描述何时执行命令;
action,告诉init执行的动作,即如何处理process字段指定的进程。action字段允许的值及对应的动作分别为:1)respawn:如果process字段指定的进程没有运行,则启动该进程,init不等待处理结束,而是继续扫描inittab文件中的后续进程,当这样的进程终止时,init会重新启动它,如果这样的进程已经运行,则什么也不做。2wait:启动process字段指定的进程,并等到处理结束才去处理inittab中的下一记录项。3)once:启动process字段指定的进程,不等待处理结束就去处理下一记录项。当这样的进程终止时,也不再重新启动它,在进入新的运行级别时,如果这样的进程仍在运行,init也不重新启动它。4)boot:只有在系统启动时,init才处理这样的记录项,启动相应进程,并不等待处理结束就去处理下一个记录项。当这样的进程终止时,系统也不重启它。5)bootwait:系统启动后,当第一次从单用户模式进入多用户模式时处理这样的记录项,init启动这样的进程,并且等待它的处理结束,然后再进行下一个记录项的处理,当这样的进程终止时,系统也不重启它。6)powerfail:当init接到断电的信号(SIGPWR)时,处理指定的进程。7)powerwait:当init接到断电的信号(SIGPWR)时,处理指定的进程,并且等到处理结束才去检查其他的记录项。8)off:如果指定的进程正在运行,init就给它发SIGTERM警告信号,在向它发出信号SIGKILL强制其结束之前等待5秒,如果这样的进程不存在,则忽略这一项。9)ondemand:功能通respawn,不同的是,与具体的运行级别无关,只用于runlevel字段是a、b、c的那些记录项。10)sysinit:指定的进程在访问控制台之前执行,这样的记录项仅用于对某些设备的初始化,目的是为了使init在这样的设备上向用户提问有关运行级别的问题,init需要等待进程运行结束后才继续。11)initdefault:指定一个默认的运行级别,只有当init一开始被调用时才扫描这一项,如果rstate字段指定了多个运行级别,其中最大的数字是默认的运行级别,如果runlevel字段是空的,init认为字段是0123456,于是进入级别6,这样便陷入了一个循环,如果inittab文件中没有包含initdefault的记录项,则在系统启动时请求用户为它指定一个初始运行级别。
  • command:指定执行的实际命令。该字段中进程可以是任意的守候进程、可执行脚本或程序,后面可以带参数。

Inittab是所有启动脚本的总入口,各种版本linux的启动脚本组织风格虽然不同,从这里都能找到它的入口。

7.1.3、/etc/rc.d

虽然不同linux版本启动脚本的组织风格是不一样,你也可以自定义出一套风格来,但是普遍来说/etc/rc.d/rcN.d是一种最常见的风格。

如在/etc/inittab文件中,N运行级别调用/etc/rc.d/rc N的命令:

107

以运行级别5为例,init将执行配置文件inittab中的以下这行:l5:5:wait:/etc/rc.d/rc 5

这一行表示以5为参数运行/etc/rc.d/rc,/etc/rc.d/rc是一个Shell脚本,它接受5作为参数,去执行/etc/rc.d /rc5.d/目录下的所有的rc启动脚本,/etc/rc.d/rc5.d/目录中的这些启动脚本实际上都是一些链接文件,而不是真正的rc启动脚本,真正的rc启动脚本实际上都是放在/etc/rc.d/init.d/目录下。而这些rc启动脚本有着类似的用法,它们一般能接受start、stop、 restart、status等参数。

/etc/rc.d/rc5.d/中的rc启动脚本通常是K或S开头的链接文件,对于以以S开头的启动脚本,将以start参数来运行。而如果发现存在相应的脚本也存在K打头的链接,而且已经处于运行态了(以/var/lock/subsys/下的文件作为标志),则将首先以stop为参数停止这些已经启动了的守护进程,然后再重新运行。这样做是为了保证是当init改变运行级别时,所有相关的守护进程都将重启。

7.1.4、启动脚本举例

例子系统1:(32bit编译服务器 32.27.155.2):

108

例子系统2:(系统1 32.1.12.119 :172.16.127.8):

109

例子系统3:(系统2 32.1.12.119 :172.16.127.80):

110

7.2、tty

/sbin/init在调用完初始化脚本以后,最后调用tty程序。在/sbin/init的配置文件/etc/inittab文件中,可以看到对mingetty、agetty的使用。一般串口终端ttySn使用agetty,ttyN控制台虚拟终端使用mingetty。

111

7.2.1、mingetty

mingetty由一个独立的软件包mingetty-1.08.tar.gz提供。/sbin/init调用mingetty以后,mingetty的功能非常简单,主要有以下功能:

  • 1、打开命令选项指定的tty设备,使用dup2函数将打开的tty设备指定为当前进程的标准输入、输出、错误输出设备(fd 0/1/2);
  • 2、等待用户输入用户名;
  • 3、使用execl()函数调用 “/bin/login”,并将相应的选项传递给login程序。

112

7.2.2、agetty

agetty由软件包util-linux-ng-2.18.tar.gz提供,agetty相比较mingetty功能更为强大,可以完全兼容mingetty。为什么还要使用mingetty?因为mingetty比较小巧,资源占用最小。

初始化时Agetty比mingetty多干一件事时,就是根据输入参数,配置tty设备的各种参数。这是mingetty没有的功能。

7.3、login

mingetty、agetty最后一件事就是调用login程序,login程序的作用就是校验用户名和密码,合法的用户名和密码才能登入系统,并赋给相应的权限。

7.3.1、shadow

login程序由shadow-4.0.15.tar.bz2软件包提供。Shadow软件包提供了一整套的用户管理工具:chage, chfn, chgpasswd, chpasswd, chsh, expiry, faillog, gpasswd, groupadd,groupdel, groupmod, grpck, grpconv, grpunconv, lastlog, login, logoutd, newgrp, newusers,nologin, passwd, pwck, pwconv, pwunconv, sg(→newgrp), su, useradd, userdel, usermod。

常见的有:登入登出系统(login、logoutd),增加用户组groupadd,增加用户useradd,修改用户密码passwd。下面详细的描述用户管理相关的一些配置文件格式。

7.3.1.1、/etc/group

用户组配置文件,使用groupadd命令添加用户组时,配置会添加到该文件。

113

The format of the /etc/group file is as follows:“groupname:!:GID:member,member,...”Where:groupname   The name of the group!  The field that normally holds the password, but that is now relocated to the /etc/gshadow file.GID   The numerical group ID numbermember   List of group members

7.3.1.2、/etc/passwd

用户配置文件,使用useradd命令添加用户组时,配置会添加到该文件。在使用shadow时,实际的密码会保存到/etc/shadow文件当中。

114

A non-shadowed /etc/passwd file has the following format:“username:passwd:UID:GID:full_name:directory:shell”Where:username   The user (login) namepasswd   The encoded passwordUID   Numerical user IDGID   Numerical default group IDfull_name   The user's full name - Actually this field is called the GECOS(General Electric Comprehensive Operating System) field and canstore information other than just the full name.  The Shadow commands and manual pages refer to this field as the comment field.directory   User's home directory (Full pathname)shell   User's login shell (Full Pathname)

7.3.1.3、/etc/shadow

用户密码的实际存储文件。

115

The /etc/shadow file contains the following information:“username:passwd:last:may:must:warn:expire:disable:reserved”Where:username   The User Namepasswd   The Encoded passwordlast   Days since Jan 1, 1970 that password was last changedmay   Days before password may be changedmust   Days after which password must be changedwarn   Days before password is to expire that user is warnedexpire   Days after password expires that account is disableddisable   Days since Jan 1, 1970 that account is disabledreserved   A reserved field

7.3.2、pam

pam全称是Pluggable Authentication Modules,中文叫“可插入认证模块”。其主要的用途是对linux权限认证的需求抽象出了一套模型架构,它是以一组so库形式存在。用户有认证方面的需求之需要配置好配置文件,调用pam库的相关接口函数即可。

116

Shadow是一套加密算法,pam是一套认证机制。Pam是可以用来对shadow提供的服务进行加强,在ssh、ftp等其他服务中都可以看到对pam的使用。

如果需要在shadow应用中打开pam的支持,需要在编译时在/configure配置时加入“–with-libpam”。确认当前使用的服务是否使用pam支持,可以用ldd查看文件是否依赖pam动态库。

117

7.3.3、调用shell

在login程序的最后,调用/etc/passwd文件中配置的用户对应的shell文件。可以在login.c中看到其实现:

118

7.4、shell

Linux系统提供多种不同的Shell以供选择。常用的有Bourne Shell(简称sh)、C-Shelll(简称csh)、Korn Shell(简称ksh)和Bourne Again Shell (简称bash)。Bourne Again Shell (即bash)是自由软件基金会(GNU)开发的一个Shell,它是Linux系统中一个默认的Shell。Bash不但与Bourne Shell兼容,还继承了C Shell、Korn Shell等优点。

7.4.1、登陆shell

登陆shell即login shell,在shell的调用名前加 “-”或者是使用—login参数的shell,就是登陆shell。

bash作为登陆shell启动时执行的startup文件如下:

* /etc/profile                                                                      * ~/.bash_profile,~/.bash_login or ~/.profile, first existing readable file is read

7.4.2、非登陆shell

使用不带参数直接启动的shell就是非登陆shell。

bash作为非登陆shell启动时读取~/.bashrc。

注意,作为登陆shell时bash并不读取~/.bashrc,但是在文件~/.bash_profile中通常都有如下语句来读取~/.bashrc:if [ –f ~/.bashrc ]; then . ~/.bashrc; fi

7.4.3、非交互shell

Shell还分两种工作模式:交互式和非交互式,执行脚本时,shell就工作在非交互式模式下。
在非交互模式下,bash读取的 startup文件由环境变量BASH_ENV来决定。“$BASH_ENV”会被执行调用。

7.4.4、环境变量

环境变量,或者称为全局变量,存在与所有的shell 中,在你登陆系统的时候就已经有了相应的系统定义的环境变量了。Linux 的环境变量具有继承性,即子shell 会继承父shell 的环境变量。

本地变量,当前shell 中的变量,很显然本地变量中肯定包含环境变量。Linux 的本地变量的非环境变量不具备继承性。
有几个相关的命令:

  • env 和printenv

这两个命令用于打印所有的环境变量;

  • set

用于显示与设置当前本地 变量。单独一个set 就显示了当前环境的所有的变量,它肯定包括环境变量和一些非环境变量;

  • unset

用于清除变量。不管这个变量是环境变量还是本地变量,它都可以清除;

  • export

用于把变量变成当前shell 和其子shell 的环境变量,存活期是当前的shell 及其子shell ,因此重新登陆以后,它所设定的环境变量就消失了。如何将环境变量永久化?修改上面介绍的那几个环境变量的配置文件;

  • source

当直接执行一个脚本的时候,其实是在一个子shell 环境运行的,即开启了一个子shell 来执行这个脚本,脚本执行完后该子shell 自动退出。有没有办法在当前shell 中执行一个脚本呢?使用source 命令就可以让脚本在当前shell 中执行。

8、参考资料