Linux进程fork()、exit()、wait()、exec族、system函数、popen函数

本文详细介绍了Linux进程管理的相关概念,包括进程的创建(fork)、退出(exit)、等待(wait)、exec族函数以及system和popen函数的使用。通过多个实战例子展示了如何在C语言中运用这些函数,帮助读者深入理解Linux进程操作。

进程相关概念、创建进程fork()、进程退出exit()、等待函数wait()、exec族、system函数、popen函数

一、进程相关概念

问1. 什么是程序,什么是进程,有什么区别?
程序是静态的概念,gcc xxx.c –o pro,磁盘中生成pro文件。
进程是程序的一次运行活动,通俗点意思是程序跑起来了,系统中就多了一个进程。
问2. 如何查看系统中有哪些进程?
a. ps -aux|grep +(程序名)
b. top指令查看,类似于任务管理器。
问3. 什么是进程标识符?
每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证。
特别地:
Pid=0: 称为交换进程(swapper)作用—进程调度
Pid=1:init进程作用—系统初始化。
获取pid值:
调用getpid函数获取自身的进程标识符。
getppid获取父进程的进程标识符。
问4. 什么叫父进程,什么叫子进程?
进程A创建了进程B。那么A叫做父进程,B叫做子进程,
问5. C程序的存储空间是如何分配?
在这里插入图片描述
正文段:代码段
初始化的数据:数据段
未初始化的数据:bss段通常是用来存放程序中未初始化的全局变量和静态变量
堆:malloc等函数申请临时空间
栈:函数调用、局部变量

二、创建进程

fork头文件和函数

// 头文件
#include <unistd.h>
#include <sys/types.h>
//函数
pid_t fork(void);
//返回:success 返回两次 返回值为0 代表当前进程是子进程
//					   返回值为非负数 代表当前进程是父进程,返回值为子进程ID

pid_t 实质是 int 被定义在 #include<sys/types.h>

fork创建子进程的目的

  • 一个父进程复制自己,父、子进程同时执行不同代码段。父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork。使得子进程处理这种请求,父进程继续下一请求。

  • 一个进程执行不同的程序,这是shell常见的情况,这种情况下fork返回立即调用exec。

fork写时拷贝
Linux的fork()使用写时拷贝(copy-on-write)页实现。写时拷贝是一种可以推迟甚至避免拷贝数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。
也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候。
vfork与fork的区别
区别一:vfork直接使用父进程储存空间,不拷贝。
区别二:vfork保证子进程先运行,当子进程exit后,父进程运行。
区别一举例说明:当子进程修改变量a=10时,那么父进程中的a也改成了10。

三、进程退出

正常退出

  1. Main函数调用return
  2. 进程调用exit(),标准C语言库
  3. 进程调用_exit()或者_Exit(),属于系统调用
  4. 进程最后一个线程返回
  5. 最后一个线程调用pthread_exit

异常退出

  1. 调用abort函数
  2. 当进程收到某些信号时,如ctrl+C
  3. 最后一个线程对取消(cancellation)请求做出响应

正常线程退出使用pthread_exit(),进程退出调用exit()。不论时正常还是异常退出状态下,子进程需要传参父进程,使得父进程知道子进程运行状态,将运行状态status传递给wait或者waitpid参数。
下面展示一些 exit代码片

// exit头文件
#include <stdlib.h>
//exit主函数
void exit(int status);
//_exit头文件
#include <unistd.h>
//_exit主函数
void _exit(int status);
//_Exit头文件
#include <stdlib.h>
//Exit主函数
void Exit(int status);

四、进程等待

进程等待函数的意义:父进程等待子进程退出,并收集子进程的退出状态。如果子进程的退出状态不被收集,变成僵尸进程(zombie)。

下面展示一些 wait()、waitpid()头文件和主函数

// 头文件
#include <sys/types.h>
#include <sys/wait.h>
//主函数
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
//返回
// success 返回  子进程ID ;  error 返回  -1

下面展示一些 参数解释

status:是一个整型数指针
非空:子进程退出状态放在它所指向的地址中。
空:不关心退出状态。
一般想要获取status的返回值,所用函数为WEXITSTATUS(status)。举例说明,如果检测子进程exit(3),可以得到WEXITSTATUS(status)=3,用法如下:
wait(status);
printf(“father process, status = %d\n”,WEXITSTATUS(status));

pid_t pid的取值
pid==-1,等待任一子进程,此时和wait()等效。
pid>0,等待与pid值一致的子进程。
pid==0,等到其组ID和调用进程的组ID相等的任一子进程。
pid<-1,等到其组ID和pid绝对值相等的任一子进程。

options的取值可以取0,或者取下列组合:
WCONTINUED:pid指定的任一子进程在暂停后已经继续,但其状态尚未报告,则返回其状态。
WNOHANG:常用,waitpid将不阻塞如果指定的pid并未结束。
WUNTRACED:如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。

孤儿进程
含义:父进程先结束,子进程后结束。子进程就变成了孤儿进程。
Linux处理方法:Linux避免存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程。

**pr_exit(status)**打印status的数值
下面展示一些 pr_exit()代码段

// pr_exit()函数
void pr_exit(int status)
{
	if(WIFEXITED(status)){
		printf("normal termination, exit status =  %d\n",WEXITSTATUS(status));
	}elseif (WIFESIGNALED(status)){
		printf("abnormal termination, exit status =  %d\n",WTERMSIG(status));
	}
}

五、exec族

功能:
  在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
  当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
函数族:
  exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe

// 头文件
#include <unistd.h>
//主函数
extern char **environ;

 int execl(const char *path, const char *arg, .../* (char  *) NULL */);
 int execlp(const char *file, const char *arg, .../* (char  *) NULL */);
 int execle(const char *path, const char *arg, ... /*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
//返回
//success时 不会返回 ;  error时 返回 -1 ,然后从原程序的调用点接着往下执行。

参数说明:
path:可执行文件的路径名字
arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。

exec族函数参数极难记忆和分辨,函数名中的字符会给我们一些帮助:
l : 使用参数列表
p:使用文件名,并从PATH环境进行寻找可执行文件
v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。
e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量

举例说明 execl

// A code block
 execl("./forkex","forkex","config.txt",NULL);
 //第一个参数path代表可执行程序的路径,第二个参数arg代表可执行程序的名字。第三个参数file代表真正的参数,第四个参数必须已NULL结尾

具体程序看后面编程举例

六、system

执行程序两种方法 。 ./11 或者 sh -c ./11

// 头文件
#include <stdlib.h>
//主函数
int system(const char* command);
//返回
//success时 返回进程状态值 ;  error时 返回 -1 ,当sh不能执行,返回127

system和exec的区别

  • system返回成功后继续执行原始程序的代码。
  • exec返回成功后,子进程直接结束。

举例说明 system和exec

//二者等同
system("./changeData config,txt");
excel("./changeData", "changeData" ,"config,txt", NULL);

七、popen

// 头文件
#include <stdio.h>
//主函数
FILE popen(const char* command,const char* type);
//返回
//success时 返回 指向文件指针 ;  error时 返回 -1 

参数说明:
type:“r” 只读 “w” 只写
函数作用
popen创建一个管道:其内部实现和调用fork,产生一个子进程,执行一个shell命令来开启一个进程,这个进程,这个进程必须由pclose()函数关闭。
与system区别
popen可以获取运行输出结果

举例说明 popen

//二者等同
fp = popen("./changeData abc","r");

feof()
int feof(FILE *stream) 测试给定流 stream 的文件结束标识符。

//头文件
#include<stdio.h>
// 主函数
int feof(FILE *stream)
//返回
//当设置了与流关联的文件结束标识符时,该函数返回一个非零值,否则返回零。

参数说明:
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。

实战举例一:进程等待

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

int main()
{
    pid_t pid;

     int cnt=0;
     pid =fork();
     int status=10;
         
     if(pid>0){ //进入父进程
 //     wait(&status);  
        waitpid(pid, &status, WNOHANG);	//进程等待
 
        while(1){
             printf("cnt=%d\n",cnt);
             printf("father process , pid =%d,status=%d\n",getpid(),WEXITSTATUS(status));
             sleep(1);
                 }
     }else if(pid == 0) {	//进入子进程
       while(1){
             printf("child process , pid =%d\n",getpid());
             cnt++;
             sleep(1);
 
             if(cnt==4){
             exit(5);
             }
    	 }
     }
 	return 0;
 }

实战举例二:exec应用

echoarg.c

#include <stdio.h>

int main(int argc,char *argv[])
{
   int i = 0;
   for(i = 0; i < argc; i++)
    {
        printf("argv[%d]: %s\n",i,argv[i]);
    }
    return 0;
 }

execl函数应用

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("before execl\n");
    if(execl("./echoarg","echoarg","abc",NULL) == -1)
    {
         printf("execl failed!\n");      
    }
    printf("after execl\n");
    return 0;
}

execlp函数应用

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("before execl\n");
    if(execlp("echoarg","echoarg","abc",NULL) == -1)
    {
         printf("execl failed!\n");      
    }
    printf("after execl\n");
    return 0;
}

execvp函数应用

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
    printf("before execl\n");
	char *argv[]={"echoarg","abc","NULL"};
	if(execvp("echoarg",argv) == -1){
         printf("execl failed!\n");      
    }
    printf("after execl\n");
    return 0;
}

system函数应用

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//函数原型:int execl(const char *path, const char *arg, ...);

int main(void)
{
	if(system("./echoarg abc") == -1)
	{
		printf("execl failed!\n");
		perror("why");
	}
	printf("after execl\n");
 	return 0;
}

实战举例三:popen应用

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
     char ret[1024]={0};
     FILE *fp=NULL;
     fp=popen("./echoarg abc","r");
   	 int nread = fread(ret,1,1024,fp);
     perror("why");

     printf("read %d types,ret = %s\n",nread,ret);
     return 0;
 }

实战举例四:fork和exec联合使用

TEST.config

  SPEED=3
  LENG=5
  SCORE=9
  LEVEL=5

主函数

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

int main()
{
        int data=10;
        pid_t pid;
        while(1){
           	printf("please input a data\n");
            scanf("%d",&data);
                  
            if(data==1){
                pid==fork();
                if(pid>0){ 
                    wait(NULL);
                 }
                int fdSrc;
                if(pid==0){
              	  execl("./forkex","forkex","config.txt",NULL); //forkex是生成好的可执行程序
                  exit(0);
                }
            }
            else{
               printf("wait, do nothing\n");
            } 
        }
        return 0;
 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值