系统级程序设计(三 )

文章目录

  • 进程管理之exec函数族
    • exec_yj.c
      • 代码
      • 运行结果
  • 进程退出
    • exit()
    • _exit()
    • 区别
  • 特殊进程
  • 进程同步
    • wait函数
      • wait_yj.c
        • 代码
        • 运行结果
      • wait_yj2.c
        • 代码
        • 运行结果
    • waitPid函数
      • waitPid_yj.c
        • 代码
        • 运行结果
      • waitPid2_yj.c
        • 代码
        • 运行结果
  • 课堂作业
    • 代码
    • 运行结果
  • 总结

进程管理之exec函数族

exec中6个函数族分别为

#include 
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[]);

可以将exec函数族分为execl和execv两类:

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

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

exec_yj.c

代码

#include 
#include 
#include 
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","exec_yj.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 
void exit(int status);

参数说明:

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

_exit()

#include 
void _exit(int status);

区别

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

特殊进程

对之前概念的补充和新概念的学习

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

进程同步

wait函数

#include 
pid_t wait(int *status);

  • 功能:挂起进程,直至子进程变为僵尸态。回收子进程资源。若当前进程有多个子进程,只要捕获到有一个子进程变为僵尸态,就恢复执行态。
  • 参数:一个int *类型的指针,保存子进程退出时的状态信息。如果参数设置为NULL,则表示不关心进程如何终止。
  • 返回值:成功:子进程id;失败:返回-1,errno被设置为ECHILD
  • 补充:errno 是记录系统的最后一次错误代码。 代码是一个int型的值,在errno.h中定义。 查看错误代码errno是调试程序的一个重要方法。

wait_yj.c

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

代码

#include 
#include 
#include 
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, getpid());
	}//of if
	printf("......finish......\n");
	return 0;
}//of main


运行结果

系统级程序设计(三 )_第2张图片
把wait函数注释掉,父进程先于子进程运行并终止。因为sleep函数导致子进程进入等待队列,所以终端竞争得到了运行权。
系统级程序设计(三 )_第3张图片
查看发现已经被init领养。
系统级程序设计(三 )_第4张图片

以上是wait函数参数为空的情况,下面进行记录子进程退出状态。

wait_yj2.c

使用wait同步进程,并使用宏获取子进程的返回值。此处声明两个判断进程退出状态的宏函数:

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

代码

#include 
#include 
#include 
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

运行结果

系统级程序设计(三 )_第5张图片

waitPid函数

#include 
pid_t waitpid(pid_t pid, int *status, int options);

  • 功能:waitpid是基于wait函数的,wait函数有一个缺点:当有很多个子进程的时候,wait函数无法保证所有子进程在父进程之前执行。(只是捕捉到一个僵尸态进程而已)
    而waitpid则可以指定子进程,也可以在父进程不阻塞的情况下获取子进程状态。
  • 参数:
    pid:指定进程组、进程号
    1.>0:等待进程号为pid的子进程退出,若退出,函数返回;若未结束,一直等待
  1. =0:等待同一进程组的所有子进程退出,注意,若某进程进入其他进程组,则waitpid则不再关心他的状态
  2. -1:退化为waitpid函数
  3. <-1:等待指定进程组中的任何子进程,进程组的id等于pid的绝对值
    options:提供控制选项,可以是一个常量,也可以是|连接的两个常量

1 WNOHANG:如果子进程没有终止,waitpid不会阻塞父进程,会立即返回;
2. WUNTRACED:如果子进程暂停执行,waitpid立即返回;
3. 0:不使用选项。

  • 返回值:成功:返回捕捉到的子进程id
    0:options=WNOHANG,waitpid发现没有已退出的子进程可回收;-1:出错,errno被设置

waitPid_yj.c

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

代码

#include 
#include 
#include 
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

运行结果

系统级程序设计(三 )_第6张图片

waitPid2_yj.c

基于waitpid函数不断获取子进程的状态。

代码

#include 
#include 
#include 
#include 
int main(){
    pid_t tempPid,tempP,tempW;
    tempPid = fork();
    if(tempPid == -1){
        perror("fork error");
        exit(1);
    }else if(tempPid == 0){//child
        sleep(5);
        printf("First child process : pid = %d\n",getpid());
    }else{
        int i;
        tempP=tempPid;
        for(i=0; i < 3; i++){
            if((tempPid=fork())==0){
                break;
            }//of if
        }//of for
        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

运行结果

系统级程序设计(三 )_第7张图片
父进程可以通过wait()和waitpid()函数可以有效防止僵尸进程的产生,对于已存在的僵尸进程,则可通过杀死其父进程的方法解决,当僵尸进程的父进程被终止后,僵尸进程将作为孤儿进程被init进程接收,init进程会不断调用wait()函数获取子进程状态,对已处于僵尸态的进程进行处理。所以说,孤儿进程永远不会成为僵尸进程。

课堂作业

创建一个子进程,使父子进程分别打印不同的内容;创建一个子进程,使子进程通过exec更改代码段,执行cat命令。整合到如下:

代码

#include 
#include 
#include 
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());
        //char *arg[]={"/home/bonjour/桌面/file", NULL};
        //execvp("cat", arg);
        execl("/bin/cat","cat","/home/bonjour/桌面/file",NULL);
        perror("error exec\n");
        printf("child process:pid=%d\n", getpid());
    } //of if
    return 0;
} //of main

运行结果

系统级程序设计(三 )_第8张图片

总结

学会了使用exec(),exit(), wait(), waitpidf() 等函数,对进程管理和进程同步有了更深的理解。

你可能感兴趣的:(系统级程序设计,c语言,linux)