目錄表

Process Control

0x00 Outline


0x01 OverView

Process Identifiers

Process Relationships

Retrieve Process Identifiers

pid_t getpid(void);
pid_t getppid(void);
uid_t getuid(void);
uid_t geteuid(void);
gid_t getgid(void);
gid_t getegid(void);
/* None of these functions has an error return */

0x02 Process Creation

The fork function

#include <unistd.h>
 
pid_t fork(void);
/* Returns: 0 in child, process ID of child in parent, -1 on error */
#include "apue.h"
 
int globvar = 6; /* external variable in initialized data */
char buf[] = "a write to stdout\n";
 
int
main(void)
{
    int var; /* automatic variable on the stack */
    pid_t pid;
 
    var = 88;
    if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
        err_sys("write error");
    printf("before fork\n"); /* we don’t flush stdout */
 
    if ((pid = fork()) < 0)
    {
        err_sys("fork error");
    }
    else if (pid == 0) /* child */
    {
        globvar++; /* modify variables */
        var++;
    }
    else /* parent */
    {
        sleep(2);
    }
 
    printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar, var);
    exit(0);
}
$ ./a.out
a write to stdout
before fork
pid = 430, glob = 7, var = 89 # child’s variables were changed
pid = 429, glob = 6, var = 88 # parent’s copy was not changed
$ ./a.out > temp.out
$ cat temp.out
a write to stdout
before fork
pid = 432, glob = 7, var = 89
before fork
pid = 431, glob = 6, var = 88

Fork and file Sharing

Handling File Descriptors after fork

Other Properties Inherited by the Child

Use of fork

Variants of fork

function 特性
fork parent 和 child 執行時互相獨立,不保證先後,變數分離,溝通需要透過 pipe 等專門機制,Linux 的 Copy on Write 技術可以減少負擔
vfork parent 和 child 共用變數,在 child 呼叫 exit 或 exec之前,parent 會被 block 住
clone 可以決定哪些東西要在 parent 和 child 之間共享

0x03 Process Termination

Child process terminate

wait and waitpid function

#include <sys/types.h>
#include <sys/wait.h>
 
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
int main(void)
{
    if ((pid = fork()) < 0)
    {
        err_sys("fork error");
    }
    else if (pid == 0) /* child */
    {
        exit(7);
    }
 
    if (wait(&status) != pid)
    {
        err_sys("wait error");
    }
    pr_exit(status);
 
    if ((pid = fork()) < 0)
    {
        err_sys("fork error");
    }
    else if (pid == 0) /* child */
    {
        abort(); /* generates SIGABRT */
    }
 
    if (wait(&status) != pid)
    {
        err_sys("wait error");
    }
    pr_exit(status);
 
    if ((pid = fork()) < 0)
    {
        err_sys("fork error");
    }
    else if (pid == 0) /* child */
    {
        status /= 0;
    }
 
    if (wait(&status) != pid)
    {
        err_sys("wait error");
    }
    pr_exit(status);
    exit(0);
}
 
void pr_exit(int status)
{
    if (WIFEXITED(status))
        printf("normal termination, exit status = %d\n", WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
        printf("abnormal termination, signal number=%d%s\n", WTERMSIG(status), WCOREDUMP(status) ? " (core file generated)" : "");
    else if (WIFSTOPPED(status))
        printf("child stopped, signal number=%d\n", WSTOPSIG(status));
}
normal termination, exit status = 7
abnormal termination, signal number = 6
abnormal termination, signal number = 8

Macros to Interpret Exit Status

Avoid Zombies by Calling fork Twice

fork 時 parent process 可能也有自己的工作,不想呼叫 wait 來等待 child process 而被 block,所以我們可以透過兩次 fork,並立刻結束第一個 child,如此 parent 不需要過多等待,而第二個 child process 會在第一個 child process 結束時被 init 接手,從此與 parent 獨立

#include "apue.h"
#include <sys/wait.h>
 
int
main(void)
{
    pid_t   pid;
 
    if ((pid = fork()) < 0) {
        err_sys("fork error");
    } else if (pid == 0) {      /* first child */
        if ((pid = fork()) < 0)
            err_sys("fork error");
        else if (pid > 0)
            exit(0);    /* parent from second fork == first child */
 
        /*
         * We're the second child; our parent becomes init as soon
         * as our real parent calls exit() in the statement above.
         * Here's where we'd continue executing, knowing that when
         * we're done, init will reap our status.
         */
        sleep(2);
        printf("second child, parent pid = %ld\n", (long)getppid());
        exit(0);
    }
 
    if (waitpid(pid, NULL, 0) != pid)   /* wait for first child */
        err_sys("waitpid error");
 
    /*
     * We're the parent (the original process); we continue executing,
     * knowing that we're not the parent of the second child.
     */
    exit(0);
}
$ ./a.out
$ second child, parent pid = 1

這邊可以注意到 “second child, parent pid = 1” 前面會先出現提示符,因為在 parent process 結束時提示符就會再出現,而後第二個 child 結束才輸出字串

Race Conditions

在呼叫 fork 後不保證 parent 和 child 誰會先執行

int main(void)
{
    pid_t pid;
    if ((pid = fork()) < 0)
    {
        err_sys("fork error");
    }
    else if (pid == 0)
    {
        charatatime("output from child\n");
    }
    else
    {
        charatatime("output from parent\n");
    }
    exit(0);
}

解法一:

while (getppid() != 1)
    sleep(1);

解法二:

#include "apue.h"
 
TELL_WAIT(); /* set things up for TELL_xxx & WAIT_xxx */
 
if ((pid = fork()) < 0) {
    err_sys("fork error");
} else if (pid == 0) { /* child */
 
    /* child does whatever is necessary ... */
 
    TELL_PARENT(getppid()); /* tell parent we’re done */
    WAIT_PARENT(); /* and wait for parent */
 
    /* and the child continues on its way ... */
 
    exit(0);
}
 
/* parent does whatever is necessary ... */
 
TELL_CHILD(pid); /* tell child we’re done */
WAIT_CHILD(); /* and wait for child */
 
/* and the parent continues on its way ... */
exit(0);
parent_go_first
} else if (pid == 0) {
    WAIT_PARENT();      /* parent goes first */
    charatatime("output from child\n");
} else {
    charatatime("output from parent\n");
    TELL_CHILD(pid);
}
child_go_first
} else if (pid == 0) {
    charatatime("output from child\n");
    TELL_PARENT(getppid());
} else {
    WAIT_CHILD(); /* child goes first */
    charatatime("output from parent\n");
}

0x04 Process Execution

exec Function

#include <unistd.h>
 
extern char **environ;
int execl(const char *pathname, const char *arg0, ...,(char *)0);
int execlp(const char *filename, const char *arg0, ...,(char *)0);
int execle(const char *pathname, const char *arg0, ...,(char *)0 , char * constenvp[]);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *filename, char *const argv[]);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int fexecve(int fd, char *const argv[], char *const envp[]);
 
/* All seven return: −1 on error, no return on success */

#include "apue.h"
#include <sys/wait.h>
 
char    *env_init[] = { "USER=unknown", "PATH=/tmp", NULL };
 
int
main(void)
{
    pid_t   pid;
 
    if ((pid = fork()) < 0)
    {
        err_sys("fork error");
    }
    else if (pid == 0)   /* specify pathname, specify environment */
    {
        if (execle("/home/sar/bin/echoall", "echoall", "myarg1", "MY ARG2", (char *)0, env_init) < 0)
            err_sys("execle error");
    }
 
    if (waitpid(pid, NULL, 0) < 0)
        err_sys("wait error");
 
    if ((pid = fork()) < 0)
    {
        err_sys("fork error");
    }
    else if (pid == 0)   /* specify filename, inherit environment */
    {
        if (execlp("echoall", "echoall", "only 1 arg", (char *)0) < 0)
            err_sys("execlp error");
    }
 
    exit(0);
}
echoall.c
#include "apue.h"
 
int
main(int argc, char *argv[])
{
    int         i;
    char        **ptr;
    extern char **environ;
 
    for (i = 0; i < argc; i++)      /* echo all command-line args */
        printf("argv[%d]: %s\n", i, argv[i]);
 
    for (ptr = environ; *ptr != 0; ptr++)   /* and all env strings */
        printf("%s\n", *ptr);
 
    exit(0);
}
$ ./a.out
argv[0]: echoall
argv[1]: myarg1
argv[2]: MY ARG2
USER=unknown
PATH=/tmp
$ argv[0]: echoall
argv[1]: only 1 arg
USER=sar
LOGNAME=sar
SHELL=/bin/bash
...
HOME=/home/sar

Interpreter Files

幾乎現今的 UNIX 系統都支援 Interpreter Files,而 exec 也可執行 Interpreter Files

 #! pathname [ optional-argument ]
#include "apue.h"
#include <sys/wait.h>
 
int
main(void)
{
    pid_t pid;
 
    if ((pid = fork()) < 0)
    {
        err_sys("fork error");
    }
    else if (pid == 0)  /* child */
    {
        if (execl("/home/sar/bin/testinterp", "testinterp", "myarg1", "MY ARG2", (char *)0) < 0)
            err_sys("execl error");
    }
    if (waitpid(pid, NULL, 0) < 0) /* parent */
        err_sys("waitpid error");
    exit(0);
}
$ cat /home/sar/bin/testinterp
#!/home/sar/bin/echoarg foo
$ ./a.out
argv[0]: /home/sar/bin/echoarg
argv[1]: foo
argv[2]: /home/sar/bin/testinterp
argv[3]: myarg1
argv[4]: MY ARG2

system Function

#include <stdlib.h>
 
int system(const char *cmdstring);

system function 的簡易實作,不含 signal handle

#include    <sys/wait.h>
#include    <errno.h>
#include    <unistd.h>
 
int
system(const char *cmdstring)   /* version without signal handling */
{
    pid_t   pid;
    int     status;
 
    if (cmdstring == NULL)
        return(1);      /* always a command processor with UNIX */
 
    if ((pid = fork()) < 0)
    {
        status = -1;    /* probably out of processes */
    }
    else if (pid == 0)  /* child */
    {
        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        _exit(127);     /* execl error */
    }
    else               /* parent */
    {
        while (waitpid(pid, &status, 0) < 0)
        {
            if (errno != EINTR)
            {
                status = -1; /* error other than EINTR from waitpid() */
                break;
            }
        }
    }
 
    return(status);
}

system and suid/sgid Programs

如果一個 process 有 suid/sgid 則又在這個 process 中呼叫了 system,則 system 執行的這個 command 擁有和呼叫他的 process 一樣的權限,所以一般不建議在 suid/sgid 的程式直接使用 system

建議使用 fork 產生新 process 後再以 seteuid and setegid 重新調整權限,確認權限無誤後才呼叫 exec 執行指令

User Identification

#include <sys/types.h>
#include <pwd.h>
 
struct passwd *getpwnam(const char *name);
 
struct passwd *getpwuid(uid_t uid);
 
int getpwnam_r(const char *name, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result);
 
int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result);
 
struct passwd {
    char   *pw_name;       /* username */
    char   *pw_passwd;     /* user password */
    uid_t   pw_uid;        /* user ID */
    gid_t   pw_gid;        /* group ID */
    char   *pw_gecos;      /* user information */
    char   *pw_dir;        /* home directory */
    char   *pw_shell;      /* shell program */
};
#include <unistd.h>
 
char *getlogin(void);
int getlogin_r(char *buf, size_t bufsize);

0x05 參考資料