系统程序设计第3课

目录

学习目标

一、exec函数族

1.exec函数族作用

2.exec()系列函数

二、进程退出

1.exit函数

2._exit函数

三、 特殊进程

1.特殊进程分类

2.特殊进程危害

四、 进程同步

1.wait函数

2.waitpid函数


学习目标

学习进程管理中exec函数族
学习进程退出
学习特殊进程
学习进程同步


一、exec函数族

1.exec函数族作用

exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,主要是在fork()之后,它会诱导fork后的子进程另起炉灶,但是PID不变,自己开创一片新天地,清空原来的堆栈区、代码区和数据区,让新的可执行程序完全替代现有的资源。

2.exec()系列函数

#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char * const argv[]);
int execvp(const char *file, char * const argv[]);
int execve(const char *path, char * const argv[], char * const envp[]);

参数说明:
(1)当参数是path,传入的为路径名;当参数是file,传入的可执行文件名;
(2)可以将exec函数族分为execl和execv两类:

  • execl类:函数将以列举的形式传入参数,由于参数列表的长度不定,所以要用哨兵NULL表示列举结束;
  • execv类:函数将以参数向量表传递参数,char * argv[]的形式传递文件执行时使用的参数,数组中最后一个参数为NULL;

(3)如果没有参数char * const envp[],则采用默认环境变量;如果有,则用传入的参数替换默认环境变量;

注意:
在使用exec函数族时,尽量要加上错误判断语句,因为exec很容易执行失败:判断execl的返回值(错误返回-1),如果出错,可以用perror( )函数打印出错误信息。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
    pid_t tempPid;
    tempPid=fork();
    if(tempPid == -1){   
        perror("fork error");
        exit(1);
    } else if(tempPid > 0) {   
        printf("parent process:pid=%d\n", getpid());
    } else {   
        printf("child process:pid=%d\n", getpid());
        //execl("/bin/ls","-a","-l","test_exec.c",NULL);    //①
        //execlp("ls","-a","-l","test_exec.c",NULL);    //②
        char *arg[]={"-a","-l","test_exec.c", NULL};    //③
        execvp("ls", arg);
        perror("error exec\n");
        printf("child process:pid=%d\n", getpid());
    } //of if  
    return 0;
} //of main

 

二、进程退出

1.exit函数

#include <stdlib.h>
void exit(int status);

2._exit函数

#include <unistd.h>
void _exit(int status);

参数说明:
(1)status:表示进程的退出状态,0表示正常退出,非0表示异常退出,一般用-1或1表示;
(2)为了可读性,标准C定义了两个宏:EXIT_SUCCESS和EXIT_FAILURE

区别:

  • _exit:系统会无条件停止操作,终止进程并清除进程所用内存空间及进程在内核中的各种数据结构;
  • exit:对_exit进行了包装,在调用_exit()之前先检查文件的打开情况,将缓冲区中的内容写回文件。相对来说exit比_exit更为安全

三、 特殊进程

1.特殊进程分类

  • 孤儿进程:父进程负责回收子进程,如果父进程在子进程退出之前退出,子进程就会变成孤儿进程,此时init进程将代替父进程完成子进程的回收工作;
  • 僵尸进程:调用exit函数后,该进程不会马上消失,而是留下一个称为僵尸进程的数据结构。它几乎放弃进程退出前占用的所有内存,既没有可执行代码也不能被调度,只是在进程列表中保留一个位置,记载进程的退出状态等信息供父进程回收。若父进程没有回收子进程的代码,子进程将会一直处于僵尸态。

2.特殊进程危害

  • 僵尸进程不能再次被运行,会占用一定的内存空间,并占据进程编号,当僵尸进程较多时,将会消耗系统内存,新进程可能因内存不足或无法获取pid而无法创建;
  • 父进程通过wait()和waitpid()函数可以有效防止僵尸进程的产生,对于已存在的僵尸进程,则可通过杀死其父进程的方法解决;
  • 当僵尸进程的父进程被终止后,僵尸进程将作为孤儿进程被init进程接收,init进程会不断调用wait()函数获取子进程状态,对已处于僵尸态的进程进行处理;
  • 孤儿进程永远不会成为僵尸进程。

四、 进程同步

1.wait函数

#include <sys/wait.h>
pid_t wait(int *status);

 

功能:
挂起进程,进程进入阻塞状态,直到子进程变为僵尸态,如果捕获到子进程的退出信息就会转为运行态,然后回收子进程资源并返回;若没有变为僵尸态的子进程,wait函数就会让进程一直阻塞。若当前进程有多个子进程,只要捕获到一个变为僵尸态的子进程,wait函数就会恢复执行态。

参数说明:
参数status是一个int *类型的指针,用来保存子进程退出时的状态信息。通常情况下该参数设为NULL,表示不关心进程时如何终止的。

返回值说明:

  • 成功:返回子进程的进程id;
  • 失败:返回-1,errno被设置为ECHILD。

案例1
若子进程p 1 是其父进程p 的先决进程,基于wait函数使得进程同步。

test_wait.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main(){
	pid_t tempPid, tempW;
	tempPid = fork();
	if(tempPid == -1){
		perror("fork error");
		exit(1);
	}else if(tempPid == 0){//child
		sleep(3);
		printf("Child process, pid = %d, ppid = %d\n", getpid(), getppid());
	}else{//parent 
		tempW = wait(NULL);
		printf("Catched a child process, pid = %d, ppid = %d\n", tempW, getppid());
	}//of if
	printf("......finish......");
	return 0;
}//of main

运行结果:

 

若wait函数的参数不为空,可以获取子进程的退出状态,退出状态存放在参数status的低八位中。Linux定义了一组判断进程退出状态的宏函数,其中最基础的两个是:

#include <sys/wait.h>
int WIFEXITED(int status);//判断子进程是否正常退出,若是,返回非0值,否则返回0
int WEXITSTATUS(int status);//和WIFEXITED配合使用,WIFEXITED返回非0值,则使用该宏提取子进程的返回值。

案例2
使用wait同步进程,并使用宏获取子进程的返回值。

test_wait2.c
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
    int tempStatus;
    pid_t tempPid, tempW;
    tempPid = fork();
    if(tempPid == -1){
        perror("fork error");
        exit(1);
    } else if(tempPid == 0){//子
        sleep(3);
        printf("Child process: pid=%d\n",getpid());
        exit(5);
     }  else{//父
        tempW = wait(&tempStatus);
        if(WIFEXITED(tempStatus)){
            printf("Child process pid=%d exit normally.\n", tempW );
            printf("Return Code:%d\n",WEXITSTATUS(tempStatus));
        } else {
            printf("Child process pid=%d exit abnormally.\n", tempW);
        }//of if
    }//of if
    return 0;
}//of main

运行结果:

2.waitpid函数

 

#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);

功能:
wait函数的缺点:当前进程有很多个子进程,wait函数无法保证所有子进程在父进程之前执行。
waitpid函数:可以应对 wait函数面临的缺点。可以等待指定的子进程,也可以在父进程不阻塞的情况下获取子进程的状态。
参数说明:

  • pid:一般是进程的pid,也有可能是其他取值。进一步说明如下:
    – pid > 0:等待子进程(编号为pid)退出,若退出,函数返回;若未结束,则一直等待;
    – pid = 0:等待同一进程组的所有子进程退出,若某子进程加入了其他进程组,则waitpid不再关心它的状态;
    – pid = -1:waitpid函数退化为wait函数,阻塞等待并回收一个子进程;
    – pid < -1:等待指定进程组中的任何子进程,进程组的id等于pid的绝对值。

  • options: 提供控制选项,可以是一个常量,也可以是|连接的两个常量,选项如下:
    – WNOHANG:如果子进程没有终止,waitpid不会阻塞父进程,会立即返回;
    – WUNTRACED:如果子进程暂停执行,waitpid立即返回;
    – 0:不使用选项。
    返回值说明:

  • 成功:返回捕捉到的子进程id;

  • 0:options = WNOHANG, waitpid发现没有已退出的子进程可回收;

  • -1:出错,errno被设置。

    功能:
    wait函数的缺点:当前进程有很多个子进程,wait函数无法保证所有子进程在父进程之前执行。
    waitpid函数:可以应对 wait函数面临的缺点。可以等待指定的子进程,也可以在父进程不阻塞的情况下获取子进程的状态。
    参数说明:

  • pid:一般是进程的pid,也有可能是其他取值。进一步说明如下:
    – pid > 0:等待子进程(编号为pid)退出,若退出,函数返回;若未结束,则一直等待;
    – pid = 0:等待同一进程组的所有子进程退出,若某子进程加入了其他进程组,则waitpid不再关心它的状态;
    – pid = -1:waitpid函数退化为wait函数,阻塞等待并回收一个子进程;
    – pid < -1:等待指定进程组中的任何子进程,进程组的id等于pid的绝对值。

  • options: 提供控制选项,可以是一个常量,也可以是|连接的两个常量,选项如下:
    – WNOHANG:如果子进程没有终止,waitpid不会阻塞父进程,会立即返回;
    – WUNTRACED:如果子进程暂停执行,waitpid立即返回;
    – 0:不使用选项。
    返回值说明:

  • 成功:返回捕捉到的子进程id;

  • 0:options = WNOHANG, waitpid发现没有已退出的子进程可回收;

  • -1:出错,errno被设置。

      案例3

  • 父进程等待进程组中指定子进程,该进程不退出,则父进程一直阻塞。

test_waitpid.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main(){
    pid_t tempPid, tempP, tempW;
    tempPid= fork();                            //创建第一个子进程
    if (tempPid == -1){                            
        perror("fork1 error");
        exit(1);
    } else if (tempPid == 0){                        //子进程沉睡
        sleep(5);
        printf("First child process:pid=%d\n", getpid());
    } else {                        //父进程继续创建进程
        int i;
        tempP = tempPid;
        for (i = 0; i < 3; i++){                    //由父进程创建3个子进程
            if ((tempPid = fork()) == 0){
                break;
            }//of if
        }//of for i
        if (tempPid == -1){                        //出错
            perror("fork error");
            exit(2);
        } else if (tempPid == 0){                    //子进程
            printf("Child process:pid=%d\n", getpid());
            exit(0);
        } else {                    //父进程
            tempW = waitpid(tempP, NULL, 0);            //等待第一个子进程执行
            if (tempW == tempP){
                printf("Catch a child Process: pid=%d\n", tempW);
            }else{
                printf("waitpid error\n");
            }//of if
        }//of if
    }//of if
    return 0;
}//of main

运行结果:

 案例4
基于waitpid函数不断获取子进程的状态

test_waitpid2.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
    pid_t tempPid, tempW;
    tempPid = fork();
    if (tempPid == -1){
        perror("fork error");
        exit(1);
    } else if (tempPid == 0){
        sleep(3);
        printf("Child process:pid=%d\n", getpid());
        exit(0);
    } else {
        do{
            tempW = waitpid(tempPid, NULL, WNOHANG);
            if (tempW == 0){
                printf("No child exited\n");
                sleep(1);
            }//of if
        } while (tempW == 0);
        if (tempW == tempPid){
            printf("Catch a Child process:pid=%d\n", tempW);
        }else{
            printf("waitpid error\n");
        }//of if
    }//of if
    return 0;
}//of main

运行结果:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值