Summary
In this paper, through a brief analysis of init process source code, carding its processing process, focusing on how to start the application program init process, summed up the idea of writing the start script file.
Source code analysis of init process
The init process is the first process launched by the Linux kernel. How do you know? From the analysis of the kernel_init() function of the kernel source code linux-2.6.xxx/init/main.c, it can be found that the kernel will start the first process according to the parameters passed in by uboot, which is usually init.
How to start it? Call the kernel_execve() function to complete, guess from the root file system / sbin/init to start, any linux application is based on the file system, starting the application before the root file system has been mounted. Well, where does the root filesystem come from? It's compiled and compiled by busybox, so to analyze init source code, go to busybox to find init source code.
Source location: / busybox/init/init.c. Looking for the main() function, we find that only init_main() has no main(), so we can guess that busybox modifies the entry of init process to init_main() through some methods. In fact, all the command tools of busybox are a link to the busybox program.
cd /sbin ls -l init lrwxrwxrwx 1 root 0 14 Nov 16 2016 init -> ../bin/busybox
As you can see, the init process is actually a link to busybox, regardless of it, just know that the entry of the init process is the init_main() function.
#if DEBUG_SEGV_HANDLER { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = handle_sigsegv; sa.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); ...... } #endif ...... console_init(); set_sane_term(); ...... /* Make sure environs is set to something sane */ putenv((char *) "HOME=/"); putenv((char *) bb_PATH_root_path); putenv((char *) "SHELL=/bin/sh"); putenv((char *) "USER=root"); /* needed? why? */
This is the first part of the init process. Set up some signal-related things, initialize console, and then set up environment variables. It doesn't seem to have anything to do with starting app. Never mind, keep looking down.
/* Check if we are supposed to be in single user mode */ if (argv[1] && (strcmp(argv[1], "single") == 0 || strcmp(argv[1], "-s") == 0 || LONE_CHAR(argv[1], '1')) ) { /* ??? shouldn't we set RUNLEVEL="b" here? */ /* Start a shell on console */ new_init_action(RESPAWN, bb_default_login_shell, ""); } else { /* Not in single user mode - see what inittab says */ /* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined, * then parse_inittab() simply adds in some default * actions (i.e., INIT_SCRIPT and a pair * of "askfirst" shells) */ parse_inittab(); }
This code is an if judgment. The comment says that if it is a single user mode, it will go to the first half of the code. If it is not a single user mode, it will call the parse_inittab() function. Because the init process started by the kernel does not pass in additional parameters, argv[1] does not exist, and the program will go to parse_inittab().
The annotation also says that if the macro CONFIG_FEATURE_USE_INITTAB is not defined, the program will execute some default action s. How do you know if the macro is defined? Guess that the macro should be an option for busybox configuration. OK, how to view busybox configuration, the same as linux kernel configuration, combined with make menuconfig and config files at all levels?
Is the macro CONFIG_FEATURE_USE_INITTAB defined?
Execute make meunconfig in busybox to enter the familiar configuration interface
Look at it. It's like there's an Init Utilities item associated with init. Go in.
Here is a "Support reading an inittab file", which is selected. The word "inittab" is described. It is very similar to parse_inittab() mentioned in the init source code. OK, make menuconfig first. Look at the configuration file, open the Config.in of the top directory, and search "init" globally. Only the bottom one is found:
source init/Config.in
Enter the init folder, open the Config.in file, and find the configuration items
config FEATURE_USE_INITTAB bool "Support reading an inittab file" default y depends on INIT help Allow init to read an inittab file when the system boot.
That's right. The macro CONFIG_FEATURE_USE_INITTAB is indeed defined. Go back to init source code analysis and enter the parse_inittab() function. First of all, there's a large comment before this function, and see what it says.
/* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined, * then parse_inittab() simply adds in some default * actions (i.e., runs INIT_SCRIPT and then starts a pair * of "askfirst" shells). If CONFIG_FEATURE_USE_INITTAB * _is_ defined, but /etc/inittab is missing, this * results in the same set of default behaviors. */
If you define XXX macro, but / etc/inittab file does not exist, you will follow the default action. Okay, guess roughly, parse_inttab() function seems to have something to do with the start of app to be analyzed. If you define XXX macro, you will parse / etc/inittab file and execute what is inside, if you do not define XXX. Macros or / etc/inittab files do not exist, and some default things are executed
Okay, to figure out one thing, / etc/inittab is very important. You may need to create this file by yourself and write something in it, but what? It's not known yet. What does the default action mean if you don't follow the path of / etc/inittab? Let's analyze the function parse_inittab().
static void parse_inittab(void) { #if ENABLE_FEATURE_USE_INITTAB char *token[4]; parser_t *parser = config_open2("/etc/inittab", fopen_for_read); if (parser == NULL) #endif { /* No inittab file - set up some default behavior */ /* Sysinit */ new_init_action(SYSINIT, INIT_SCRIPT, ""); /* Askfirst shell on tty1-4 */ new_init_action(ASKFIRST, bb_default_login_shell, ""); //TODO: VC_1 instead of ""? "" is console -> ctty problems -> angry users new_init_action(ASKFIRST, bb_default_login_shell, VC_2); new_init_action(ASKFIRST, bb_default_login_shell, VC_3); new_init_action(ASKFIRST, bb_default_login_shell, VC_4); /* Reboot on Ctrl-Alt-Del */ new_init_action(CTRLALTDEL, "reboot", ""); /* Umount all filesystems on halt/reboot */ new_init_action(SHUTDOWN, "umount -a -r", ""); /* Swapoff on halt/reboot */ new_init_action(SHUTDOWN, "swapoff -a", ""); /* Restart init when a QUIT is received */ new_init_action(RESTART, "init", ""); return; } #if ENABLE_FEATURE_USE_INITTAB /* optional_tty:ignored_runlevel:action:command * Delims are not to be collapsed and need exactly 4 tokens */ while (config_read(parser, token, 4, 0, "#:", PARSE_NORMAL & ~(PARSE_TRIM | PARSE_COLLAPSE))) { /* order must correspond to SYSINIT..RESTART constants */ static const char actions[] ALIGN1 = "sysinit\0""wait\0""once\0""respawn\0""askfirst\0" "ctrlaltdel\0""shutdown\0""restart\0"; int action; char *tty = token[0]; if (!token[3]) /* less than 4 tokens */ goto bad_entry; action = index_in_strings(actions, token[2]); if (action < 0 || !token[3][0]) /* token[3]: command */ goto bad_entry; /* turn .*TTY -> /dev/TTY */ if (tty[0]) { tty = concat_path_file("/dev/", skip_dev_pfx(tty)); } new_init_action(1 << action, token[3], tty); if (tty[0]) free(tty); continue; bad_entry: message(L_LOG | L_CONSOLE, "Bad inittab entry at line %d", parser->lineno); } config_close(parser); #endif }
First read the file / etc/inittab. If it does not exist, many new_init_action() are executed. If it does exist, a while() loop is taken. It is assumed that the contents of the / etc/inittab file should be parsed and new_init_action () should be executed according to the contents of the file. Okay, so what format does the inittab file actually write, and what is it? while() loop has a static const char actions [] array that looks like it has something to do with the contents of inittab, with strings like "sysinit", but it's still not clear how to write the inittab file.
How to write inittab file
/ busybox/examples/Here's an example of an inittab script. Open it and see a sentence similar to the format description:
Format for each entry: <id>:<runlevels>:<action>:<process>
Guess there are multiple entries in the inittab file. Each entry format has four entries: id, runlevels, action, and process. Action also appears here, which is similar to the action array in the code. The document also says that ID and runlevels are not important. Okay, to figure out how to write inittab, the key is to understand action and process, and continue to read the instructions.
action
action includes: sysinit, respawn, askfirst, wait, once, restart, ctrlaltdel, and shutdown.
process
Specify the program to run and its parameters
Then I said that if there is no inittab file, run the following
::sysinit:/etc/init.d/rcS ::askfirst:/bin/sh ::ctrlaltdel:/sbin/reboot ::shutdown:/sbin/swapoff -a ::shutdown:/bin/umount -a -r ::restart:/sbin/init tty2::askfirst:/bin/sh tty3::askfirst:/bin/sh tty4::askfirst:/bin/sh
This should be the sequence of net_init_action executed if the inittab file is not read in the code
Looking further down, the first example entry appears
::sysinit:/etc/init.d/rcS
Is it familiar that the file / etc/init.d/rcS is usually used in embedded devices of linux system. It is a shell script. According to the previous format, analysis shows that id and runlevel are empty, action is sysinit, process is / etc/init.d/rcS, so the first thing to do is to execute rcS script, and rcS script can do anything you want to do?
The next example is
::askfirst:-/bin/sh
The comment says to start the shell to console port, no matter, keep looking.
tty4::respawn:/sbin/getty 38400 tty5 tty5::respawn:/sbin/getty 38400 tty6
Open getty
::restart:/sbin/init
Specify the restart location of the init process
::ctrlaltdel:/sbin/reboot ::shutdown:/bin/umount -a -r ::shutdown:/sbin/swapoff -a
What to do before restarting
Back in the code, this while() loop traverses each entry of the inittab file, parsing out the four parts of entry: id, runlevel, action and process, and putting them into a pointer array char *token[4], then token[2] and token[3] represent action and process. The program calls the index_in_strings() function to convert token[2] into a string, that is, the equivalent of "sysinit", and then calls net_iniken [4]. T_action(), analysis of net_init_action() source code shows that these actions and processes are actually added to a list, and no actual processing is done. In the subsequent code, the parse_inittab() function returns.
...... /* Now run everything that needs to be run */ /* First run the sysinit command */ run_actions(SYSINIT); 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); /* Now run the looping stuff for the rest of forever. */ while (1) { ......
Here run_action() is called to run each entry in the list, and the first action to run is the action for sysinit.
summary
Here, we have a rough idea of how the init process started the app. The flow chart above
Simply put, the init process first analyzes the / etc/inittab file, of course, it can modify the busybox source code itself to start analysis from any file, if there is no inittab file, it performs the default action; if the inittab file exists, it executes according to the entry in the inittab file, usually to execute script commands in the / etc/init.d/rcS file, of course, to modify the source code, You can also let it execute other scripts
rcS scripts are written in shell Scripting language. The general way is
- Load Driver Module
- Configuring network, building bridge, distribution card address
- Start app