当init启动后,它通过执行各种启动事务来继续引导进程(检查并监视文件系统,启动后台程序daemons,等等),直至完成用户所有操作环境的设置工作。这里主要涉及4个程序:init、getty(agetty)、login和shell程序。这4个程序之间的关系见下图所示。
init进程的主要任务是根据/etc/rc文件中设置的信息,执行其中设置的命令,然后根据/etc/inittab文件中的信息,为每一个允许登录的终端设备使用fork()创建一个子进程,并在每个新创建的子进程中运行agetty (getty)程序。而init进程则调用wait(),进入等待子进程结束状态。每当它的一个子进程结束退出,它就会根据wait()返回的pid号知道是哪个对应终端的子进程结束了,因此就会为相应终端设备再创建一个新的子进程,并在该子进程中重新执行agetty程序。这样,每个被允许的终端设备都始终有一个对应的进程为其等待处理。
在正常的操作下,init确定agetty正在工作着以允许用户登录,并且收取孤立进程。孤立进程是指那些其父辈进程已结束的进程;在Linux中所有的进程必须属于单棵进程树,所以孤立进程必须被收取。当系统关闭时,init负责杀死所有其它的进程,卸载所有的文件系统以及停止处理器的工作,以及任何它被配置成要做的工作。
getty程序的主要任务是设置终端类型、属性、速度和线路规程。它打开并初始化一个tty端口,显示提示信息,并等待用户键入用户名。该程序只能由超级用户执行。通常,若/etc/issue文本文件存在,则 getty会首先显示其中的文本信息,然后显示登录提示信息(例如:plinux login: ),读取用户键入的登录名,并执行login程序。
为了能让init程序运行getty,/etc/inittab文件中必须含有getty(agetty)命令。/etc/inittab文件中有关agetty的内容例子见如下所示。
列表 3.1 poeigl-1.2中的inittab文件
# inittab for linux, poeigl 1.2
# format:
# ttyline:termcap-entry:getty-command
tty1:con80x60:/bin/agetty 9600 tty1
tty2:con80x60:/bin/agetty 9600 tty2
tty3:con80x60:/bin/agetty 9600 tty3
tty4:con80x60:/bin/agetty 9600 tty4
# tty5:con80x60:/bin/agetty 9600 tty5
# tty64:dumb:/bin/agetty 9600 tty64
# tty65:dumb:/bin/agetty -m -t60 2400 tty65
每个终端都有自己的getty命令。其中列出了tty1— tty4对应的登录项信息。以’#’开始的是注释行。第1列是所用终端设备名称,第2列是指定终端的类型,这里指定了终端类型是con80x60。第3列是所执行的命令及其参数。最后两行中的tty64和tty65对应连接在串行端口上的终端。
对于使用串行端口与主机直接相连的终端以及通过modem拨号连接的终端,Linux的agetty程序还有其它一些属性。如在读取登录名时自动调整tty的设置信息,例如奇偶校验位、檫除字符、行结束字符以及上档键字符等。可选择地从链接的Hayes兼容modem信息中检测出传输波特率。
/dev/inittab中每一项的参数格式与具体使用哪一种getty程序有关。目前一般常用的getty程序有如下几种:
1.agetty(有时直接称为getty):容易设置,无须配置文件。适用于直接连接的终端;
2.getty(getty_ps的一部分):适用于直接连接的终端;
3.mgetty:最适合于通过modem连接,也可用于直连;
4.uugetty:仅用于通过modem连接终端,是getty_ps软件包的部分;
5.mingetty:简单的getty。适用于控制台终端或虚拟终端;
6.fbgetty:适用于控制台或虚拟终端。
Redhat 9系统默认配置中带有mingetty和agetty两个程序。控制台或虚拟终端使用的是mingetty。对于实际的字符终端则一般使用agetty。因此在Redhat 9系统的/etc/inittab文件中会看到以下的信息。
列表 3.2 RedHat 9系统的/etc/inittab文件中有关getty的信息
# Run gettys in standard runlevels
1:2345:respawn:/sbin/mingetty tty1
2:2345:respawn:/sbin/mingetty tty2
3:2345:respawn:/sbin/mingetty tty3
4:2345:respawn:/sbin/mingetty tty4
5:2345:respawn:/sbin/mingetty tty5
6:2345:respawn:/sbin/mingetty tty6
其中第1列表示名称tty后的数字,2345表示该mingetty的运行层。respawn表示如果该mingetty被终止,则mingetty将再次自动执行。/sbin/mingetty是命令。ttyn代表/dev/ttyn(n表示数字1—5)。
在登录到Linux系统中之后,你会发现(使用”top”或”ps –ax”命令)自己终端原来的getty进程已经找不到了。因为getty进程执行了login程序,被替换成了login进程,并且最后被替换成你的登录shell进程。
当你在”login: “提示符下键入了你的用户名后,getty会读取用户名并且去执行login程序,也把用户名信息传给了它。因此getty进程被替换成了login进程。此时login进程会接着要求你输入口令。在口令检查通过后就会去执行/etc/passwd文件中对应你用户名项中记录的程序。通常这个程序是 bash shell程序。因此原来的getty进程最终被替换成了bash进程,对应的这三个程序也就都具有相同的进程ID。
当注销登录(log out)时,则该终端上的所有进程都会被终止(killed),包括登录shell进程bash。因此,对于在/etc/inittab文件中列出的 getty程序,一旦其被替换执行的bash程序被终止或退出,init进程就会为对应终端重新创建一个getty进程。
login程序则主要用于要求登录用户输入密码。根据用户输入的用户名,它从口令文件passwd中取得对应用户的登录项,然后调用getpass()以显示”password:”提示信息,读取用户键入的密码,然后使用加密算法对键入的密码进行加密处理,并与口令文件中该用户项中pw_passwd字段作比较。如果用户几次键入的密码均无效,则login程序会以出错码1退出执行,表示此次登录过程失败。此时父进程(进程init)的wait()会返回该退出进程的pid,因此会根据记录下来的信息再次创建一个子进程,并在该子进程中针对该终端设备再次执行agetty程序,重复上述过程。
login程序也可以被用户在运行过程中在shell下当作一个命令执行。此时它可以被用随时从一个用户切换成另一个用户。如果执行时没有给出参数,则login就会显示输入用户名的提示信息。如果用户不是超级用户(root),并且/etc/目录下存在一个名为nologin的文件,那么该文件中的信息就会被显示出来,此次登录过程也随即被终止。
如果在/etc/usertty文件中对该用户指定了特殊的访问限制,那么这些限制要求必须满足。如果是一个超级用户,那么所使用的登录tty设备必须是在/etc/securetty文件中指定的。
在所有这些条件满足之后,login同样也会要求用户输入密码并对其进行检查。如果.hushlogin存在的话,login就会执行一个“安静”的登录过程,也即不检查是否有邮件,也不显示上次登录时间和 motd文件中的信息。否则如果/var/log/lastlog文件存在的话,就会显示其中的最后登录时间。
如果用户键入的密码正确,则login就会把当前工作目录(Currend Work Directory)修改成口令文件中指定的该用户的起始工作目录。并把对该终端设备的访问权限修改成用户读/写和组写,设置进程的组ID。然后利用所得到的信息初始化环境变量信息,例如起始目录(HOME=)、使用的shell程序(SHELL=)、用户名(USER=和LOGNAME=)和系统执行程序的默认路径序列(PATH=)。接着显示/etc/motd文件(message-of-the-day)中的文本信息,并检查并显示该用户是否有邮件的信息。最后login程序改变成登录用户的用户ID并执行口令文件中该用户项中指定的shell程序,如bash或csh等。
如果口令文件/etc/passwd中该用户项中没有指定使用哪个shell程序,系统则会使用默认的/bin/sh程序。如果口令文件中也没有为该用户指定用户起始目录的话,系统就会使用默认的根目录/。有关 login程序的一些执行选项和特殊访问限制的说明,请参见Linux系统中的在线手册页(man -8 login)。
Shell程序是一个复杂的命令行解释程序,是当用户登录系统进行交互操作时执行的程序。它是用户与计算机进行交互操作的地方。它获取用户输入的信息,然后执行命令。用户可以在终端上向shell直接进行交互输入,也可以使用shell脚本文件向shell解释程序输入。在Linux系统中,目前常用的shell有:
Bourne Again Shell,/bin/bash
C shell,/bin/csh(或tcsh)
BSD shell/bin/ash(或bsh)
在登录过程中,系统(login)会从口令文件用户对应登录项的最后一个字段知道应该为用户执行哪个shell程序。
shell程序中实现了一个具有流控制结构的语言,使用相当广泛。目前这些shell程序都朝着与IEEE POSIX 1003.2兼容的方向发展,因此它们各自虽然各自有自己的特点,但基本功能已经越来越相象。本书主要介绍bash的工作原理和实现机制,其它几种 shell的实现机制与之类似。
在登录过程中login开始执行shell时,所带参数 argv[0]的第一个字符是’-’,表示该shell是作为一个登录shell被执行。此时该shell程序会根据该字符,执行某些与登录过程相应的操作。登录shell会首先从/etc/profile文件以及.profile文件(若存在的话)读取命令并执行。如果在进入shell时设置了ENV环境变量,或者在登录shell的.profile文件中设置了该变量,则shell下一步会从该变量命名的文件中读去命令并执行。因此用户应该把每次登录时都要执行的命令放在.profile文件中,而把每次运行shell都要执行的命令放在ENV变量指定的文件中。设置ENV环境变量的方法是把下列语句放在你起始目录的.profile文件中。
ENV=$HOME/.anyfilename; export ENV
在执行shell时,除了一些指定的可选项以外,如果还指定了命令行参数,则shell会把第一个参数看作是一个脚本文件名并执行其中的命令,而其余的参数则被看作是shell的位置参数($1、$2等)。否则shell程序将从其标准输入中读取命令。
在执行shell程序时可以有很多选项,请参见Linux系统中的有关sh的在线手册页中的说明。
------------------------------------------------------------------------------------
2001 年 5 月 01 日
本文以Redhat 6.0 Linux 2.2.19 for Alpha/AXP为平台,描述了从开机到登录的 Linux 启动全过程。该文对i386平台同样适用。
Bootloader
在Alpha/AXP平台上引导Linux通常有两种方法,一种是由MILO及其他类似的引导程序引导,另一种是由Firmware直接引导。MILO功能与i386平台的LILO相近,但内置有基本的磁盘驱动程序(如IDE、SCSI等),以及常见的文件系统驱动程序(如ext2,iso9660等), firmware有ARC、SRM两种形式,ARC具有类BIOS界面,甚至还有多重引导的设置;而SRM则具有功能强大的命令行界面,用户可以在控制台上使用boot等命令引导系统。ARC有分区(Partition)的概念,因此可以访问到分区的首扇区;而SRM只能将控制转给磁盘的首扇区。两种firmware都可以通过引导MILO来引导Linux,也可以直接引导Linux的引导代码。
“arch/alpha/boot”下就是制作Linux Bootloader的文件。“head.S”文件提供了对 OSF PAL/1的调用入口,它将被编译后置于引导扇区(ARC的分区首扇区或SRM的磁盘0扇区),得到控制后初始化一些数据结构,再将控制转给“main.c”中的start_kernel(), start_kernel()向控制台输出一些提示,调用pal_init()初始化PAL代码,调用openboot() 打开引导设备(通过读取Firmware环境),调用load()将核心代码加载到START_ADDR(见 “include/asm-alpha/system.h”),再将Firmware中的核心引导参数加载到ZERO_PAGE(0) 中,最后调用runkernel()将控制转给0x100000的kernel,bootloader部分结束。
Bootloader中使用的所有“srm_”函数在“arch/alpha/lib/”中定义。
以上这种Boot方式是一种最简单的方式,即不需其他工具就能引导Kernel,前提是按照 Makefile的指导,生成bootimage文件,内含以上提到的bootloader以及vmlinux,然后将 bootimage写入自磁盘引导扇区始的位置中。
当采用MILO这样的引导程序来引导Linux时,不需要上面所说的Bootloader,而只需要 vmlinux或vmlinux.gz,引导程序会主动解压加载内核到0x1000(小内核)或0x100000(大内核),并直接进入内核引导部分,即本文的第二节。
对于I386平台
i386系统中一般都有BIOS做最初的引导工作,那就是将四个主分区表中的第一个可引导分区的第一个扇区加载到实模式地址0x7c00上,然后将控制转交给它。
在“arch/i386/boot”目录下,bootsect.S是生成引导扇区的汇编源码,它首先将自己拷贝到0x90000上,然后将紧接其后的setup部分(第二扇区)拷贝到0x90200,将真正的内核代码拷贝到0x100000。以上这些拷贝动作都是以bootsect.S、setup.S以及vmlinux在磁盘上连续存放为前提的,也就是说,我们的bzImage文件或者zImage文件是按照bootsect,setup, vmlinux这样的顺序组织,并存放于始于引导分区的首扇区的连续磁盘扇区之中。
bootsect.S完成加载动作后,就直接跳转到0x90200,这里正是setup.S的程序入口。 setup.S的主要功能就是将系统参数(包括内存、磁盘等,由BIOS返回)拷贝到 0x90000-0x901FF内存中,这个地方正是bootsect.S存放的地方,这时它将被系统参数覆盖。以后这些参数将由保护模式下的代码来读取。
除此之外,setup.S还将video.S中的代码包含进来,检测和设置显示器和显示模式。最后,setup.S将系统转换到保护模式,并跳转到0x100000(对于bzImage格式的大内核是 0x100000,对于zImage格式的是0x1000)的内核引导代码,Bootloader过程结束。
对于2.4.x版内核
没有什么变化。
回页首
Kernel引导入口
对于I386平台
在i386体系结构中,因为i386本身的问题,在"arch/alpha/kernel/head.S"中需要更多的设置,但最终也是通过call SYMBOL_NAME(start_kernel)转到start_kernel()这个体系结构无关的函数中去执行了。
所不同的是,在i386系统中,当内核以bzImage的形式压缩,即大内核方式(__BIG_KERNEL__)压缩时就需要预先处理bootsect.S和setup.S,按照大核模式使用$(CPP) 处理生成bbootsect.S和bsetup.S,然后再编译生成相应的.o文件,并使用 "arch/i386/boot/compressed/build.c"生成的build工具,将实际的内核(未压缩的,含 kernel中的head.S代码)与"arch/i386/boot/compressed"下的head.S和misc.c合成到一起,其中的head.S代替了"arch/i386/kernel/head.S"的位置,由Bootloader引导执行(startup_32入口),然后它调用misc.c中定义的decompress_kernel()函数,使用 "lib/inflate.c"中定义的gunzip()将内核解压到0x100000,再转到其上执行 "arch/i386/kernel/head.S"中的startup_32代码。
对于2.4.x版内核
没有变化。
回页首
核心数据结构初始化--内核引导第一部分
start_kernel()中调用了一系列初始化函数,以完成kernel本身的设置。这些动作有的是公共的,有的则是需要配置的才会执行的。
在start_kernel()函数中,
* 输出Linux版本信息(printk(linux_banner))
* 设置与体系结构相关的环境(setup_arch())
* 页表结构初始化(paging_init())
* 使用"arch/alpha/kernel/entry.S"中的入口点设置系统自陷入口(trap_init())
* 使用alpha_mv结构和entry.S入口初始化系统IRQ(init_IRQ())
* 核心进程调度器初始化(包括初始化几个缺省的Bottom-half,sched_init())
* 时间、定时器初始化(包括读取CMOS时钟、估测主频、初始化定时器中断等,time_init())
* 提取并分析核心启动参数(从环境变量中读取参数,设置相应标志位等待处理,(parse_options())
* 控制台初始化(为输出信息而先于PCI初始化,console_init())
* 剖析器数据结构初始化(prof_buffer和prof_len变量)
* 核心Cache初始化(描述Cache信息的Cache,kmem_cache_init())
* 延迟校准(获得时钟jiffies与CPU主频ticks的延迟,calibrate_delay())
* 内存初始化(设置内存上下界和页表项初始值,mem_init())
* 创建和设置内部及通用cache("slab_cache",kmem_cache_sizes_init())
* 创建uid taskcount SLAB cache("uid_cache",uidcache_init())
* 创建文件cache("files_cache",filescache_init())
* 创建目录cache("dentry_cache",dcache_init())
* 创建与虚存相关的cache("vm_area_struct","mm_struct",vma_init())
* 块设备读写缓冲区初始化(同时创建"buffer_head"cache用户加速访问,buffer_init())
* 创建页cache(内存页hash表初始化,page_cache_init())
* 创建信号队列cache("signal_queue",signals_init())
* 初始化内存inode表(inode_init())
* 创建内存文件描述符表("filp_cache",file_table_init())
* 检查体系结构漏洞(对于alpha,此函数为空,check_bugs())
* SMP机器其余CPU(除当前引导CPU)初始化(对于没有配置SMP的内核,此函数为空,smp_init())
* 启动init过程(创建第一个核心线程,调用init()函数,原执行序列调用cpu_idle() 等待调度,init())
至此start_kernel()结束,基本的核心环境已经建立起来了。
对于I386平台
i386平台上的内核启动过程与此基本相同,所不同的主要是实现方式。
对于2.4.x版内核
2.4.x中变化比较大,但基本过程没变,变动的是各个数据结构的具体实现,比如Cache。
回页首
外设初始化--内核引导第二部分
init()函数作为核心线程,首先锁定内核(仅对SMP机器有效),然后调用 do_basic_setup()完成外设及其驱动程序的加载和初始化。过程如下:
* 总线初始化(比如pci_init())
* 网络初始化(初始化网络数据结构,包括sk_init()、skb_init()和proto_init()三部分,在proto_init()中,将调用protocols结构中包含的所有协议的初始化过程,sock_init())
* 创建bdflush核心线程(bdflush()过程常驻核心空间,由核心唤醒来清理被写过的内存缓冲区,当bdflush()由kernel_thread()启动后,它将自己命名为kflushd)
* 创建kupdate核心线程(kupdate()过程常驻核心空间,由核心按时调度执行,将内存缓冲区中的信息更新到磁盘中,更新的内容包括超级块和inode表)
* 设置并启动核心调页线程kswapd(为了防止kswapd启动时将版本信息输出到其他信息中间,核心线调用kswapd_setup()设置kswapd运行所要求的环境,然后再创建 kswapd核心线程)
* 创建事件管理核心线程(start_context_thread()函数启动context_thread()过程,并重命名为keventd)
* 设备初始化(包括并口parport_init()、字符设备chr_dev_init()、块设备 blk_dev_init()、SCSI设备scsi_dev_init()、网络设备net_dev_init()、磁盘初始化及分区检查等等,device_setup())
* 执行文件格式设置(binfmt_setup())
* 启动任何使用__initcall标识的函数(方便核心开发者添加启动函数,do_initcalls())
* 文件系统初始化(filesystem_setup())
* 安装root文件系统(mount_root())
至此do_basic_setup()函数返回init(),在释放启动内存段(free_initmem())并给内核解锁以后,init()打开 /dev/console设备,重定向stdin、stdout和stderr到控制台,最后,搜索文件系统中的init程序(或者由init=命令行参数指定的程序),并使用 execve()系统调用加载执行init程序。
init()函数到此结束,内核的引导部分也到此结束了,这个由start_kernel()创建的第一个线程已经成为一个用户模式下的进程了。此时系统中存在着六个运行实体:
* start_kernel()本身所在的执行体,这其实是一个"手工"创建的线程,它在创建了init()线程以后就进入cpu_idle()循环了,它不会在进程(线程)列表中出现
* init线程,由start_kernel()创建,当前处于用户态,加载了init程序
* kflushd核心线程,由init线程创建,在核心态运行bdflush()函数
* kupdate核心线程,由init线程创建,在核心态运行kupdate()函数
* kswapd核心线程,由init线程创建,在核心态运行kswapd()函数
* keventd核心线程,由init线程创建,在核心态运行context_thread()函数
对于I386平台
基本相同。
对于2.4.x版内核
这一部分的启动过程在2.4.x内核中简化了不少,缺省的独立初始化过程只剩下网络(sock_init())和创建事件管理核心线程,而其他所需要的初始化都使用__initcall()宏包含在do_initcalls()函数中启动执行。
回页首
init进程和inittab引导指令
init进程是系统所有进程的起点,内核在完成核内引导以后,即在本线程(进程)空间内加载init程序,它的进程号是1。
init程序需要读取/etc/inittab文件作为其行为指针,inittab是以行为单位的描述性(非执行性)文本,每一个指令行都具有以下格式:
id:runlevel:action:process其中id为入口标识符,runlevel为运行级别,action为动作代号,process为具体的执行程序。
id一般要求4个字符以内,对于getty或其他login程序项,要求id与tty的编号相同,否则getty程序将不能正常工作。
runlevel是init所处于的运行级别的标识,一般使用0-6以及S或s。0、1、6运行级别被系统保留,0作为shutdown动作,1作为重启至单用户模式,6为重启;S和s意义相同,表示单用户模式,且无需inittab文件,因此也不在inittab中出现,实际上,进入单用户模式时, init直接在控制台(/dev/console)上运行/sbin/sulogin。
在一般的系统实现中,都使用了2、3、4、5几个级别,在Redhat系统中,2表示无NFS支持的多用户模式,3表示完全多用户模式(也是最常用的级别),4保留给用户自定义,5表示XDM图形登录方式。7-9级别也是可以使用的,传统的Unix系统没有定义这几个级别。runlevel可以是并列的多个值,以匹配多个运行级别,对大多数action来说,仅当runlevel与当前运行级别匹配成功才会执行。
initdefault是一个特殊的action值,用于标识缺省的启动级别;当init由核心激活以后,它将读取inittab中的initdefault项,取得其中的runlevel,并作为当前的运行级别。如果没有inittab文件,或者其中没有initdefault项,init将在控制台上请求输入 runlevel。
sysinit、boot、bootwait等action将在系统启动时无条件运行,而忽略其中的runlevel,其余的action(不含initdefault)都与某个runlevel相关。各个action的定义在inittab的man手册中有详细的描述。
在Redhat系统中,一般情况下inittab都会有如下几项:
id:3:initdefault:
#表示当前缺省运行级别为3--完全多任务模式;
si::sysinit:/etc/rc.d/rc.sysinit
#启动时自动执行/etc/rc.d/rc.sysinit脚本
l3:3:wait:/etc/rc.d/rc 3
#当运行级别为3时,以3为参数运行/etc/rc.d/rc脚本,init将等待其返回
0:12345:respawn:/sbin/mingetty tty0
#在1-5各个级别上以tty0为参数执行/sbin/mingetty程序,打开tty0终端用于
#用户登录,如果进程退出则再次运行mingetty程序
x:5:respawn:/usr/bin/X11/xdm -nodaemon
#在5级别上运行xdm程序,提供xdm图形方式登录界面,并在退出时重新执行
回页首
rc启动脚本
上一节已经提到init进程将启动运行rc脚本,这一节将介绍rc脚本具体的工作。
一般情况下,rc启动脚本都位于/etc/rc.d目录下,rc.sysinit中最常见的动作就是激活交换分区,检查磁盘,加载硬件模块,这些动作无论哪个运行级别都是需要优先执行的。仅当rc.sysinit执行完以后init才会执行其他的boot或bootwait动作。
如果没有其他boot、bootwait动作,在运行级别3下,/etc/rc.d/rc将会得到执行,命令行参数为3,即执行 /etc/rc.d/rc3.d/目录下的所有文件。rc3.d下的文件都是指向/etc/rc.d/init.d/目录下各个Shell脚本的符号连接,而这些脚本一般能接受start、stop、restart、status等参数。rc脚本以start参数启动所有以S开头的脚本,在此之前,如果相应的脚本也存在K打头的链接,而且已经处于运行态了(以/var/lock/subsys/下的文件作为标志),则将首先启动K开头的脚本,以stop 作为参数停止这些已经启动了的服务,然后再重新运行。显然,这样做的直接目的就是当init改变运行级别时,所有相关的服务都将重启,即使是同一个级别。
rc程序执行完毕后,系统环境已经设置好了,下面就该用户登录系统了。
回页首
getty和login
在rc返回后,init将得到控制,并启动mingetty(见第五节)。mingetty是getty的简化,不能处理串口操作。getty的功能一般包括:
* 打开终端线,并设置模式
* 输出登录界面及提示,接受用户名的输入
* 以该用户名作为login的参数,加载login程序
注:用于远程登录的提示信息位于/etc/issue.net中。
login程序在getty的同一个进程空间中运行,接受getty传来的用户名参数作为登录的用户名。
如果用户名不是root,且存在/etc/nologin文件,login将输出nologin文件的内容,然后退出。这通常用来系统维护时防止非root用户登录。
只有/etc/securetty中登记了的终端才允许root用户登录,如果不存在这个文件,则root可以在任何终端上登录。/etc/usertty文件用于对用户作出附加访问限制,如果不存在这个文件,则没有其他限制。
当用户登录通过了这些检查后,login将搜索/etc/passwd文件(必要时搜索 /etc/shadow文件)用于匹配密码、设置主目录和加载shell。如果没有指定主目录,将默认为根目录;如果没有指定shell,将默认为/bin/sh。在将控制转交给shell以前, getty将输出/var/log/lastlog中记录的上次登录系统的信息,然后检查用户是否有新邮件(/usr/spool/mail/{username})。在设置好shell的uid、gid,以及TERM,PATH 等环境变量以后,进程加载shell,login的任务也就完成了。
回页首
bash
运行级别3下的用户login以后,将启动一个用户指定的shell,以下以/bin/bash为例继续我们的启动过程。
bash是Bourne Shell的GNU扩展,除了继承了sh的所有特点以外,还增加了很多特性和功能。由login启动的bash是作为一个登录shell启动的,它继承了getty设置的TERM、PATH等环境变量,其中PATH对于普通用户为"/bin:/usr/bin:/usr/local/bin",对于root 为"/sbin:/bin:/usr/sbin:/usr/bin"。作为登录shell,它将首先寻找/etc/profile 脚本文件,并执行它;然后如果存在~/.bash_profile,则执行它,否则执行 ~/.bash_login,如果该文件也不存在,则执行~/.profile文件。然后bash将作为一个交互式shell执行~/.bashrc文件(如果存在的话),很多系统中,~/.bashrc都将启动 /etc/bashrc作为系统范围内的配置文件。
当显示出命令行提示符的时候,整个启动过程就结束了。此时的系统,运行着内核,运行着几个核心线程,运行着init进程,运行着一批由rc启动脚本激活的守护进程(如 inetd等),运行着一个bash作为用户的命令解释器。
回页首
附:XDM方式登录
如果缺省运行级别设为5,则系统中不光有1-6个getty监听着文本终端,还有启动了一个XDM的图形登录窗口。登录过程和文本方式差不多,也需要提供用户名和口令,XDM 的配置文件缺省为/usr/X11R6/lib/X11/xdm/xdm-config文件,其中指定了 /usr/X11R6/lib/X11/xdm/xsession作为XDM的会话描述脚本。登录成功后,XDM将执行这个脚本以运行一个会话管理器,比如gnome-session等。
除了XDM以外,不同的窗口管理系统(如KDE和GNOME)都提供了一个XDM的替代品,如gdm和kdm,这些程序的功能和XDM都差不多。
关于作者
杨沙洲,男,现攻读国防科大计算机学院计算机软件方向博士学位。您可以通过电子邮件 pubb@163.net跟他联系。
阅读(2063) | 评论(0) | 转发(0) |