busybox的init
busybox中的init程序可以完成初始化的基本功能而且去掉了System V系统init程序的多余功能,init程序执行的顺序如下:
<1>.创建init的信号处理<2>.初始化控制台
<3>.分析inittab文件,/etc/inittab
<4>.执行系统初始化脚本,/etc/init.d/rcS
<5>.执行所有inittab中类型为wait的程序
<6>.执行所有inittab中类型为once的程序
完成以上步骤后,init循环执行所有inittab中类型为respawn和askfirst的程序。文件inittab中的每一行配置遵照格式如下:
id:runlevel:action:process
虽然这个格式与传统的System V init 类似,但是id 域的含义不同。id 通常是用来指定控制启动进程运行的tty。如果id域为空则代表这个进程的运行不需要与shell交互。在busybox中,runlevel域 可以完全忽略。process 指明执行程序以及所带参数选项。 action 域可以选择一下八个类型:
sysinit提供给init初始化脚本所在的路径;
respawn当进程终止后将其重新启动;
askfirst与respawn类似,不同之处是在终端提示“Please press Enter to activate this console.”;
wait代表init等待这个程序执行完后才继续执行;
once表示执行一次而且不需要等待完成;
ctrlaltdel表示当按下Ctrl-Alt-Delete时执行;
shutdown表示程序在关机时执行;
restart表示程序在重启时执行,通常是init本身。
askfirst改成respawn就得.
ttyS0上没shell,想要的话在/etc/inittab中加:
ttyS0::respawn:-/bin/sh
BusyBox - The Swiss Army Knife of Embedded Linux
Prev Chapter 3. BusyBox Commands Next
--------------------------------------------------------------------------------
init
Usage: init
Init is the parent of all processes.
This version of init is designed to be run only by the kernel.
BusyBox init doesn't support multiple runlevels. The runlevels field of the /etc/inittab file is completely ignored by BusyBox init. If you want runlevels, use sysvinit.
BusyBox init works just fine without an inittab. If no inittab is found, it has the following default behavior:
::sysinit:/etc/init.d/rcS
::askfirst:/bin/sh
If it detects that /dev/console is _not_ a serial console, it will also run:
tty2::askfirst:/bin/sh
If you choose to use an /etc/inittab file, the inittab entry format is as follows:
:::
id
WARNING: This field has a non-traditional meaning for BusyBox init! The id field is used by BusyBox init to specify the controlling tty for the specified process to run on. The contents of this field are appended to "/dev/" and used as-is. There is no need for this field to be unique, although if it isn't you may have strange results. If this field is left blank, the controlling tty is set to the console. Also note that if BusyBox detects that a serial console is in use, then only entries whose controlling tty is either the serial console or /dev/null will be run. BusyBox init does nothing with utmp. We don't need no stinkin' utmp.
runlevels
The runlevels field is completely ignored.
action
Valid actions include: sysinit, respawn, askfirst, wait, once, and ctrlaltdel.
The available actions can be classified into two groups: actions that are run only once, and actions that are re-run when the specified process exits.
Run only-once actions:
'sysinit' is the first item run on boot. init waits until all sysinit actions are completed before continuing. Following the completion of all sysinit actions, all 'wait' actions are run. 'wait' actions, like 'sysinit' actions, cause init to wait until the specified task completes. 'once' actions are asyncronous, therefore, init does not wait for them to complete. 'ctrlaltdel' actions are run immediately before init causes the system to reboot (unmounting filesystems with a 'ctrlaltdel' action is a very good idea).
Run repeatedly actions:
'respawn' actions are run after the 'once' actions. When a process started with a 'respawn' action exits, init automatically restarts it. Unlike sysvinit, BusyBox init does not stop processes from respawning out of control. The 'askfirst' actions acts just like respawn, except that before running the specified process it displays the line "Please press Enter to activate this console." and then waits for the user to press enter before starting the specified process.
Unrecognized actions (like initdefault) will cause init to emit an error message, and then go along with its business. All actions are run in the reverse order from how they appear in /etc/inittab.
process
Specifies the process to be executed and its command line.
Example /etc/inittab file
# This is run first except when booting in single-user mode.
#
::sysinit:/etc/init.d/rcS
# /bin/sh invocations on selected ttys
#
# Start an "askfirst" shell on the console (whatever that may be)
::askfirst:-/bin/sh
# Start an "askfirst" shell on /dev/tty2-4
tty2::askfirst:-/bin/sh
tty2::askfirst:-/bin/sh
tty2::askfirst:-/bin/sh
# /sbin/getty invocations for selected ttys
#87501810
tty4::respawn:/sbin/getty 38400 tty5
tty5::respawn:/sbin/getty 38400 tty6
# Example of how to put a getty on a serial line (for a terminal)
#
#::respawn:/sbin/getty -L ttyS0 9600 vt100
#::respawn:/sbin/getty -L ttyS1 9600 vt100
#
# Example how to put a getty on a modem line.
#::respawn:/sbin/getty 57600 ttyS2
# Stuff to do before rebooting
::ctrlaltdel:/bin/umount -a -r
::ctrlaltdel:/sbin/swapoff
mknod: /dev/ptyp1: File exists
mknod: /dev/ttyp1: File exists
后台程序没有启动或fork进程自动退出---busybox init启动思考
我的busybox inittab有如下行:
ttyS1::sysinit:/etc/init.d/rcS
ttyS1::respawn:-/bin/sh
然后在rcS调用的启动进程脚本中,有文件S50update.sh:
upgrade_osd &
然后我发现启动过程中,upgrade_osd根本没有启动。
我到upgrade_osd的main中,fork全部过程,然后在S50update.sh:
upgrade_osd
然后发现启动了,但是又退出了,尽管我的upgrade_osd设计是不退出的。
后来了解busybox init的启动,尤其是inittab的描述,理解如下:
ttyS1::sysinit:/etc/init.d/rcS (其中sysinit表示启动是调用)
之后调用ttyS1::respawn:-/bin/sh
不是kill了之前的shell然后重新启动一个。
所以修改如下:
::sysinit:/etc/init.d/rcS
ttyS1::respawn:-/bin/sh
重要参考:
如果存在/etc/inittab文件,Busybox init程序解析之,然后按照文件的只是创建各种子进程,否则使用默认的配置创建子进程。/etc/inittab文件中每个条目用来定义一个子进程,并确定它的启动方法,格式如下:
<id>:<runlevels>:<action>:<process>
<id>: 表示这个子进程要使用的控制台(即标准输入、标准输出、标准错误设备)。如果省略,则使用与init进
程一样的控制台;
<runlevels>: 对于Busybox init程序,没有意义,可以省略;
<action>: 表示init进程如何控制这个子进程,有8中取值:
sysinit, wait, once, respawn, askfirst, shutdown, restart, ctrlaltdel
<process>: 要执行的程序,可以是可执行程序,也可以是脚本,如果<process>字段前有“-”字符,这个程序
被称为“交互的”。
在/etc/inittab文件的控制下,init进程的行为总结如下:
1>在系统启动前期,init进程首先启动<action>为sysinit, wait, once的3类子进程
2>在系统正常启动期间,init进程首先启动<action>为respawn, askfirst的两类子进程,并监视它们,发现
某个子进程退出时重新启动它
3>在系统退出时,执行<action>为shutdown, restart, ctrlaltdel的3类子进程(之一或全部)。
如果根文件系统中没有/etc/inittab文件,Busybox init程序使用默认的inittab条目:
::sysinit:/etc/init.d/rcS
::askfirst:-/bin/sh
tty2::askfirst:-/bin/sh
tty3::askfirst:-/bin/sh
tty4::askfirst:-/bin/sh
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a
::restart:/sbin/init
由于init=/linuxrc,因此,在文件系统挂载后,运行的第一个程序就是根目录下的linuxrc,而这是一个指向/bin/busybox 的链接,也就是说,系统起来后运行的第一个程序就是busybox本身。
busybox首先将试图解析/etc/inittab来获取进一步的初始化配置信息(参考busybox源代码init/init.c中的parse_inittab()函数)。而事实上,root_qtopia中并没有/etc/inittab这个配置文件,根据busybox的裸机,它将生成默认的配置。其中最重要的一个,就是new_init_action(SYSINIT,INIT_SCRIPT,""),也就决定了接下来初始化的脚本是INIT_SCRIPT所定义的值,这个宏的默认值是"etc/init.d/rcS"。
下面是文件系统中/etc/init.d/rcS的内容,也是我们要分析的重点
1.PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:
2.runlevel=S
3.prevlevel=N
4.umask 022
5.export PATH runlevel prevlevel
##为启动环境设置必要的环境变量;
1./bin/hostname FriendlyARM
##设置机器名字;
1./bin/mount -n -t proc none /proc
2./bin/mount -n -t sysfs none /sys
3./bin/mount -n -t usbfs none /proc/bus/usb
4./bin/mount -t ramfs none /dev
##挂载"虚拟"文件系统"/proc"和"/sys",并且在/dev目录下挂载一个ramfs,相当于把原本nandflash上的只读的/dev目录"覆盖"上一块可写的空的SDRAM。
##这里要注意的是,/sys和挂载了ramfs的/dev是正确创建设备节点的关键。对于2.6.29内核来说,已经没有devfs的支持,创建设备节点只有通过两种办法由文件系统完成:
1)制作文件系统镜像前用mknod手动创建好系统所有的(包括可能有的)设备节点,并把这些节点文件一起做进文件系统镜像中;
2)在文件系统初始化过程中,通过/sys目录所输出的信息,在/dev目录下动态的创建系统中当前实际有的设备节点。
显然,方法1)有很大的局限性,仅限于没有设备动态增加或减少的情况,不适用于很多设备热插拔的情况,比如U盘,SD卡等等。方法2)是目前大多数PC上的linux的做法(基于udev实现)。这种方法有2个前提:/sys目录挂载和一个可写的/dev目录。这也就是为什么我们这里需要挂载/sys和ramfs在/dev目录上,事实上,这种方法最早就是为热插拔设计的,你可以理解为当系统启动时,所有设备一下子全部"插入"了进来。
这里有一点要说明的是,在文件系统初始化跑到这里之前,原来的/dev目录下必须有一个设备节点:/dev/console。
其实,要搞清楚"程序"这种东西,没有什么好的办法,无非2个东西,源码和脚本
1.echo /sbin/mdev> /proc/sys/kernel/hotplug
2./sbin/mdev -s
3./bin/hotplug
##这几个就是用来完成上面所说的两个东西:1)通过mdev -s在/dev目录下创建必要的设备节点;2)设置内核的hotplug handler 为mdev,即当设备热插拔时,由mdev接受来自内核的消息并作出相应的回应,比如挂载U盘。
对于mdev,需要注意的是,文件系统里存在/etc/mdev.conf文件,它包含了medv的配置信息。通过这个文件,我们可以自定义一些设备节点的名称或链接来满足特定的需要。这是root qtopia中mdev.conf的内容:
1.#system all-writable devices
2.full 0:0 0666
3.null 0:0 0666
4.ptmx 0:0 0666
5.random 0:0 0666
6.tty 0:0 0666
7.zero 0:0 0666
8.
9.#console devices
10.tty[0-9]* 0:5 0660
11vc/[0-9]* 0:5 0660
12.
13. # serial port devices
14. s3c2410_serial0 0:5 0666 =ttySAC0
15. s3c2410_serial1 0:5 0666 =ttySAC1
16. s3c2410_serial2 0:5 0666 =ttySAC2
17. s3c2410_serial3 0:5 0666 =ttySAC3
18. 19. # loop devices
20. loop[0-9]* 0:0 0660 =loop/
21.
22. # i2c devices
23. i2c-0 0:0 0666 =i2c/0
24. i2c-1 0:0 0666 =i2c/1
25.
26. # frame buffer devices
27. fb[0-9] 0:0 0666
28.
29. # input devices
30. mice 0:0 0660 =input/
31. mouse.* 0:0 0660 =input/
32. event.* 0:0 0660 =input/
33. ts.* 0:0 0660 =input/
34.
35. # rtc devices
36. rtc0 0:0 0644 >rtc
37. rtc[1-9] 0:0 0644
38.
39. # misc devices
40. mmcblk0p1 0:0 0600 =sdcard */bin/hotplug
41. sda1 0:0 0600 =udisk * /bin/hotplug
可以看到,原本串口驱动注册的设备名是 s3c2410_serial0, s3c2410_serial1 和
s3c2410_serial2,而 mdev 则会在/dev 目录下对应生成 ttySAC0, ttySAC1和ttySAC2以符合
应用程序对于串口设备名称的习惯。同样的,/dev/sdcard和/dev/udisk 永远分别指向 SD 卡和
U盘的第一个分区。(所以,用那些没有分区表的SD卡或U盘的兄弟知道原因了吧...)
BusyBox init及其inittab文件分析(转)
/* Line is: "id:runlevel_ignored:action:command" */
这是BusyBox-1.11.1中init.c文件中的一句注释,该注释指明了inittab文件中每行的格式。以下对各字段进行简要解析:
1、id
尽管该格式与发行版linux的Sys V init类似,但是,id在BusyBox的init中具有不同的意义。对BusyBox而言,id用来指定启动进程的控制终端 。如果所启动的进程并不是可以交互的shell,例如BusyBox的sh(ash),应该会有个控制终端,如果控制终端不存在,BusyBox的sh会报错。
2、runlevel_ignored
由该字段的名称可知,BusyBox init忽略runlevel_ignored字段 ,所以配置inittab时空着它就行了。
4、command
command字段用来指定要执行命令(含路径),包括命令行选项。
3、action
在BusyBox-1.11.1中init.c定义了以下8种action
其中,STR_SYSINIT、STR_RESPAWN、STR_ASKFIRST、STR_WAIT、STR_ONCE、 STR_CTRLALTDEL、STR_SHUTDOWN、STR_RESTART为action_type,即action的编码。它们各占1字节,具体定义如下:
下表列举了这 8种action的含义 :
下面简要介绍一下BusyBox init怎么对inittab进行分析执行的。由BusyBox-1.11.1中init.c文件可知,BusyBox init通过init_main方法对inittab文件的分析执行,大致过程如下:
1、init_main方法先通过parse_inittab分析inittab文件,将该文件中的每一行通过 new_init_action(uint8_t action_type, const char *command, const char *cons)添加到init_action_list列表中。其中cons就是每行的id字段。init_action_list的定义如下:
若不支持ENABLE_FEATURE_USE_INITTAB或支持ENABLE_FEATURE_USE_INITTAB但inittab文件不存在,则执行一个默认的操作,如下:
通过代码中的英文注释,应该都可以看懂该代码。需要解释可能只有INIT_SCRIPT。INIT_SCRIPT的定义如下:
#define INIT_SCRIPT "/etc/init.d/rcS" /* Default sysinit script. */
即, BusyBox init默认的初始化脚本是/etc/init.d/rcS。
当支持ENABLE_FEATURE_USE_INITTAB且inittab文件存在时,BusyBox init会对inittab文件进行如下分析:
2、BusyBox init分析完inittab之后,就是执行各command了。BusyBox init通过run_actions(int action_type)方法,查找init_action_list列表中action类型为action_type的所有init_action,并为符合条件的init_action节点调用run(const struct init_action *a)。而实际上,run方法中,是通过init_exec(a->command)来执行具体的command。run_actions的源码如下:
由run_actions源码可知:action_type为SYSINIT、WAIT、CTRLALTDEL、SHUTDOWN 、 RESTART和ONCE的init_action,执行其command后,都会通过delete_init_action(struct init_action *action)将它从init_action_list中删除,这样做的原因可能是:这些init_action节点只会被BusyBox init执行一次,并且删除它们可提高对init_action_list其它类型的init_action节点的查找效率并节省空间。结合以下 BusyBox init对各类init_action的执行顺序,可能会更好的理解这一点。
由init_main方法可知,BusyBox init对各类init_action的执行顺序如下 :
/* Now run everything that needs to be run */
/* First run the sysinit command */
run_actions( SYSINIT) ;
/* Next run anything that wants to block */
run_actions( WAIT) ;
/* Next run anything to be run only once */
run_actions( ONCE) ;
/* Now run the looping stuff for the rest of forever */
while ( 1) {
/* run the respawn/askfirst stuff */
run_actions( RESPAWN | ASKFIRST) ;
/* Don't consume all CPU time -- sleep a bit */
sleep ( 1) ;
/* Wait for any child process to exit */
wpid = wait( NULL ) ;
while ( wpid > 0) {
/* Find out who died and clean up their corpse */
for ( a = init_action_list; a; a = a- > next) {
if ( a- > pid = = wpid) {
/* Set the pid to 0 so that the process gets
* restarted by run_actions() */
a- > pid = 0;
message( L_LOG, "process '%s' (pid %d) exited. "
"Scheduling for restart." ,
a- > command, wpid) ;
}
}
/* see if anyone else is waiting to be reaped */
wpid = wait_any_nohang( NULL ) ;
}
}
}
要写自己的inittab,需要理解busybox的inittab文件格式。
busybox的inittab文件与通常的inittab不同,它没有runlevel的概念,语句功能上也有限制。inittab语句的标准格式是
<id>:<runlevels>:<action>:<process> 各字段的含义如下
<id>: id字段与通常的inittab中的含义不同,它代表的是这个语句中process执行所在的tty设备,内容就是/dev目录中tty设备的文件名。由于是运行process的tty设备的文件名,所以也不能象通常的inittab那样要求每条语句id的值唯一。
<runlevels>: busybox不支持runlevel,所以此字段完全被忽略。
<action>: 为下列这些值之一:
sysinit, respawn, askfirst, wait,once, restart, ctrlaltdel, shutdown
其 含义与通常的inittab的定义相同。特别提一下askfirst,它的含义与respawn相同,只是在运行process前,会打出一句话 “please press Enter to active this console”,然后等用户在终端上敲入回车键后才运行process。
<process>: 指定要运行的process的命令行。
busybox init进程分析
转自: http://www.haogongju.net/art/1405286
本人喜欢用代码+伪代码的方式写笔记。文中的花括号可能是方便记录而已。
如:
hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module)
{
问:怎么获得模块信息的?
答:hardware\libhardware\Hardware.c
...........
}
原创分析, 转载请注明出处:http://www.cnblogs.com/langlang/
作者email: dayhappyhappy@163.com
文件 init.c
int init_main( int argc UNUSED_PARAM, char **argv)
{
// 填充链表init_action_list
parse_inittab();
原型:new_init_action(uint8_t action_type, const char * command, const char *cons)
{
// 读取文件 /etc/inittab
parser_t *parser = config_open2( " /etc/inittab ", fopen_for_read);
// 假如 /etc/inittab不存在
if (parser == NULL)
{
new_init_action(CTRLALTDEL, " reboot ", "");
原型 new_init_action(uint8_t action_type, const char *command, const char *cons)
{
struct init_action *a, **nextp;
{
注 : struct init_action {
struct init_action *next; // 链表指针
pid_t pid; // 线程ID
uint8_t action_type; // action的类型 ①
char terminal[CONSOLE_NAME_SIZE]; // 运行该command的终端 (*cons)
char command[COMMAND_SIZE]; // command字段用来指定要执行命令(含路径),包括命令行选项。
};
}
nextp = &init_action_list;
假如init_action_list不为空
{
比较查找
}
假如为空
{
a = xzalloc( sizeof(*a));
}
a->action_type = action_type;
safe_strncpy(a->command, command, sizeof(a->command));
safe_strncpy(a->terminal, cons, sizeof(a->terminal));
}
..........
}
else
{
// 分析
new_init_action( 1 << action, token[ 3], tty);
}
}
/* Now run everything that needs to be run */
/* First run the sysinit command */
run_actions(SYSINIT);
原型: void run_actions( int action_type)
{
struct init_action *a;
for (a = init_action_list; a; a = a->next) {
if (!(a->action_type & action_type))
continue; // 继续查找
if (a->action_type & (SYSINIT | WAIT | ONCE | CTRLALTDEL | SHUTDOWN)) {
pid_t pid = run(a); // 执行
{
创建进程
// ASKFIRST类型需要等待回车
if (BB_MMU && (a->action_type & ASKFIRST)) {
static const char press_enter[] ALIGN1 =
" \nPlease press Enter to activate this console. ";
char c;
// 输出
dbg_message(L_LOG, " waiting for enter to start '%s' "
" (pid %d, tty '%s')\n ",a->command, getpid(), a->terminal);
full_write(STDOUT_FILENO, press_enter, sizeof(press_enter) - 1);
while (safe_read(STDIN_FILENO, &c, 1) == 1 && c != ' \n ')
continue; // 等待回车
}
}
if (a->action_type & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN))
waitfor(pid); // 待它的command执行完,再继续执行。
}
if (a->action_type & (RESPAWN | ASKFIRST)) {
/* Only run stuff with pid == 0. If pid != 0,
* it is already running
*/
if (a->pid == 0)
a->pid = run(a); // init则不会等待它执行完
}
}
}
check_delayed_sigs();
/* Next run anything that wants to block */
run_actions(WAIT);
check_delayed_sigs();
/* Next run anything to be run only once */
run_actions(ONCE);
while ( 1) {
/* (Re)run the respawn/askfirst stuff */
run_actions(RESPAWN | ASKFIRST);
maybe_WNOHANG |= check_delayed_sigs();
/* Don't consume all CPU time - sleep a bit */
sleep( 1);
maybe_WNOHANG |= check_delayed_sigs();
}
}
① action_type
/* 为init提供初始化命令脚本的路径 */
#define SYSINIT 0x01
/* 告诉init必须等到相应的进程执行完成之后才能继续执行 */
#define WAIT 0x02
/* 仅执行相应的进程一次,而且不会等待它执行完成 */
#define ONCE 0x04
/* 每当相应的进程终止执行时,重新启动该进程 */
#define RESPAWN 0x08
/* 类似respawn,主要用途是减少系统上执行的终端应用程序的数量。
它将会促使init在控制台上显示
“Please press Enter to active this console”的信息,
并在重新启动进程之前等待用户按下“enter”键 */
#define ASKFIRST 0x10
/* 当按下Ctrl+Alt+Delete组合键时,执行相应的进程 */
#define CTRLALTDEL 0x20
/* 当系统关机时,执行相应的进程 */
#define SHUTDOWN 0x40
/* 当init重新启动时,执行相应的进程,通常此处所执行的进程就是init本身 */
但是我们的最终目的不是启动init进程,而是运行客户的程序!
那么init进程是如何选择性的运行客户的程序呢?我们猜测init进程肯定需要:
(1) 读取一个配置文件
(2) 解析该配置文件
(3) 根据配置文件执行客户的程序
下面我们来阅读busybox中init程序的源码,在init.c中的init_main()中:
1.首先是设置信号
2.初始化/dev/console
3.解析配置文件
内核启动/sbin/init是没有传如何参数,所以进入parse_inittab()函数,
我们进入到该函数:
由此可以知道init进程读取的配置文件就是/etc/inittab,busybox中的inittab文件中规定了/etc/inittab内容的填写格式如下:
<id>:<runlevel>:<action>:<process>
Id:id会加上一个/dev前缀作为一个控制终端(stdin,stdout,stderr)
Runlevel:忽略
Action:执行的时机,包括SYSINIT,WAIT,ONCE, RESPAWN,ASKFIRST等
Process:要执行的应用程序或者脚本
继续分析parse_inittab():
如果配置文件/etc/inittab不存在的话则执行if语句,也就是说如果没/etc/inittab的话init进程会直接调用new_init_action来构造默认配置项,根据if语句里的内容,我们可以反推出等效的/etc/inittab的内容如下:
::CTRLALTDEL:reboot
::SHUTDOWN:umount -a –r
::RESTART:init
::ASKFIRST:-/bin/ah
tty2:: ASKFIRST:-/bin/sh
tty3:: ASKFIRST:-/bin/sh
tty4:: ASKFIRST:-/bin/sh
::SYSINIT:/etc/init.d/rcS
继续分析parse_inittab():
后面就是对配置文件/etc/inittab里的内容里的解析了,如#则视作注释等等,最后就调用new_init_action(a->action, command, id);将/etc/inittab里的每一条配置项做成一个init_action结构体并添加到具有相同执行时机的init_action_list中去。说到底解析/etc/inittab这个配置文件就是为了把各配置项添加到对应的init_action_list中去。
这样parse_inittab()这个函数就结束了,我们继续看init_main()这个函数:
4.开始运行parse_inittab()帮我们添加到init_action_list中的程序或脚本:
run_actions为运行一类程序或脚本,这里的一类就是按照执行时机来分类的。由上面这段代码我们就可以看出执行时机的优先级了:SYSINIT> WAIT> ONCE> RESPAWN> ASKFIRST,具体的可以继续分析源码