linux---进程控制

目录

进程标识

函数fork()

僵尸(Zombie)进程

销毁僵尸进程:函数wait和waitpid

信号处理

信号和signal函数

alarm函数

利用sigaction函数进行信号处理

利用信号处理技术消灭僵尸进程 

函数exec

函数system


进程标识

每个进程都有一个非负整型表示的唯一进程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不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内。如果不关心终止状态,则可将该参数指定为空指针。

检查wait和waitpid所返回的终止状态的宏
说明
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中说明。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值