目录
进程标识
每个进程都有一个非负整型表示的唯一进程ID。因为进程ID标识符总是唯一的,常将其用作其他标识符的一部分以保证其唯一性。例如,应用程序有时就把进程ID作为名字的一部分来创建 一个唯一的文件名。
虽然是唯一的,但是进程ID是可复用的。当一个进程终止后,其进程ID就成为复用的候选者。大多数UNIX系统实现延迟复用算法,使得赋予新建进程的ID不同于最近终止进程所使用的ID。这防止了将新进程误认为是使用同一ID的某个已终止的先前进程。
函数fork()
一个现有的进程可以调用fork()函数创建一个新进程。
#include <unistd.h>
pid_t fork(void);
返回值: 子进程返回0,父进程返回子进程ID;若出错,返回-1.
由fork()创建的新进程被称为子进程(child process)。fork函数被调用一次,但返回2次。
两次返回的区别时:子进程的返回值是0,而父进程的返回值则是新建子进程的进程ID。
将子进程ID返回给父进程的理由是:因为一个进程的子进程可以有多个,并且没有一个函数使一个进程可以获得其所有子进程的进程ID。
fork()使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getppid以获得其父进程的进程ID
(进程ID 0总是由内核交换进程使用,所以一个子进程的进程ID不可能是0)
子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如,子进程获得父进程数据空间、堆和栈的副本。注意,这是子进程所拥有的副本。父进程和子进程并不共享这些存储空间部分。 父进程和子进程共享正文段。
/* fork.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int globvar = 6;
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)
{
perror("write error");
exit(EXIT_FAILURE);
}
printf("before fork\n"); //we don't flush stdout
if ((pid = fork()) < 0)
{
perror("fork error");
exit(EXIT_FAILURE);
}
else if (pid == 0) //child
{
globvar++; //modify variables
var++;
}
else //parent
{
sleep(2);
}
printf("pid = %ld, globvar = %d, var = %d\n", (long)getpid(), globvar, var);
exit(EXIT_SUCCESS);
}
mali@mali:~/code/process$ gcc fork.c -o fork
mali@mali:~/code/process$ ./fork
a write to stdout
before fork
pid = 5131, globvar = 7, var = 89
pid = 5130, globvar = 6, var = 88
mali@mali:~/code/process$ cat temp.out
a write to stdout
before fork
pid = 5598, globvar = 7, var = 89
before fork
pid = 5597, globvar = 6, var = 88
mali@mali:~/code/process$
一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的,这取决于内核所使用的调度算法。如果要求父进程和子进程之间相互同步,则要求某种形式的进程间通信。
在fork.c中,父进程使自己休眠2s,以此使子进程先执行。但并不保证2s已经足够。
当写标准输出时,将buf长度减去1作为输出字节数,这是为了避免将终止null字节写出。strlen计算不包含终止null字节的字符串长度,而sizeof则计算包括终止null字节的缓冲区长度。两者之间的另一个差别是,使用strlen需进行一次函数调用,而对于sizeof而言,因为缓冲区已用已知字符串进行初始化,其长度时固定的,所以sizeof是在编译时计算缓冲区长度。
write函数是不带缓冲的。因为在fork之前调用write,所以其数据写到标准输出一次。但是标准I/O库是带缓冲的。如果标准输出连到终端设备,则它是行缓冲的;否则它是全缓冲的。当以交互方式运行该程序时(行缓冲),只得到该printf输出的行一次,其原因是标准输出缓冲区由换行符冲洗。但是当将标准输出重定向到一个文件时(全缓冲),却得到printf输出行两次。其原因是:在fork之前调用了printf一次,但当调用fork时,该行数据仍在缓冲区中,然后在将父进程数据空间复制到子进程中时,该缓冲区数据也被复制到子进程中,此时父进程和子进程各自有了带该行内容的缓冲区。在exit之前的第二个printf将其数据追加到已有的缓冲区中。当每个进程终止时,其缓冲区中的内容都被写到相应文件中。
僵尸(Zombie)进程
产生僵尸进程的原因:
- 传递参数并调用exit函数
- main函数中执行return语句并返回值
向exit函数传递的参数值和main函数的return语句返回的值都会传递给操作系统。而操作系统不会销毁子进程,直到把这些值传递给产生该子进程的父进程。处在这种状态下的进程就是僵尸进程。也就是说,把子进程变成僵尸进程的正是操作系统。
僵尸进程何时被销毁?
“应该向创建子进程的父进程传递子进程的exit参数值或return语句的返回值”。
如何向父进程传递这些值呢?操作系统不会主动把这些值传递给父进程。只有父进程主动发起请求(函数调用)时,操作系统才会传递该值。换言之,如果父进程未主动要求获得子进程的结束状态值,操作系统将一直保存,并让子进程长时间处于僵尸进程状态。
/* zombie.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
pid_t pid = fork();
if (pid < 0)
{
perror("fork error");
exit(1);
}
else if (pid == 0)
{
puts("Hi, I am a child process");
}
else
{
printf("Child Process ID: %d\n", pid);
sleep(30);
}
if (pid == 0)
puts("End child process");
else
puts("End parent process");
return 0;
}
mali@mali:~/code/process$ ./zombie
Child Process ID: 8951
Hi, I am a child process
End child process
程序开始运行后,将在如上所示状态暂停。跳出这种状态前,应验证子进程是否为僵尸进程。该验证在其他控制台窗口进行。
mali@mali:~/code/process$ ps -al
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 8950 3798 0 80 0 - 1087 hrtime pts/4 00:00:00 zombie
1 Z 1000 8951 8950 0 80 0 - 0 - pts/4 00:00:00 zombie <defunct>
0 R 1000 8953 8926 0 80 0 - 7662 - pts/19 00:00:00 ps
可以看到,pid为8951的进程为僵尸进程(Z)。另外,经过30s的等待时间后,PID为8950的父进程和之前的僵尸子进程同时销毁。
mali@mali:~/code/process$ ps -al
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 R 1000 8980 8926 0 80 0 - 7655 - pts/19 00:00:00 ps
mali@mali:~/code/process$ ./zombie
Child Process ID: 8951
Hi, I am a child process
End child process
End parent process
mali@mali:~/code/process$
后台处理(background processing)
后台处理是指将控制台窗口的指令放在后台运行的方式。如果以如下方式运行上述示例,则程序将在后台运行(&将触发后台处理):
mali@mali:~/code/process$ ./zombie &
[1] 8995
mali@mali:~/code/process$ Child Process ID: 8996
Hi, I am a child process
End child process
ps -al
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 8995 3798 0 80 0 - 1087 hrtime pts/4 00:00:00 zombie
1 Z 1000 8996 8995 0 80 0 - 0 - pts/4 00:00:00 zombie <defunct>
0 R 1000 8997 3798 0 80 0 - 7662 - pts/4 00:00:00 ps
mali@mali:~/code/process$ ps -al
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 8995 3798 0 80 0 - 1087 hrtime pts/4 00:00:00 zombie
1 Z 1000 8996 8995 0 80 0 - 0 - pts/4 00:00:00 zombie <defunct>
0 R 1000 8999 3798 0 80 0 - 7662 - pts/4 00:00:00 ps
mali@mali:~/code/process$ End parent process
ps -al
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 R 1000 9000 3798 0 80 0 - 7655 - pts/4 00:00:00 ps
[1]+ Done ./zombie
mali@mali:~/code/process$
销毁僵尸进程:函数wait和waitpid
当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。因为子进程终止是个异步事件(这可以在父进程运行的任何时候发生),所以这种信号也是内核向父进程发的异步通知。
父进程可以选择忽略该信号,或者提供一个该信号发生时即被调用执行的函数(信号处理程序)。对于这种信号的系统默认动作时忽略它。
为了销毁子进程,父进程应主动请求获取子进程的返回值。
调用wait或waitpid的进程会发生的情况:
- 如果其所有子进程都还在运行,则阻塞;
- 如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
- 如果它没有任何子进程,则立即出错返回。
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
两个函数返回值:若成功,返回终止的子进程ID;若出错,返回-1
这两个函数的区别如下:
- 在一个子进程终止前,wait使其调用者阻塞,而waitpid有一选项,可使调用者不阻塞
- waitpid并不等待在其调用之后的第一个终止子进程,它有若干个选项,可以控制它所等待的进程
/* Bits in the third argument to `waitpid'. */
#define WNOHANG 1 /* Don't block waiting. */
#define WUNTRACED 2 /* Report status of stopped children. */
WNOHANG:即使没有终止的子进程也不会进入阻塞状态,而是返回0并退出函数。
如果子进程已经终止,并且是一个僵尸进程,则wait立即返回并取得该子进程的状态;否则wait使其调用者阻塞,直到一个子进程终止。
如调用者阻塞而且它有多个子进程,则在其某一子进程终止时,wait就立即返回。因为wait返回终止子进程的进程ID,所以它总是了解是哪一个子进程终止了。
这两个函数的参数statloc是一个整型指针。如果statloc不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内。如果不关心终止状态,则可将该参数指定为空指针。
宏 | 说明 |
WIFEXITED(status) | 若为正常终止子进程返回的状态,则为真 |
WIFSIGNALED(status) | 若为异常终止子进程返回的状态,则为真(接到一个不捕捉的信号) |
WIFSTOPPED(status) | 若为当前暂停子进程返回的状态,则为真 |
WIFCONTINUED(status) | 若在作业控制暂停后已经继续的子进程返回了状态,则为真 |
/* wait.c */
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
void print_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),
#ifdef WCOREDUMP
WCOREDUMP(status) ? "(core file generated)" : "");
#else
"");
#endif
}
else if (WIFSTOPPED(status))
{
printf("child stopped, signal number = %d\n", WSTOPSIG(status));
}
}
int main(void)
{
pid_t pid;
int status;
if ((pid = fork()) < 0)
{
perror("fork error");
exit(EXIT_FAILURE);
}
else if (pid == 0)
{
exit(7);
}
if (wait(&status) != pid)
{
perror("wait error");
exit(EXIT_FAILURE);
}
print_exit(status);
if ((pid = fork()) < 0)
{
perror("fork error");
exit(EXIT_FAILURE);
}
else if (pid == 0)
{
abort();//generates SIGABRT
}
if (wait(&status) != pid)
{
perror("wait error");
exit(EXIT_FAILURE);
}
print_exit(status);
if ((pid = fork()) < 0)
{
perror("fork error");
exit(EXIT_FAILURE);
}
else if (pid == 0)
{
status /= 0;//divide by 0 generates SIGFPE
}
if (wait(&status) != pid)
{
perror("wait error");
exit(EXIT_FAILURE);
}
print_exit(status);
exit(0);
}
mali@mali:~/code/process$ gcc wait.c -o wait
wait.c: In function ‘main’:
wait.c:76:16: warning: division by zero [-Wdiv-by-zero]
status /= 0;//divide by 0 generates SIGFPE
^
mali@mali:~/code/process$ ./wait
normal termination, exit status = 7
abnormal termination, signal number=6(core file generated)
abnormal termination, signal number=8(core file generated)
mali@mali:~/code/process$
/* Signals. signum.h */
#define SIGHUP 1 /* Hangup (POSIX). */
#define SIGINT 2 /* Interrupt (ANSI). */
#define SIGQUIT 3 /* Quit (POSIX). */
#define SIGILL 4 /* Illegal instruction (ANSI). */
#define SIGTRAP 5 /* Trace trap (POSIX). */
#define SIGABRT 6 /* Abort (ANSI). */
#define SIGIOT 6 /* IOT trap (4.2 BSD). */
#define SIGBUS 7 /* BUS error (4.2 BSD). */
#define SIGFPE 8 /* Floating-point exception (ANSI). */
#define SIGKILL 9 /* Kill, unblockable (POSIX). */
#define SIGUSR1 10 /* User-defined signal 1 (POSIX). */
#define SIGSEGV 11 /* Segmentation violation (ANSI). */
#define SIGUSR2 12 /* User-defined signal 2 (POSIX). */
#define SIGPIPE 13 /* Broken pipe (POSIX). */
#define SIGALRM 14 /* Alarm clock (POSIX). */
#define SIGTERM 15 /* Termination (ANSI). */
#define SIGSTKFLT 16 /* Stack fault. */
#define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */
#define SIGCHLD 17 /* Child status has changed (POSIX). */
#define SIGCONT 18 /* Continue (POSIX). */
#define SIGSTOP 19 /* Stop, unblockable (POSIX). */
#define SIGTSTP 20 /* Keyboard stop (POSIX). */
#define SIGTTIN 21 /* Background read from tty (POSIX). */
#define SIGTTOU 22 /* Background write to tty (POSIX). */
#define SIGURG 23 /* Urgent condition on socket (4.2 BSD). */
#define SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */
#define SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). */
#define SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */
#define SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */
#define SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */
#define SIGPOLL SIGIO /* Pollable event occurred (System V). */
#define SIGIO 29 /* I/O now possible (4.2 BSD). */
#define SIGPWR 30 /* Power failure restart (System V). */
#define SIGSYS 31 /* Bad system call. */
对于waitpid函数中的pid参数的作用解释如下:
- pid == -1 等待任一子进程终止。此种情况下,waitpid和wait等效
- pid > 0 等待进程ID与pid相等的子进程
- pid == 0 等待组ID等于调用进程组ID的任一子进程
- pid < -1 等待组ID等于pid绝对值得任一子进程
/*waitpid.c --fork两次以避免僵死进程*/
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
if ((pid = fork()) < 0)
{
perror("fork error");
exit(EXIT_FAILURE);
}
else if (pid == 0)
{
if ((pid = fork()) < 0)
{
perror("fork error");
exit(EXIT_FAILURE);
}
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
{
perror("waitpid error");
exit(EXIT_FAILURE);
}
/*
*We're the parent(the original process); we continue executing,
*knowing that we're not the parent of the second child.
*/
exit(0);
}
mali@mali:~/code/process$ gcc waitpid.c -o waitpid
mali@mali:~/code/process$ ./waitpid
mali@mali:~/code/process$ second child, parent pid = 1412
如下示例验证调用waitpid时,程序不会阻塞
/* waitpid1.c */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char* argv[])
{
int status;
pid_t pid = fork();
if (pid < 0)
{
perror("fork error");
exit(1);
}
else if (pid == 0)
{
sleep(15);
exit(24);
}
else
{
while (!waitpid(pid, &status, WNOHANG))
{
sleep(3);
puts("sleep 3sec.");
}
if (WIFEXITED(status))
printf("child send %d\n", WEXITSTATUS(status));
}
return 0;
}
while循环中调用waitpid函数。向第三个参数传递WNOHANG,因此,若之前没有终止的子进程将返回0.
mali@mali:~/code/process$ gcc waitpid1.c -o waitpid1
mali@mali:~/code/process$ ./waitpid1
sleep 3sec.
sleep 3sec.
sleep 3sec.
sleep 3sec.
sleep 3sec.
child send 24
mali@mali:~/code/process$
可以看出“sleep 3sec.”共执行了5次,可证waitpid函数并未阻塞。
信号处理
“子进程究竟何时终止?调用waitpid函数后要无休止地等待吗?“
子进程终止的识别主体是操作系统。
信号和signal函数
进程:“嘿,操作系统!如果我之前创建的子进程终止,就帮我调用zombie_handler函数。”
操作系统:“好的!如果你的子进程终止,我会帮你调用zombie_handler函数,你先把该函数要执行的语句编好!”
上述对话过程中进程所讲的相当于“注册信号”过程,即进程发现自己的子进程结束时,请求操作系统调用特定函数。该请求通过如下函数调用完成(因此称此函数为信号注册函数)。
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
为了在产生信号时调用,返回之前注册的函数指针。
函数名:signal
参数:int signo, void (*func)(int)
返回类型:参数类型为int型,返回类型为void型的函数指针
调用上述函数时,第1个参数为特殊情况信息,第2个参数为特殊情况下将要调用的函数的地址值。
发生第一个参数代表的情况时,调用第二个参数所指的参数。
SIGALRM: 已到通过调用alarm函数注册的时间
SIGINT:输入ctrl+c
SIGCHLD: 子进程终止
接下来编写调用signal函数的语句完成如下请求:
“子进程终止则调用mychild函数”
此时mychild函数的参数应为int,返回值类型应为void。只有这样才能成为signal函数的第二个参数。另外,常数SIGCHLD定义了子进程终止的情况,应成为signal函数的第一个参数。也就是说,signal函数调用语句如下:
signal(SIGCHLD, mychild);
接下来编写signal函数的调用语句,分别完成如下2个请求:
"已到通过alarm函数注册的时间,请调用timeout函数“
“输入ctrl+c时调用keycontrol函数”
代表这2中情况的常数分别为SIGALRM和SIGINT,因此按如下方式调用signal函数。
signal(SIGALRM, timeout);
signal(SIGINT, keycontrol);
注册好信号后,发生注册信号时,操作系统将调用该信号对应的函数。
alarm函数
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
返回0或以秒为单位的距SIGALRM信号发生所剩时间。
如果调用该函数的同时向它传递一个正整型参数,相应时间后将产生SIGALRM信号。若向该函数传递0,则之前对SIGALRM信号的预约将取消。
如果通过该函数预约信号后未指定该信号对应的处理函数,则(通过调用signal函数)终止进程,不做任何处理。
/* signal.c */
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void timeout(int sig)
{
if (sig == SIGALRM)
puts("Time out!");
alarm(2);
}
void keycontrol(int sig)
{
if (sig == SIGINT)
puts("CTRL+C pressed!");
}
int main(int argc, char* argv[])
{
int i;
signal(SIGALRM, timeout);
signal(SIGINT, keycontrol);
alarm(2);
for (i = 0; i < 3; i++)
{
puts("wait ...");
sleep(100);
}
return 0;
}
为了查看信号产生和信号处理器的执行并提供每次100秒 共3次的等待时间,在循环中调用sleep函数。也就是说,再过300秒、约5分钟后终止程序,这是相当长的一段时间,但实际执行时只需不到10秒。???
mali@mali:~/code/process$ gcc signal.c -o signal
mali@mali:~/code/process$ ./signal
wait ...
Time out!
wait ...
Time out!
wait ...
Time out!
mali@mali:~/code/process$ ./signal
wait ...
^CCTRL+C pressed!
wait ...
Time out!
wait ...
^CCTRL+C pressed!
mali@mali:~/code/process$ ./signal
wait ...
Time out!
wait ...
^CCTRL+C pressed!
wait ...
Time out!
mali@mali:~/code/process$ ./signal
wait ...
^CCTRL+C pressed!
wait ...
^CCTRL+C pressed!
wait ...
^CCTRL+C pressed!
mali@mali:~/code/process$
“发生信号时将唤醒由于调用sleep函数而进入阻塞状态的进程”
调用函数的主体的确是操作系统,但进程处于睡眠状态时无法调用函数。因此,产生信号时,为了调用信号处理器,将唤醒由于调用sleep函数而进入阻塞状态的进程。而且,进程一旦被唤醒,就不会再进入睡眠状态。即使还未到sleep函数中规定的时间也是如此。所以,上述示例运行不到10s就会结束,连续输入ctrl+c则有可能 1s都不到。
利用sigaction函数进行信号处理
#include <signal.h>
int sigaction(int signo, const sruct sigaction* act, struct sigaction* oldact);
成功时返回0,失败时返回-1.
声明并初始化sigaction结构体变量以调用上述函数,该结构体定义如下:
struct sigaction
{
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
}
/* sigaction.c */
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void timeout(int sig)
{
if (sig == SIGALRM)
puts("Time out!");
alarm(2);
}
int main(int argc, char* argv[])
{
int i;
struct sigaction act;
act.sa_handler = timeout;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGALRM, &act, 0);
alarm(2);
for (int i = 0; i < 3; i++)
{
puts("wait");
sleep(100);
}
return 0;
}
mali@mali:~/code/process$ gcc sigaction.c -o sigaction
mali@mali:~/code/process$ ./sigaction
wait
Time out!
wait
Time out!
wait
Time out!
mali@mali:~/code/process$
利用信号处理技术消灭僵尸进程
/* remove_zombie.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
void read_childproc(int sig)
{
int status;
pid_t id = waitpid(-1, &status, WNOHANG);
if (WIFEXITED(status))
{
printf("Remove proc id:%d\n", id);
printf("Child send:%d\n", WEXITSTATUS(status));
}
}
int main(int argc, char* argv[])
{
pid_t pid;
struct sigaction act;
act.sa_handler = read_childproc;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD, &act, 0);
pid = fork();
if (pid < 0)
{
perror("fork error");
exit(1);
}
else if (pid == 0)
{
puts("Hi, I am the first child process");
sleep(10);
return 12;
}
else
{
printf("first Child Process ID: %d\n", pid);
pid = fork();
if (pid < 0)
{
perror("fork error");
exit(1);
}
else if (pid == 0)
{
puts("Hi, I am the second child process");
sleep(10);
exit(24);
}
else
{
int i;
printf("second Child Process ID: %d\n", pid);
for (i = 0; i < 5; i++)
{
puts("wait...");
sleep(5);
}
}
}
return 0;
}
mali@mali:~/code/process$ gcc remove_zombie.c -o remove_zombie
mali@mali:~/code/process$ ./remove_zombie
first Child Process ID: 9980
second Child Process ID: 9981
wait...
Hi, I am the second child process
Hi, I am the first child process
wait...
Remove proc id:9981
Child send:24
wait...
Remove proc id:9980
Child send:12
wait...
wait...
mali@mali:~/code/process$
函数exec
当进程调用一种exec函数时,该进程执行的程序完全替换为新程序,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。
/* Replace the current process, executing PATH with arguments ARGV and
environment ENVP. ARGV and ENVP are terminated by NULL pointers. */
int execve (const char *__path, char *const __argv[],
char *const __envp[]) __THROW __nonnull ((1, 2));
/* Execute PATH with arguments ARGV and environment from `environ'. */
int execv (const char *__path, char *const __argv[])
__THROW __nonnull ((1, 2));
/* Execute PATH with all arguments after PATH until a NULL pointer,
and the argument after that for environment. */
int execle (const char *__path, const char *__arg, ...)
__THROW __nonnull ((1, 2));
/* Execute PATH with all arguments after PATH until
a NULL pointer and environment from `environ'. */
int execl (const char *__path, const char *__arg, ...)
__THROW __nonnull ((1, 2));
/* Execute FILE, searching in the `PATH' environment variable if it contains
no slashes, with arguments ARGV and environment from `environ'. */
int execvp (const char *__file, char *const __argv[])
__THROW __nonnull ((1, 2));
/* Execute FILE, searching in the `PATH' environment variable if
it contains no slashes, with all arguments after FILE until a
NULL pointer and environment from `environ'. */
extern int execlp (const char *__file, const char *__arg, ...)
#ifdef __USE_XOPEN2K8
/* Execute the file FD refers to, overlaying the running program image.
ARGV and ENVP are passed to the new program, as for `execve'. */
int fexecve (int __fd, char *const __argv[], char *const __envp[])
__THROW __nonnull ((2));
#endif
/* echoall.c */
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int i;
char **ptr;
extern char **environ;
for (i = 0; i < argc; i++)
printf("argv[%d]: %s\n", i, argv[i]);
for (ptr = environ; *ptr != 0; ptr++)
printf("%s\n", *ptr);
return 0;
}
/*exec.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
char *env_init[] = {"USER=unknown", "PATH=/tmp", NULL};
int main(void)
{
pid_t pid;
if ((pid = fork()) < 0)
{
perror("fork error");
exit(1);
}
else if (pid == 0)
{
if (execle("/home/mali/code/process/echoall", "echoall", "myarg1",
"MY ARG2", (char*)0, env_init) < 0)
{
perror("execle error");
exit(1);
}
}
if (waitpid(pid, NULL, 0) < 0)
{
perror("waitpid error");
exit(1);
}
if ((pid = fork()) < 0)
{
perror("fork error");
exit(1);
}
else if (pid == 0)
{
if (execlp("echoall", "echoall", "only 1 arg", (char*)0) < 0)
{
perror("execlp error");
exit(1);
}
}
exit(0);
}
mali@mali:~/code/process$ PATH=$PATH:~/code/process
mali@mali:~/code/process$ echo $PATH
/home/mali/bin:/home/mali/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/mali/code/process
mali@mali:~/code/process$ ./exec
argv[0]: echoall
argv[1]: myarg1
argv[2]: MY ARG2
USER=unknown
PATH=/tmp
mali@mali:~/code/process$ argv[0]: echoall
argv[1]: only 1 arg
XDG_VTNR=7
LC_PAPER=zh_CN.UTF-8
LC_ADDRESS=zh_CN.UTF-8
XDG_SESSION_ID=c2
XDG_GREETER_DATA_DIR=/var/lib/lightdm-data/mali
LC_MONETARY=zh_CN.UTF-8
CLUTTER_IM_MODULE=xim
GPG_AGENT_INFO=/home/mali/.gnupg/S.gpg-agent:0:1
......
函数system
#include <stdlib.h>
int system(const char *cmdstring);
由于system在其实现过程中调用了fork exec 和waitpid,因此有三种返回值:
- fork失败或者waitpid返回除EINTR之外的出错,则system返回-1,并且设置errno以指示错误类型
- 如果exec失败(表示不能执行shell),则其返回值如同shell执行了exit(127)一样
- 否则三个函数都成功,那么system的返回值是shell的终止状态,其格式已在waitpid中说明。