进程控制原语

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>                                                           
#include<stdlib.h>

int main(void)
{
    pid_t fpid; //fpid表示fork函数返回的值
    int count=0;
    fpid=fork(); 
    if (fpid < 0) 
        printf("error in fork!\n"); 
    else if (fpid == 0) {
        printf("i am the child process, my process id is %d\n",getpid()); 
        printf(" my parent process id is %d\n",getppid()); 
        count++;
    }
    else {
        printf("i am the parent process, my process id is %d\n",getpid()); 
        count++;
        

    }
    printf("count 统计结果是: %d\n",count);
    while(1);//无限阻塞
    return 0;
}

 

fork 函数,创建子进程。

函数原型:

 

 

关于其返回值:

 

 

fork函数一次调用,两次返回。子进程中返回0,父进程中,返回子进程的ID。如果fork失败,返回-1.并且不会创建子进程,同时错误代码errno会被设置。

fork的读时共享,写时复制机制。子进程拥有和父进程一样的0-3G用户空间,但是3-4G内核空间中PCB(进程控制块)的进程ID号并不相同。子进程和父进程有如此多相同的地方,如果仅仅是读取0-3G用户空间的数据,则没有必要复制一份父进程的数据到内存,但如果需要写入数据,就必须开辟新的空间了。

——————————————————》》》分割线

但是,子进程创建出来如果所做的任务和父进程完全一致,那么也就没有必要创建子进程的意义了。通常而言,子进程被创建,是要做与父进程不一样的事情。

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>                                                           
#include<stdlib.h>

int main(void)
{
    pid_t fpid; 
    int count=0;
    fpid=fork(); 
    if (fpid < 0) 
        printf("error in fork!\n"); 
    else if (fpid == 0) {
        char *const argv[]={"ls","-l",NULL};
        printf("i am a child process\n");
        execvp("ls",argv);
        printf("where is my process?\n");
    }
    else {
        printf("i am a parent process\n"); 

    }
    while(1);
    return 0;
}

execvp:属于exec族中的一个函数。

 

当进程调用了exec族的某一函数之后,该进程执行的程序被替换成全新的程序,新程序从其main函数开始执行。由于调用exec函数不会创建新的进程,所以替换前后的进程ID并不会改变。exec只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。

在上面的程序中,使用了ls –l这个指令,可以发现该指令确实正确执行了,但是显示的列表中,a.out没有颜色信息。其次,在char *const argv[]={"ls","-l",NULL};中,第一个“ls”字符串只有一个占位符的作用,该字符串无论是什么都不会影响程序的运行结果,因为在execvp("ls",argv);中已经指定了ls程序,在argv指针数组中的argv[0]只用作占位符。哪怕将其改为”hehe”:

程序依然正常运行,exec函数会从argv[1]开始,直到NULL结束。

现在新建一个upper.c文件:

/* upper.c */
#include<stdio.h>
#include <ctype.h>

int main(void)
{
    in ch;
    while((ch=getchar())!=EOF)
        putchar(toupper(ch));//小写转大写
    return 0;
}

把之前的test.c改成:

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>                                                           
#include<stdlib.h>

int main(void)
{
    pid_t fpid; 
    int count=0;
    fpid=fork(); 
    if (fpid < 0) 
        printf("error in fork!\n"); 
    else if (fpid == 0) {
        char *const argv[]={"upper",NULL};
        printf("i am a child process\n");
        execv("./app",argv);
        printf("where is my process?\n");
    }
    else {
        printf("i am a parent process\n"); 

    }
    while(1);
    return 0;
}

先编译upper.c生成app,然后编译test.c生成a.out,运行a.out:

exec函数运行之后,只有出错才会返回,这也就意味着,exec函数后面的语句不会被执行。同样,这里的char *const argv[]={"upper",NULL};中的“upper”字符串也可以是其他任何字符串(即使为NULL也行,但通常使用能代表其含义的名称)。

wait和waitpid函数:

僵尸进程: 子进程退出,父进程没有回收子进程资源(PCB),则子进程变成僵尸进程。

孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为1号进程init进程,称为init进程领养孤儿进程。

一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,并彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看(echo $?),因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。如果一个进程已经终止,但是它的父进程尚未调用wait或waitpid对它进行清理,这时的进程状态称为僵尸(Zombie)进程。任何进程在刚终止时都是僵尸进程,正常情况下,僵尸进程都立刻被父进程清理了。

目前,只用知道,wait和waitpid的基本用法即可,至于到底怎么应用在程序中,可以暂时不理会。

如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。

 

posted @ 2019-04-29 22:05  Crystal_Guang  阅读(1307)  评论(0编辑  收藏  举报