内核启动文件系统后第一个执行的文件(inittab启动脚本分析)

  1. Linux 开机脚本启动顺序: 
  2. 第一步:启动内核
  3. 第二步:执行init (配置文件/etc/inittab)
  4. 第三步:启动相应的脚本,执行inittab脚本,并且执行里面的脚本/etc/init.d rc.sysinit rc.d rc.local。。。
  5. 第四步:启动login登录界面 login
  6. 第五步:在用户登录的时候执行sh脚本的顺序:每次登录的时候都会完全执行的 /etc/profile /etc/bashrc /root/.bashrc /root/.bash_profile
  7.     
  8. inittab脚本:
  9. init的进程号为1,是所有进程的父进程,内核初始化完毕之后,init程序开始运行。其他软件也同时开始运行。init程序通过/etc/inittab文件进行配置。
  10. /etc/inittab文件每一行包括四个字段:label:runlevel:action:process。详细解释如下。
  11. 1.label
  12. 登记项标志符,是一个任意指定的、4个字符以内的序列标号,在本文件内必须唯一。
  13. label是1到4个字符的标签,用来标示输入的值。一些系统只支持2个字符的标签。鉴于此原因,多数人都将标签字符的个数限制在2个以内。该标签可以是任意字符构成的字符串,但实际上,某些特定的标签是常用的,在Red Hat Linux中使用的标签是:
  14. id 用来定义缺省的init运行的级别
  15. si 是系统初始化的进程
  16. ln 其中的n从1~6,指明该进程可以使用的runlevel的级别
  17. ud 是升级进程
  18. ca 指明当按下Ctrl+Alt+Del是运行的进程
  19. pf 指当UPS表明断电时运行的进程
  20. pr 是在系统真正关闭之前,UPS发出电源恢复的信号时需要运行的进程
  21. x 是将系统转入X终端时需要运行的进程
  22. 2.runlevels
  23. 系统运行级,即执行登记项的init级别。用于指定相应的登记项适用于哪一个运行级,即在哪一个运行级中被处理。如果该字段为空,那么相应的登记项将适用于所有的运行级。在该字段中,可以同时指定一个或多个运行级,其中各运行级分别以数字0, 1, 2, 3, 4, 5, 6或字母a, b, c表示,且无须对其进行分隔。
  24. 0-->Halt,关闭系统.
  25. 1-->单用户,在grub启动时加上为kernel加上参数single即可进入此运行等级
  26. 2-->无网络多用户模式.
  27. 3-->有网络多用户模式.
  28. 4-->有网络多用户模式.
  29. 5-->X模式
  30. 6-->reboot重启系统
  31. S/s-->同运行等级1
  32. a,b,c-->自定义等级,通常不使用.
  33. 3.action
  34. 表示进入对应的runlevel时,init应该运行process字段的命令的方式,有效的action值如下。
  35. boot:只有在引导过程中,才执行该进程,但不等待该进程的结束。当该进程死亡时,也不重新启动该进程。
  36. bootwait:只有在引导过程中,才执行该进程,并等待进程的结束。当该进程死亡时,也不重新启动该进程。实际上,只有在系统被引导后,并从单用户模式进入多用户模式时,这些登记项才被处理;如果系统的默认运行级设置为2(即多用户模式),那么这些登记项在系统引导后将马上被处理。
  37. initdefault:指定系统的默认运行级。系统启动时,init将首先查找该登记项,如果存在,init将依据此决定系统最初要进入的运行级。具体来说,init将指定登记项"run_level"字段中的最大数字(即最高运行级)为当前系统的默认运行级;如果该字段为空,那么将其解释为"0123456",并以"6"作为默认运行级。如果不存在该登记项,那么init将要求用户在系统启动时指定一个最初的运行级。
  38. off:如果相应的进程正在运行,那么就发出一个告警信号,等待20秒后,再通过关闭信号强行终止该进程。如果相应的进程并不存在,那么就忽略该登记项。
  39. once:启动相应的进程,但不等待该进程结束便继续处理/etc/inittab文件中的下一个登记项;当该进程终止时,init也不重新启动该进程。在从一个运行级进入另一个运行级时,如果相应的进程仍然在运行,那么init就不重新启动该进程。
  40. ondemand:与"respawn"的功能完全相同,但只用于运行级为a、b或c的登记项。
  41. powerfail:只在init接收到电源失败信号时,才执行该进程,但不等待该进程结束。
  42. powerwait:只在init接收到电源失败信号时,才执行该进程,并在继续对/etc/inittab文件进行任何处理前等待该进程结束。
  43. respawn:如果相应的进程还不存在,那么init就启动该进程,同时不等待该进程的结束就继续扫描/etc/inittab文件;当该进程终止时,init将重新启动该进程。如果相应的进程已经存在,那么init将忽略该登记项并继续扫描/etc/inittab文件。
  44. sysinit:只有在启动或重新启动系统并首先进入单用户模式时,init才执行这些登记项。而在系统从运行级1~6进入单用户模式时,init并不执行这些登记项。"action"字段为"sysinit"的登记项在"run_level"字段不指定任何运行级。
  45. wait:启动进程并等待其结束,然后再处理/etc/inittab文件中的下一个登记项。
  46. ctrlaltdel:用户在控制台键盘上按下Ctrl+Alt+Del组合键时,允许init重新启动系统。注意,如果该系统放在一个公共场所,系统管理员可将Ctrl+Alt+Del组合键配置为其他行为,比如忽略等。
  47. 4.process
  48. 具体应该执行的命令。并负责在退出运行级时将其终止(当然在进入的runlevel中仍要运行的程序除外)。当运行级别改变,并且正在运行的程序并没有在新的运行级别中指定需要运行时,那么init会先发送一个SIGTERM 信号终止,然后是SIGKILL。
  49. 5.实例分析:
  50. /*************************/etc/inittab***********************************/
  51. //将系统切换到 initdefault 操作所定义的运行级别即运行级别5。我们可以将运行级别看作是系统的状态。运行级别 0 定义了系统挂起状态,运行级别 1 是单用户模式。运行级别 2 到 5 是多用户状态,运行级别 6 表示重启
  52. id:5:initdefault:
  53. //sysinit表示在进行其他工作之前先完成系统初始化.init在处理其它运行等级的脚本之前,首先会执行这一行.是系统的初始化进程.用于设置主机名,挂载文件系统,启动交换分区等.
  54. //rcS脚本会调用/etc/rcS.d目录下的所有脚本进行初始化
  55. si::sysinit:/etc/init.d/rcS //在运行boot或bootwait进程之前运行系统初始化的进程
  56. //下条语句可以让系统在重新启动、进入单用户模式的时候提示输入超级用户密码。
  57. //S同运行等级1,并等待其结束,然后再处理/etc/inittab文件中的下一个登记项。
  58. ~~:S:wait:/sbin/sulogin 
  59. //当运行级别为5时,以5为参数运行/etc/rc5.d下的脚本,init将等待其返回(wait)
  60. //rc.sysinit,rcS,rc这些都是shell的脚本,完成大量的系统初始化的工作。
  61. //主要工作包括:激活交换分区,检查磁盘,加载硬件模块以及其它一些需要优先执行任务。
  62. //执行rc脚本,传入参数为0-6,即会调用/etc/rc0.d-rc6.d目录下的所有文件
  63. //initdefault 指定默认的 init 级别是 5(多用户模式)。在定义初始的运行级别之后,则调用rc脚本以及参数5(运行级别)来启动系统,即rc脚本(参数5)会调用/etc/rc5.d下的所有脚本。
  64. l0:0:wait:/etc/init.d/rc 0 //使用级别0运行此程序
  65. l1:1:wait:/etc/init.d/rc 1
  66. l2:2:wait:/etc/init.d/rc 2
  67. l3:3:wait:/etc/init.d/rc 3
  68. l4:4:wait:/etc/init.d/rc 4
  69. //会运行该/etc/rc5.d下的3个脚本:
  70. //S10telnetd脚本:开启telnetd服务 start-stop-daemon --start --quiet --exec $telnetd
  71. //S20syslog脚本:开启syslog服务start-stop-daemon -S -b -n syslogd -a /sbin/syslogd -- -n $SYSLOG_ARGS start-stop-daemon -S -b -n klogd -a /sbin/klogd -- -n
  72. //S99rmnologin脚本:删除/etc/nologin文件 rm -f /etc/nologin /etc/nologin.boot
  73. l5:5:wait:/etc/init.d/rc 5
  74. l6:6:wait:/etc/init.d/rc 6
  75. z6:6:respawn:/sbin/sulogin//脚本运行等级为6时才执行 
  76. //在2、3、4、5级别上以ttyX为参数执行/sbin/mingetty程序,打开ttyX终端用于用户登录,
  77. //如果进程退出则再次运行mingetty程序(respawn),所以登录出错时,接着登录
  78. //缺省波特率是115200
  79. S2:2345:respawn:/sbin/mingetty ttyS2 //修改了mingetty和login程序,系统就可以在自动登录了
  80. /*************************etc/init.d/rcS***********************************/
  81. //设置PATH,runlevel,prevlevel环境变量,并export
  82. PATH=/sbin:/bin:/usr/sbin:/usr/bin
  83. runlevel=S
  84. prevlevel=N
  85. umask 022 //缺省的文件权限
  86. export PATH runlevel prevlevel
  87. if [ -x /sbin/unconfigured.sh ]//检查/sbin/unconfigured.sh是否可执行
  88. then
  89.   /sbin/unconfigured.sh//如果可执行就执行unconfigured.sh,我的根文件系统不存在此文件
  90. fi
  91. //执行default目录下的rcS,设置一些变量
  92. //即source /etc/default/rcS
  93. . /etc/default/rcS
  94. //trap可以使你在脚本中捕捉信号。该命令的一般形式为:trap name signal(s)
  95. //name是捕捉到信号以后所采取的一系列操作。实际应用中, name一般是一个专门来处理所捕捉信号的函数。
  96. //name需要用双引号( “ ” )引起来。signal就是待捕捉的信号。
  97. //这里就是捕捉INT QUIT TSTP三个信号,执行“:”,实际就是忽略这三个信号,防止脚本执行时使用ctrl-C 就退出脚本
  98. trap ":" INT QUIT TSTP
  99. //将执行/etc/init.d中rc,传入参数为“S”,目的就是为了执行/etc/init.d/rcS.d目录下的所有脚本文件,都是连接到/etc/init.d/目录下的链接
  100. //S02banner脚本建立tty设备节点:/bin/mknod -m 0666 /dev/tty c 5 0
  101. //S03sysfs脚本挂载proc和sysfs文件系统:mount -t proc proc /proc mount sysfs /sys -t sysfs
  102. //S03udev脚本:开启udev服务,后台运行udevd程序:/sbin/udevd -d
  103. //S06alignment脚本,输出cpu信息到proc文件系统
  104. //S10checkroot脚本
  105. //S20modutils.sh脚本:insmod module
  106. //S30ramdisk脚本:
  107. //S35mountall.sh脚本:挂载Mount all filesystems
  108. //S37populate-volatile.sh脚本
  109. //S38devpts.sh脚本:挂载mount -t devpts devpts /dev/pts
  110. //S39hostname.sh脚本:输出主机名称(arago)写入/etc/hostname文件:hostname -F /etc/hostname
  111. //S40networking脚本:开启网络服务
  112. //S45mountnfs.sh脚本:挂载nfs
  113. //S55bootmisc.sh脚本:
  114. //S98configure脚本:opkg-cl configure
  115. //S99finish.sh脚本:结束脚本
  116. exec /etc/init.d/rc S 
  117. //若rc.boot是目录,则执行rc.boot所有的脚本程序
  118. [ -d /etc/rc.boot ] && run-parts /etc/rc.boot
  119. //若setup.sh可执行,就执行,没有此程序
  120. if [ -x /sbin/setup.sh ]
  121. then
  122.   /sbin/setup.sh
  123. fi
  124. /*************************etc/init.d/rc***********************************/
  125. //这个脚本作用主要是运行/etc/rcS.d目录下的文件,
  126. //其中在 /etc/rcS.d/ 的目录下有一个 README 文本来说明该 /etc/rcS.d/ 目录下脚本的作用:
  127. //即 /etc/rcS.d/ 中是一些到 /etc/init.d/ 中脚本的符号连接。
  128. //执行完 /etc/rcS.d/ 中的脚本后,触发相应的 runlevel 事件,开始运行 /etc/rc.conf 脚本
  129. . /etc/default/rcS
  130. export VERBOSE //etc/default/rcS这个脚本中定义的VERBOSE=no
  131. startup_progress() {
  132.     step=$(($step + $step_change))
  133.     if [ "$num_steps" != "0" ]; then
  134.         progress=$((($step * $progress_size / $num_steps) + $first_step))
  135.     else
  136.         progress=$progress_size
  137.     fi
  138.     if type psplash-write >/dev/null 2>&1; then
  139.         TMPDIR=/mnt/.psplash psplash-write "PROGRESS $progress" || true
  140.     fi
  141. }
  142. startup() {
  143.   [ "$VERBOSE" = very ] && echo "INIT: Running $@..."//VERBOSE=no,所以后边的不打印
  144.     //以.sh结尾的脚本是必须执行的脚本,不是以.sh结尾的脚本服务是可以开启或关闭的,通过start或stop参数
  145.   case "$1" in//传入的第一个参数是要执行的文件名,第二个参数是start
  146.     *.sh)
  147.         (//若文件名是以.sh结尾的则执行这个脚本
  148.             trap - INT QUIT TSTP
  149.             scriptname=$1
  150.             shift
  151.             . $scriptname //执行这个脚本,不带参数
  152.         )
  153.         ;;
  154.     *)//若不是以.sh结尾的
  155.         /*实际上rc进程调用的脚本都称为初始化脚本。每个在/etc/init.d下的脚本都可以在执行时带上以下参数,如:start、stop、restart、pause、zap、status、ineed、iuse、needsme、usesme或者broken。
  156.         要启动、停止或者重启一个服务(和所有依赖于它的服务),应该用参数start、stop和restart。*/
  157.         "$@"//执行这个脚本带参数,比如传入的是$@=“/etc/rcS.d/S02banner start”,即带start参数执行这个脚本,这样可以灵活的控制服务的start或者stop
  158.         ;;
  159.   esac
  160.   startup_progress
  161. }
  162.     //忽略这三个信号,防止脚本执行时使用ctrl-C 就退出脚本
  163.   trap ":" INT QUIT TSTP
  164.     
  165.     //stty用于设置终端特性。在命令行中设置一个stty选项,一般格式为:stty name character
  166.     //以下将退格设置为^ H:stty erase '\^H',即ctrl+H在此脚本中是退格键
  167.   //设置终端,将 CR 字符映射为 NL 字符,避免阶梯效应
  168.   stty onlcr 0>&1
  169.   //Now find out what the current and what the previous runlevel are.
  170.   runlevel=$RUNLEVEL
  171.   //得到第一个参数是“S”,表示等级1,得到当前运行等级1,runlevel=S
  172.   [ "$1" != "" ] && runlevel=$1
  173.   if [ "$runlevel" = "" ]//运行等级为空的话,则退出
  174.   then
  175.     echo "Usage: $0 " >&2
  176.     exit 1
  177.   fi
  178.   previous=$PREVLEVEL
  179.   [ "$previous" = "" ] && previous=N
  180.     //传入参数是S的话,则$runleve=S $previous=N
  181.   export runlevel previous
  182.   //若$runlevel=“S”,即检查rcS.d是否为目录。
  183.   if [ -d /etc/rc$runlevel.d ]
  184.   then
  185.     //rcS.d是目录
  186.     PROGRESS_STATE=0
  187.     //Split the remaining portion of the progress bar into thirds
  188.     progress_size=$(((100 - $PROGRESS_STATE) / 3))//progress_size = 100/3 =33
  189.     case "$runlevel" in//runlevel=S
  190.         0|6)
  191.             first_step=-100
  192.             progress_size=100
  193.             step_change=1
  194.             ;;
  195.      S)
  196.             //Begin where the initramfs left off and use 2/3of the remaining space
  197.             first_step=$PROGRESS_STATE ///progress_size = 100/3 =33
  198.             progress_size=$(($progress_size * 2))//progress_size=66
  199.             step_change=1
  200.             ;;
  201.         *)
  202.             //Begin where rcS left off and use the final 1/3 ofthe space (by leaving progress_size unchanged)
  203.             first_step=$(($progress_size * 2 + $PROGRESS_STATE))
  204.             step_change=1
  205.             ;;
  206.     esac
  207.     num_steps=0
  208.     for s in /etc/rc$runlevel.d/[SK]*; //s取/etc/rcS.d目录下以S或K开头的文件名
  209.     do
  210.         //这句话的含义去掉变量s中所有的/etc/rcS.d/S??的部分
  211.         //例:s=/etc/rc$runlevel.d/S10checkroot,那么去掉/etc/rc$runlevel.d/K??部分后,s为checkroot
  212.     case "${s##/etc/rc$runlevel.d/S??}" in
  213.         gdm|xdm|kdm|reboot|halt)//若s剩下的文件名中为这五个则跳出for语句
  214.             break
  215.             ;;
  216.     esac
  217.     num_steps=$(($num_steps + 1))//num_steps递加,表示查找到此目录下/etc/rcS.d有多少个脚本
  218.   done//for语句结束
  219.   
  220.   step=0
  221.     //首先运行KILL脚本
  222.     if [ $previous != N ]//由于$previous=N,所以以下不执行
  223.     then
  224.         for i in /etc/rc$runlevel.d/K[0-9][0-9]*//取以K开头的文件名
  225.         do
  226.             //检查是否为常规文件
  227.             [ ! -f $i ] && continue
  228.             //Stop the service.
  229.             startup $i stop
  230.         done
  231.     fi
  232.     //然后运行这个级别的START脚本
  233.     for i in /etc/rc$runlevel.d/S*//取得S开头的脚本
  234.     do
  235.         [ ! -f $i ] && continue//检查是否为常规文件,不是则进行下次循环
  236.         if [ $previous != N ] && [ $previous != S ]//由于$previous=N,所以此if语句不执行
  237.         then
  238.             //Find start script in previous runlevel and stop script in this runlevel.
  239.             suffix=${i#/etc/rc$runlevel.d/S[0-9][0-9]}//获得i文件名的后缀,假如是S10checkroot,则suffix=checkroot
  240.             stop=/etc/rc$runlevel.d/K[0-9][0-9]$suffix //得到stop文件名,假如/etc/rc$runlevel.d/K[0-9][0-9]checkroot
  241.             previous_start=/etc/rc$previous.d/S[0-9][0-9]$suffix
  242.             //如果有起始脚本,并且没有停止脚本,则不进行这项服务,continue继续下一次循环
  243.             [ -f $previous_start ] && [ ! -f $stop ] && continue
  244.         fi
  245.         
  246.         case "$runlevel" in//runlevel = S
  247.             0|6)
  248.                 startup $i stop
  249.                 ;;
  250.             *)
  251.                 startup $i start//调用start函数,参数就是脚本名称
  252.                 ;;
  253.         esac
  254.     done//for循环结束
  255.     
  256.   fi
  257. /*************************etc/profile***********************************/
  258. //为启动shell设定一些环境变量
  259. PATH="/usr/local/bin:/usr/bin:/bin" 
  260. EDITOR="/bin/vi"            //needed for packages like cron
  261. test -z "$TERM" && TERM="vt100"    //Basic terminal capab. For screen etc.
  262. //若此文件存在,则设置时区
  263. if [ ! -e /etc/localtime ]; then
  264.     TZ="UTC"                
  265.     export TZ
  266. fi
  267. //显示用户ID是否为0,是0则设置用户的PATH路径
  268. if [ "`id -u`" -eq 0 ]; then
  269.    PATH=$PATH:/usr/local/sbin:/usr/sbin:/sbin:
  270. fi
  271. //设置环境变量PS1
  272. if [ "$PS1" ]; then
  273.    PS1='\u@\h:\w\$ '
  274. fi
  275. // /etc/profile.d是否为目录
  276. if [ -d /etc/profile.d ]; then
  277.   for i in /etc/profile.d/*.sh /*遍历目录下所有以.sh结尾的脚本文件,并执行*/
  278.   do
  279.     if [ -r $i ]; then
  280.       . $i
  281.     fi
  282.   done
  283.   unset i
  284. fi
  285. //可以设置登录后自动运行的APP
  286. . /etc/init.d/autorun-f    
  287. export PATH PS1 OPIEDIR QPEDIR QTDIR EDITOR TERM
  288. umask 022
  289. /*********************************************************mingetty.c**********************************************/
  290. /* name of this program (argv[0]) */
  291. static char *progname;
  292. /* on which tty line are we sitting? (e.g. tty1) */
  293. static char *tty;
  294. /* some information about this host */
  295. static struct utsname uts;
  296. /* the hostname */
  297. static char hn[MAXHOSTNAMELEN + 1];
  298. /* process and session ID of this program */
  299. static pid_t pid, sid;
  300. /* login program invoked */
  301. static char *loginprog = "/bin/login";
  302. /* Do not send a reset string to the terminal. */
  303. static int noclear = 0;
  304. /* Do not print a newline. */
  305. static int nonewline = 0;
  306. /* Do not print /etc/issue. */
  307. static int noissue = 0;
  308. /* Do not call vhangup() on the tty. */
  309. static int nohangup = 0;
  310. /* Do not print any hostname. */
  311. static int nohostname = 0;
  312. /* Print the whole string of gethostname() instead of just until the next "." */
  313. static int longhostname = 0;
  314. /* time to wait, seconds */
  315. static int delay = 0;
  316. /* chroot directory */
  317. static char *ch_root = NULL;
  318. /* working directory to change into */
  319. static char *ch_dir = NULL;
  320. /* 'nice' level of the program */
  321. static int priority = 0;
  322. /* automatic login with this user */
  323. static char *autologin = NULL;
  324. /* update_utmp() - update our utmp entry */
  325. static void update_utmp (void)
  326. {
  327.     struct utmp ut;
  328.     struct utmp *utp;
  329.     time_t cur_time;
  330.     setutent ();
  331.     while ((utp = getutent ()))
  332.         if (utp->ut_type == INIT_PROCESS && utp->ut_pid == pid)
  333.             break;
  334.     if (utp) {
  335.         memcpy (&ut, utp, sizeof (ut));
  336.     } else {
  337.         /* some inits don't initialize utmp... */
  338.         const char *x = tty;
  339.         memset (&ut, 0, sizeof (ut));
  340.         if (strncmp (x, "tty", 3) == 0)
  341.             x += 3;
  342.         if (strlen (x) > sizeof (ut.ut_id))
  343.             x += strlen (x) - sizeof (ut.ut_id);
  344.         strncpy (ut.ut_id, x, sizeof (ut.ut_id));
  345.     }
  346.     strncpy (ut.ut_user, "LOGIN", sizeof (ut.ut_user));
  347.     strncpy (ut.ut_line, tty, sizeof (ut.ut_line));
  348.     time (&cur_time);
  349.     ut.ut_time = cur_time;
  350.     ut.ut_type = LOGIN_PROCESS;
  351.     ut.ut_pid = pid;
  352.     ut.ut_session = sid;
  353.     pututline (&ut);
  354.     endutent ();
  355.     updwtmp (_PATH_WTMP, &ut);
  356. }
  357. /* open_tty - set up tty as standard { input, output, error } */
  358. static void open_tty (void)
  359. {
  360.     struct sigaction sa, sa_old;
  361.     char buf[40];
  362.     int fd;
  363.     //得到要打开的tty终端名
  364.     if (tty[0] == '/')
  365.         strcpy (buf, tty);
  366.     else {
  367.         strcpy (buf, "/dev/");
  368.         strcat (buf, tty);
  369.     }
  370.     
  371.     //修改设备文件属性,使其可以访问
  372.     if (chown (buf, 0, 0) || chmod (buf, 0600))
  373.         if (errno != EROFS)
  374.             error ("%s: %s", tty, strerror (errno));
  375.     sa.sa_handler = SIG_IGN;
  376.     sa.sa_flags = 0;
  377.     sigemptyset (&sa.sa_mask);
  378.     sigaction (SIGHUP, &sa, &sa_old);//终端关闭发出SIGHUP信号,忽略此信号
  379.     //打开tty终端设备,缺省波特率是115200
  380.     if ((fd = open (buf, O_RDWR, 0)) < 0)
  381.         error ("%s: cannot open tty: %s", tty, strerror (errno));
  382.     if (ioctl (fd, TIOCSCTTY, (void *) 1) == -1)
  383.         error ("%s: no controlling tty: %s", tty, strerror (errno));
  384.     if (!isatty (fd))
  385.         error ("%s: not a tty", tty);
  386.     
  387.     //
  388.     if (nohangup == 0) {
  389.         if (vhangup ())
  390.             error ("%s: vhangup() failed", tty);
  391.         close (2);
  392.         close (1);
  393.         close (0);
  394.         close (fd);
  395.         if ((fd = open (buf, O_RDWR, 0)) != 0)
  396.             error ("%s: cannot open tty: %s", tty,strerror (errno));
  397.         if (ioctl (fd, TIOCSCTTY, (void *) 1) == -1)
  398.             error ("%s: no controlling tty: %s", tty,strerror (errno));
  399.     }
  400.     
  401.     //将标准输入输出出错都复制给tty终端
  402.     if (dup2 (fd, 0) != 0 || dup2 (fd, 1) != 1 || dup2 (fd, 2) != 2)
  403.         error ("%s: dup2(): %s", tty, strerror (errno));
  404.     if (fd > 2)
  405.         close (fd);
  406.     if (noclear == 0)
  407.         write (0, "\033c", 2);
  408.     
  409.     //恢复原来SIGHUP的信号处理
  410.     sigaction (SIGHUP, &sa_old, NULL);
  411. }
  412. static void output_special_char (unsigned char c)
  413. {
  414.     switch (c) {
  415.     case 's':
  416.         printf ("%s", uts.sysname);
  417.         break;
  418.     case 'n':
  419.         printf ("%s", uts.nodename);
  420.         break;
  421.     case 'r':
  422.         printf ("%s", uts.release);
  423.         break;
  424.     case 'v':
  425.         printf ("%s", uts.version);
  426.         break;
  427.     case 'm':
  428.         printf ("%s", uts.machine);
  429.         break;
  430.     case 'o':
  431.         printf ("%s", uts.domainname);
  432.         break;
  433.     case 'd':
  434.     case 't':
  435.         {
  436.             time_t cur_time;
  437.             struct tm *tm;
  438. #if    0
  439.             char buff[20];
  440.             time (&cur_time);
  441.             tm = localtime (&cur_time);
  442.             strftime (buff, sizeof (buff),
  443.                 c == 'd'? "%a %b %d %Y" : "%X", tm);
  444.             fputs (buff, stdout);
  445.             break;
  446. #else
  447.             time (&cur_time);
  448.             tm = localtime (&cur_time);
  449.             if (c == 'd') /* ISO 8601 */
  450.                 printf ("%d-%02d-%02d", 1900 + tm->tm_year,tm->tm_mon + 1, tm->tm_mday);
  451.             else
  452.                 printf ("%02d:%02d:%02d", tm->tm_hour,tm->tm_min, tm->tm_sec);
  453.             break;
  454. #endif
  455.         }
  456.     case 'l':
  457.         printf ("%s", tty);
  458.         break;
  459.     case 'u':
  460.     case 'U':
  461.         {
  462.             int users = 0;
  463.             struct utmp *ut;
  464.             setutent ();
  465.             while ((ut = getutent ()))
  466.                 if (ut->ut_type == USER_PROCESS)
  467.                     users++;
  468.             endutent ();
  469.             printf ("%d", users);
  470.             if (c == 'U')
  471.                 printf (" user%s", users == 1 ? "" : "s");
  472.             break;
  473.         }
  474.     default:
  475.         putchar (c);
  476.     }
  477. }
  478. static void do_prompt (int showlogin)
  479. {
  480.     FILE *fd;
  481.     int c;
  482.     if (nonewline == 0)
  483.         putchar ('\n');
  484.     if (noissue == 0 && (fd = fopen ("/etc/issue", "r"))) {//存有Arago图案,打印此登录图案
  485.         while ((c = getc (fd)) != EOF) {
  486.             if (c == '\\')
  487.                 output_special_char (getc (fd));
  488.             else
  489.                 putchar (c);
  490.         }
  491.         fclose (fd);
  492.     }
  493.     if (nohostname == 0)
  494.         printf ("%s ", hn);
  495.     if (showlogin)
  496.         printf ("login: ");
  497.     fflush (stdout);
  498. }
  499. static char *get_logname (void)
  500. {
  501.     static char logname[40];
  502.     char *bp;
  503.     unsigned char c;
  504.     tcflush (0, TCIFLUSH);        /* flush pending input */
  505.     for (*logname = 0; *logname == 0;) {
  506.         do_prompt (1);//打印登录图案,argo:
  507.         for (bp = logname;;) {
  508.             if (read (0, &c, 1) < 1) {//从标准输入里读入一个字符
  509.                 if (errno == EINTR || errno == EIO|| errno == ENOENT)
  510.                     exit (EXIT_SUCCESS);
  511.                 error ("%s: read: %s", tty, strerror (errno));
  512.             }
  513.             if (c == '\n' || c == '\r') {//回车或换行符号表示结束
  514.                 *bp = 0;
  515.                 break;
  516.             } else if (!isprint (c))
  517.                 error ("%s: invalid character 0x%x in login"" name", tty, c);
  518.             else if ((size_t)(bp - logname) >= sizeof (logname) - 1)
  519.                 error ("%s: too long login name", tty);
  520.             else
  521.                 *bp++ = c;
  522.         }
  523.     }
  524.     return logname;
  525. }
  526. static struct option const long_options[] = {
  527.     { "autologin", required_argument, NULL, 'a' },
  528.     { "chdir", required_argument, NULL, 'w' },
  529.     { "chroot", required_argument, NULL, 'r' },
  530.     { "delay", required_argument, NULL, 'd' },
  531.     { "noclear", no_argument, &noclear, 1 },
  532.     { "nonewline", no_argument, &nonewline, 1 },
  533.     { "noissue", no_argument, &noissue, 1 },
  534.     { "nohangup", no_argument, &nohangup, 1 },
  535.     { "no-hostname", no_argument, &nohostname, 1 }, /* compat option */
  536.     { "nohostname", no_argument, &nohostname, 1 },
  537.     { "loginprog", required_argument, NULL, 'l' },
  538.     { "long-hostname", no_argument, &longhostname, 1 },
  539.     { "nice", required_argument, NULL, 'n' },
  540.     { 0, 0, 0, 0 }
  541. };
  542. int main (int argc, char **argv)
  543. {
  544.     char *logname, *s;
  545.     int c;
  546.     progname = argv[0];//程序名称,即mingetty
  547.     if (!progname)
  548.         progname = "mingetty";
  549.     /*struct utsname
  550.       { 
  551.          char sysname[_UTSNAME_SYSNAME_LENGTH];//当前操作系统名
  552.          char nodename[_UTSNAME_NODENAME_LENGTH];//网络上的名称
  553.          char release[_UTSNAME_RELEASE_LENGTH];//当前发布级别
  554.          char version[_UTSNAME_VERSION_LENGTH];//当前发布版本
  555.          char machine[_UTSNAME_MACHINE_LENGTH];//当前硬件体系类型
  556.          char domainname[_UTSNAME_DOMAIN_LENGTH]; //当前域名
  557.       };*/
  558.     uname (&uts);//获取当前内核名称和其它信息,并存于utsname结构中
  559.     gethostname (hn, MAXHOSTNAMELEN);//本地主机的标准主机名,存到数组hn中
  560.     hn[MAXHOSTNAMELEN] = '\0';
  561.     pid = getpid ();//取得进程ID
  562.     sid = getsid (0);//get the process group ID of session leader
  563.     putenv ("TERM=linux");//把字符串加到当前环境中,设置的环境仅对程序本身有效
  564.     
  565.     //解析命令行选项参数。
  566.     //字符串optstring可以下列元素:
  567.     //单个字符,表示选项,
  568.     //单个字符后接一个冒号:表示该选项后必须跟一个参数。参数紧跟在选项后或者以空格隔开。该参数的指针赋给optarg。
  569.     //单个字符后跟两个冒号,表示该选项后可以有参数也可以没有参数。如果有参数,参数必须紧跟在选项后不能以空格隔开。该参数的指针赋给optarg。(这个特性是GNU的扩张)。
  570.     while ((c = getopt_long (argc, argv, "a:d:l:n:w:r:", long_options,(int *) 0)) != EOF) {
  571.         switch (c) {
  572.         case 0:
  573.             break;
  574.         case 'a':
  575.             autologin = optarg;//获得参数-a后面的参数
  576.             break;
  577.         case 'd':
  578.             delay = atoi (optarg);
  579.             break;
  580.         case 'l':
  581.             loginprog = optarg;
  582.             break;
  583.         case 'n':
  584.             priority = atoi (optarg);
  585.             break;
  586.         case 'r':
  587.             ch_root = optarg;
  588.             break;
  589.         case 'w':
  590.             ch_dir = optarg;
  591.             break;
  592.         default:
  593.             usage ();
  594.         }
  595.     }
  596.     
  597.     //s指针保存主机名
  598.     if (longhostname == 0 && (s = strchr (hn, '.')))
  599.         *s = '\0';
  600.         
  601.     //获得终端结构,例如ttyS0等
  602.     tty = argv[optind];
  603.     if (!tty)
  604.         usage ();
  605.         
  606.     //终端名添加"/dev/",变成/dev/ttyS0
  607.     if (strncmp (tty, "/dev/", 5) == 0)
  608.         tty += 5;
  609.     update_utmp ();//更新登录信息
  610.     if (delay)
  611.         sleep (delay);
  612.     open_tty ();//打开终端设备
  613.     
  614.     if (autologin) { //如果前边参数为-a且-a后边带有登录名,则会设置autologin即自动登录
  615.         do_prompt (0);//打印登录图形
  616.         printf ("login: %s (automatic login)\n", autologin);
  617.         logname = autologin;//获得自动登录名
  618.     } else//不是自动登录
  619.         while ((logname = get_logname ()) == 0);//获得登录名
  620.     
  621.     if (ch_root)
  622.         chroot (ch_root);
  623.     if (ch_dir)
  624.         chdir (ch_dir);
  625.     if (priority)
  626.         nice (priority);
  627.     
  628.     //带着登录名参数,执行loginprog=/bin/login程序登录,即login--logname
  629.     execl (loginprog, loginprog, autologin? "-f" : "--", logname, NULL);
  630.     /*login函数片段
  631.     。。。。。。。    
  632.     strcpy(buff, "exec ");
  633.     strcat(buff, pwd->pw_shell);
  634.     childArgv[childArgc++] = "/bin/sh";
  635.     childArgv[childArgc++] = "-sh";
  636.     childArgv[childArgc++] = "-c";
  637.     childArgv[childArgc++] = buff;
  638.     childArgv[childArgc++] = NULL;
  639.     //登录成功,执行/bin/sh进入shell
  640.   execvp(childArgv[0], childArgv + 1);
  641.     */
  642.     //启动shell后,首先启动 /etc/profile 文件,然后再启动用户目录下的 ~/.bash_profile、 ~/.bash_login或 ~/.profile文件中的其中一个,执行的顺序为:~/.bash_profile、 ~/.bash_login、 ~/.profile。如果 ~/.bash_profile文件存在的话,一般还会执行 ~/.bashrc文件。
  643.     error ("%s: can't exec %s: %s", tty, loginprog, strerror (errno));
  644.     sleep (5);
  645.     exit (EXIT_FAILURE);
  646. }
posted @ 2018-03-01 16:08  竹主  阅读(2527)  评论(0编辑  收藏  举报