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 */
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
write
function 是 not buffered,且是在 fork 前被呼叫,所以在呼叫時字串就會被寫到 standard outputFork 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 之間共享 |
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);
} else if (pid == 0) { WAIT_PARENT(); /* parent goes first */ charatatime("output from child\n"); } else { charatatime("output from parent\n"); TELL_CHILD(pid); }
} else if (pid == 0) { charatatime("output from child\n"); TELL_PARENT(getppid()); } else { WAIT_CHILD(); /* child goes first */ charatatime("output from parent\n"); }
exec Function
fork
creates new processesexec
functions initiates new programsexit
handles terminationwait
functions handle waiting for termination#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); }
#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);