busybox-background

了解process daemond定義
了解busybox的background帶起的方式


更新記錄

item note
20170513 第一版

目錄


總結

  • tty關閉, 相同sid的prcoess產生終結
    由SIGHUP來通知
    不會通知backgroud

  • session id
    The session ID of a process is the process group ID of the session leader.
    若是開一個tty登入,再帶程式,此時帶起的程式皆為相同的sid

  • busybox &(即backgroud)
    同 daemon:使用fork,close stdin/stdout/stderr
    不同 daemon:帶起之後的ppid還是此tty,當tty關閉後會被init接受,不會處理相關SIG

  • daemon
    fork : 產生新的process
    setsid: 產生新的session id(分離目前的tty,防止SIGHUP訊息)
    catch signal: 自行處理SIGCHILD/SIGPIPE等訊息
    for again: 讓process直接由init接管(此時ppid為1)
    chdir: 切換預期工作目錄
    umask: 設定檔案權限
    close: 關關stdin/stdout/stderr,改由檔案來log


Daemon

Daemon程序

來源:Linux Daemon Writing HOWTO,創建 Daemon 程式

  • daemon starts up
    • Fork off the parent process : fork出新的processs
    • Change file mode mask (umask)
    • Open any logs for writing
    • Create a unique Session ID (SID) : setsid()
    • Change the current working directory to a safe place
    • Close standard file descriptors : close std in, std out and std err
    • Enter actual daemon code

來源:Linux中创建守护程序

  • busybox的daemon (即&)
  • 與一般daemon程序比較來少了
    • fork agin (?)
      在tty關閉之後會被init接收
    • catch signal
item daemon busybox & telnetd
fork V V V
setsid V V V
Catch signals V X V
fork again V X? X?
chdir V X X
umask V X X
close V V V
  • fork off the parent process & let it terminate if forking was successful. -> Because the parent process has terminated, the child process now runs in the background.
  • setsid - Create a new session. The calling process becomes the leader of the new session and the process group leader of the new process group. The process is now detached from its controlling terminal (CTTY).
  • Catch signals - Ignore and/or handle signals.
  • fork again let the parent process terminate to ensure that you get rid of the session leading process. (Only session leaders may get a TTY again.)
    fork兩次,這樣讓子進程直接退出,孫進程結束後就成了孤兒進程,會被init接管,直接回收,不用祖父進程管了
  • chdir Change the working directory of the daemon
  • umask - Change the file mode mask according to the needs of the daemon.
  • close - Close all open file descriptors that may be inherited from the parent process.

telnetd : 處理SIGPIPE/SIGCHLD信號

github daemon


daemon example

關閉tty將會產生SIGHUP訊息,關閉相同sid的process

  • 使用telnet登入,板端

    • login[1399]: root login on ‘pts/0’
    • 1399即telnet
    • 1403為a.out的process
      1
      2
      3
      4
      5
      6
       1399 root      3180 S    -sh
      1403 root 1732 S ./a.out
      1404 root 3180 R ps
      # cat /tmp/showppid.1403.txt
      cnt:3, getppid = 1399
      #
  • 關閉telnet, 因此a.out收到SIGHUP訊息關閉

    • pid 1399 (即telnet),1403(即a.out)皆被終結
      1
      2
      3
      4
       1221 root      3508 S    telnetd
      1346 root 3180 S -sh
      1407 root 3180 R ps
      #
  • showppid.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    /* showppid.c */
    #include <unistd.h>
    #include <stdio.h>

    int main(void)
    {
    int cnt=0;
    FILE *fp;
    char path[80];

    //sleep(30);
    snprintf(path, sizeof(path) - 1, "/tmp/showppid.%d.txt", getpid());
    fp = fopen(path, "w+");
    if(fp == 0){
    return 0;
    }

    fprintf(fp, "getppid = %d\n", getppid());
    fclose(fp);
    while(1){
    printf("showip\n");
    fp = fopen(path, "w+");
    if(fp != 0){
    fprintf(fp, "cnt:%d, getppid = %d\n",cnt++,getppid());
    fclose(fp);
    }
    sleep(3);
    }
    return 0;
    }

使用nohup不會受到SIGHUP影響

  • nohup ./a.out

    1
    2
    3
    4
    5
    6
     1346 root      3180 S    -sh
    1408 root 3180 S -sh
    1410 root 1732 S ./a.out
    1411 root 3180 R ps
    # cat /tmp/showppid.1410.txt
    cnt:4, getppid = 1408
  • pid 1410(即a.out)還在存,pid 1408(telnet)關閉

    1
    2
    3
    4
    5
    6
     1346 root      3180 S    -sh
    1410 root 1732 S ./a.out
    1413 root 3180 R ps
    # cat /tmp/showppid.1410.txt
    cnt:22, getppid = 1
    #

使用background方式(即busybox &)

  • ./a.out&

    1
    2
    3
    4
    5
    6
     1419 root      3180 S    -sh
    1420 root 1732 S ./a.out
    1421 root 3180 R ps
    # cat /tmp/showppid.1420.txt
    cnt:2, getppid = 1419
    #
  • 關閉telnet(即pid 1419),此時a.out還在存(即pid 1420),但由init接收

    1
    2
    3
    4
    5
    6
     1346 root      3180 S    -sh
    1420 root 1732 S ./a.out
    1423 root 3180 R ps
    # cat /tmp/showppid.1420.txt
    cnt:12, getppid = 1
    #

session id

  • getsid - get session ID
    The session ID of a process is the process group ID of the session leader.

  • session即為tty的pid

  • 使用&帶起
    sid應該要不同,因為setsid()?

    1
    2
    3
    4
    5
    6
    7
    a2.out&

    1443 root 3180 S -sh
    1445 root 1732 S ./a2.out
    1446 root 3180 R ps
    # cat /tmp/showppid.1445.txt
    cnt:3, getppid = 1443, getsid(0):1443
  • 使用nohup帶起,之後關閉tty

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    nohup a2.out

    1346 root 3180 S -sh
    1465 root 3180 S -sh
    1469 root 1732 S ./a2.out
    1470 root 3180 R ps
    # cat /tmp/showppid.1469.txt
    cnt:3, getppid = 1465, getsid(0):1465
    #

    1346 root 3180 S -sh
    1469 root 1732 S ./a2.out
    1473 root 3180 R ps
    # cat /tmp/showppid.1469.txt
    cnt:12, getppid = 1, getsid(0):1465
    #

busybox

來源:daemon 與 background process 的差異

background

  • 在ash環境下使用a.out& (即帶&:表示background)
  • 注意,未設定: 忽略SIGHUP信號
    此訊息為關閉console/tty時會,發給所有相同session id的prcoess

  • busybox下使用&, 處理程序如下: (ex. ./a.out&)

  • bb_daemonize(DAEMON_DEVNULL_STDIO + DAEMON_CLOSE_EXTRA_FDS);
1
2
3
4
5
6
7
8
9
debianutils/start_stop_daemon.c

400 *--argv = startas;
401 if (opt & OPT_BACKGROUND) {
403 bb_daemonize(DAEMON_DEVNULL_STDIO + DAEMON_CLOSE_EXTRA_FDS);
404 /* DAEMON_DEVNULL_STDIO is superfluous -
405 * it's always done by bb_daemonize() */
xxx
425 }

telnetd

  • telnetd處理SIGPIPE/SIGCHLD信號
  • busybox telnetd如下
    • bb_daemonize_or_rexec()
    • signal(SIGPIPE, SIG_IGN);
    • signal(SIGCHLD, SIG_IGN);
1
2
3
4
5
6
7
8
9
10
11
12
 busybox-1.16.1/networking/telnetd.c

470 if (!(opt & OPT_FOREGROUND)) {
471 /* DAEMON_CHDIR_ROOT was giving inconsistent
472 * behavior with/without -F, -i */
473 bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv);
474 }
...
503 /* We don't want to die if just one session is broken */
504 signal(SIGPIPE, SIG_IGN);
...
509 signal(SIGCHLD, SIG_IGN);

busybox daemon

  • bb_daemonize(DAEMON_DEVNULL_STDIO + DAEMON_CLOSE_EXTRA_FDS);
  • FAST_FUNC bb_daemonize_or_rexec(int flags, char **argv)
    • DAEMON_DEVNULL_STDIO: 關閉標準輸入/輸出/錯誤判面,將這些導到/dev/null
    • fork_or_rexec(): fork出1個thread
    • setsid
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
include/libbb.h
847 enum {
848 DAEMON_CHDIR_ROOT = 1,
849 DAEMON_DEVNULL_STDIO = 2,
850 DAEMON_CLOSE_EXTRA_FDS = 4,
851 DAEMON_ONLY_SANITIZE = 8, /* internal use */
852 };

856 # define fork_or_rexec(argv) fork_or_rexec()
857 # define bb_daemonize_or_rexec(flags, argv) bb_daemonize_or_rexec(flags)
858 # define bb_daemonize(flags) bb_daemonize_or_rexec(flags, bogus)

libbb/vfork_daemon_rexec.c

283 /* Due to a #define in libbb.h on MMU systems we actually have 1 argument -
284 * char **argv "vanishes" */
285 void FAST_FUNC bb_daemonize_or_rexec(int flags, char **argv)
286 {
287 int fd;
288
289 if (flags & DAEMON_CHDIR_ROOT)
290 xchdir("/");
291
292 if (flags & DAEMON_DEVNULL_STDIO) {
293 close(0);
294 close(1);
295 close(2);
296 }
297
298 fd = open(bb_dev_null, O_RDWR);
299 if (fd < 0) {
300 /* NB: we can be called as bb_sanitize_stdio() from init
301 * or mdev, and there /dev/null may legitimately not (yet) exist!
302 * Do not use xopen above, but obtain _ANY_ open descriptor,
303 * even bogus one as below. */
304 fd = xopen("/", O_RDONLY); /* don't believe this can fail */
305 }
306
307 while ((unsigned)fd < 2)
308 fd = dup(fd); /* have 0,1,2 open at least to /dev/null */
309
310 if (!(flags & DAEMON_ONLY_SANITIZE)) {
311 if (fork_or_rexec(argv))
312 exit(EXIT_SUCCESS); /* parent */
313 /* if daemonizing, make sure we detach from stdio & ctty */
314 setsid();
315 dup2(fd, 0);
316 dup2(fd, 1);
317 dup2(fd, 2);
318 }
319 while (fd > 2) {
320 close(fd--);
321 if (!(flags & DAEMON_CLOSE_EXTRA_FDS))
322 return;
323 /* else close everything after fd#2 */
324 }
325 }

272 pid_t FAST_FUNC fork_or_rexec(void)
273 {
274 pid_t pid;
275 pid = fork();
276 if (pid < 0) /* wtf? */
277 bb_perror_msg_and_die("fork");
278 return pid;
279 }

nohup

nohup - run a command immune to hangups, with output to a non-tty

  • 關閉stdin
  • 將stdout/stderr,導到/root/nohup.out
  • signal(SIGHUP, SIG_IGN),不理SIGHUP訊息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
coreutils/nohup.c

int nohup_main
46 /* If stdin is a tty, detach from it. */
47 if (isatty(STDIN_FILENO)) {
48 /* bb_error_msg("ignoring input"); */
49 close(STDIN_FILENO);
50 xopen(bb_dev_null, O_RDONLY); /* will be fd 0 (STDIN_FILENO) */
51 }
...
53 nohupout = "nohup.out";
54 /* Redirect stdout to nohup.out, either in "." or in "$HOME". */
55 if (isatty(STDOUT_FILENO)) {
56 close(STDOUT_FILENO);
57 if (open(nohupout, O_CREAT|O_WRONLY|O_APPEND, S_IRUSR|S_IWUSR) < 0) {
...
69 /* If we have a tty on stderr, redirect to stdout. */
70 if (isatty(STDERR_FILENO)) {
71 /* if (stdout_wasnt_a_tty)
72 bb_error_msg("redirecting stderr to stdout"); */
73 dup2(STDOUT_FILENO, STDERR_FILENO);
74 }
...
76 signal(SIGHUP, SIG_IGN);

參考

當程序經由系統呼叫 fork() 產生另一個 程序時,這兩個程序間就存在了父子關係

  • 當子程序結束時,系統會將子程序的結束狀態保存起來,接著發出訊號 SIGCHLD 通 知父程序來取子程序的結束狀態碼
  • 如果父程序比子程序早結束的話,則子程序將成為孤兒,那麼系統會將此孤兒交給 程序 init (pid = 0)
  • 父程序根本忘了要處理的話,那麼就會形成一個 zombie 程序,已經完全沒有活 動,所使用的資源也都被系統回收了,但是仍然佔了處理程序表的一筆記錄
  • 因為系統對於 SIGCHLD 訊號的處理方式,預設是不理會,如果父程序忘了設定捕捉 SIGCHLD 訊號

在一般的daemon中會看到fork二次,來產生孤兒,因此自然由init接管

  • 利用兩次 fork() ,形成祖孫關係,由於系統只承認程序間的父子關係,並不承認 祖孫關係,這樣可讓新生的程序在結束時,自然地形成孤兒,由系統交給 init 程 序執行