Linux基础--进程(二)

进程在退出时会返回退出码,用于指示程序执行状态。退出可以通过main函数return、exit或_exit函数。waitpid用于父进程等待子进程并获取退出信息。进程替换允许子进程执行新的程序,如execl、execlp等函数实现。环境变量和参数可以通过exec系列函数传递给新程序。
摘要由CSDN通过智能技术生成

进程退出

      进程退出的时候,都会返回进程退出码,当你在命令行下执行命令的时候,同样会返回进程退出码,可以使用  echo $?  查看最近一个进程在命令行中执行完毕时对应的退出码,跟main函数的return返回值是一个道理。

进程退出的情况:1、main函数return正常返回0; 2、代码跑完了,结果不正确,return !0; 3、代码没跑完,程序异常结束,退出码无意义。而进程退出码最大的意义在于情况2,让你知道代码是为何出错了。

进程是如何退出的:1、main函数return返回;2、任意地方调用exit(code)返回;3、_exit()函数的调用。其中exit(code)终止进程,主动刷新缓冲区而_exit()终止进程,不会刷新缓冲区。其实exit()是c语言封装的函数,其底层是_exit()函数,是一个系统调用,归操作系统调用。而对于OS而言,在调用_exit()之后,是肯定会刷新缓冲区的,证明了用户的数据一定不是在系统调用及其往下,应该是在用户级的缓冲区---基础IO的时候。

进程等待:但进程完成自己的任务之后,需要将数据传输出去或者被回收资源的时候的状态称之为僵尸状态。可以通过进程等待(waitpid函数方法)的方式去解决僵尸进程的问题。当父子进程一起执行的时候,本该是由父进程负责回收子进程的资源,但是父进程率先退出的时候,此时的子进程便会被“寄养”在操作系统名下,此时的进程称之为孤儿进程,但子进程的任务完成之后,又操作系统回收资源,获取子进程退出信息。

如何获取子进程的退出信息:

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

#include <sys/types.h>
#include <sys/wait.h>
int main()
{
  进程退出码///    
  pid_t id =fork();
  if(id==0)
  {
    //child
    int cnt=5;
    while(cnt)
    {
      printf("child process id :%d  ppid: %d   cnt %d\n",getpid(),getppid(),cnt);
      cnt--;
      sleep(1);
    }
  }
  //parent
  int status=0;  //在waitpid中是低16位用来做输出型参数,有自己的位图结构
  pid_t pd=waitpid(id,&status,0);  //waipid函数进程等待,status是一个输出型参数
  if(id>0)
  {
  //sign number 是status的低7位,用来表示代码是否正常退出,0表示正常退出,其余的kill -l查看 
  //child exit code 是status的低16位的高8位,是自己自定义的错误退出码,根据退出码查询对应的错误
  printf("wait success %d,sign number:%d child exit code: %d   \n",pd,(status&0x7f),(status>>8)&0xff);
  }
  return 0;
}


//vim 的底行模式下的批量替换: %s/xxxx/替换字母/g

进程退出之后会变成僵尸--会把自己的退出结果写入到自己的PCB中,return的结果也是被放进到PCB里面,而上述代码提到的wait/waitpid是一个系统调用,OS也有资格去读取子进程的PCB,以及获取退出子进程PCB的进程退出码。系统的waipid有阻塞和非阻塞两种方式:

int ret = waitpid(id,&status,WNOHANG);//轮询检测

int ret = waitpid(id,&status,0); //阻塞检测

进程等待:通过系统调用让父进程等待子进程的一种方式,为了释放子进程僵尸以及获取子进程的进程退出码,方式是通过调用wait/waitpid的方式。

进程替换

创建子进程的目的不仅仅在于让子进程执行父进程代码的一部分,可以使用程序替换的方式去让子进程执行一个全新的程序,让子进程加载磁盘上的指定程序,执行新程序的代码和数据,这就是进程替换。简单的进程替换:

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
int  main()
{
  //.c----.exe ----load -----processs-->运行-->执行替换好的代码
  printf("process is running\n");
  //只要是一个函数,调用就可能失败,失败了之后就不会被覆盖
  //exec*为什么没有成功返回值呢?因为成功了就和接下来的代码,返回值毫无意义,所以exec*只要返回了一定是错误的
  execl("/usr/bin/ls"/*执行程序的位置*/, "ls","-a" , NULL/*你想要怎么执行*/);   //所有的exec* 程序必须都以NULL结尾
  perror(execl);

  printf("process is end\n");  //printf不会被执行,是因为被替换的代码覆盖了,无法被执行
  exit(1);

}

对于上述的函数

int execl(const char *path, const char *arg, ...);
//传入替换进程的位置,并如何执行该进程,后面可以加参数,
//其中 ...表示可变参数列表,在传参的最后exec*类别的函数
//都要以NULL结尾

 理解原理

 在程序替换之前,如黑线所示,父子进程页表映射的是同一块儿物理地址空间,在程序替换的时候,因为需要覆盖原来程序的代码和数据,此时发生写时拷贝,出现红色线条发生的一切,主要是为了不影响父进程的执行。虚拟地址空间+页表保证进程的独立性,一旦有执行流想替换代码或者数据的时候,就会发生写时拷贝。

使用系统程序覆盖子进程的程序

int  main()
{
  int id =fork();
  assert(id!=-1);
  if(id==0)
  {
    //子进程
    sleep(1);
    printf("child process\n");
    //execl("/usr/bin/ls","ls", "-a","-l","--color=auto",NULL);

    //execlp("ls","ls", "-a","-l","--color=auto",NULL);  //两个ls不重复,一个是告诉执行那个程序,一个是指令

    char *const arg_[]={
      "ls",
      "-a",
      "-l",
      "--color=auto",
      NULL
    };
    execv("/usr/bin/ls", arg_);

    exit(1);    //这里的代码在成功替换之后,是不会执行的
  }
  //父进程
 int status=0;
 pid_t ret = waitpid(id,&status,0);  //阻塞等待
 if(ret>0)
 {
   printf("wait sucess exit code:%d   sig:%d\n",(status>>8)&0xff,status&0x7f);
 }
   return 0;
 }





int execl(const char *path, const char *arg, ...); //l: list ,告诉程序的地址,并且如何执行,最后以NULL结尾


int execlp(const char *file, const char *arg, ...); //p:环境变量,只需告诉函数要执行程序的名称,会自己去环境变量中寻找,arg参数同上,最后以NULL结尾


int execv(const char *path, char *const argv[]);// v vcector,传入参/数列表即可,argv的参数必须是NULL结尾


int execvp(const char *file, char *const argv[]);//vector path均有


int execle(const char *path, const char *arg,..., char * const envp[]); //e:自定义环境变量


int execvpe(const char *file, char *const argv[],char *const envp[]);

除了可以调用系统的程序,还可以调用自己写的程序

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int  main()
{
  int id =fork();
  assert(id!=-1);
  if(id==0)
  {
    execl("./mybin","./mybin",NULL);
    exit(1);    //这里的代码在成功替换之后,是不会执行的
  }
  //父进程
 int status=0;
 pid_t ret = waitpid(id,&status,0);  //阻塞等待
 if(ret>0)
 {
   printf("wait sucess exit code:%d   sig:%d\n",(status>>8)&0xff,status&0x7f);
 }
   return 0;
 }

 不仅可以调用C语言,还可以调用C++,python,shell脚本等程序。

向子进程传入环境变量

int  main()
{
  int id =fork();
  assert(id!=-1);
  if(id==0)
  {

    extern char** environ;
    putenv((char*)"MYENV=4444424142121");  //将指定的环境变量导入到系统中,,environ指向的环境变量中
    execle("./mybin","./mybin",NULL,environ);  //实际上。默认的环境变量你不传,子进程也可以获取
    exit(1);    //这里的代码在成功替换之后,是不会执行的
  }
  //父进程
 int status=0;
 pid_t ret = waitpid(id,&status,0);  //阻塞等待
 if(ret>0)
 {
   printf("wait sucess exit code:%d   sig:%d\n",(status>>8)&0xff,status&0x7f);
 }
   return 0;
 }

 从argv中获取环境变量

int  main(int argc,char* argv[])
{
  int id =fork();
  assert(id!=-1);
  if(id==0)
  {
    //子进程
    sleep(1);
    printf("child process\n");
    execvp(argv[1],&argv[1]);    //这里argv[0]-->./sub  argv[1] =ls ....
    exit(1);    //这里的代码在成功替换之后,是不会执行的
  }
  //父进程
 int status=0;
 pid_t ret = waitpid(id,&status,0);  //阻塞等待
 if(ret>0)
 {
   printf("wait sucess exit code:%d   sig:%d\n",(status>>8)&0xff,status&0x7f);
 }
   return 0;
 }

上述的六个exec*函数都是基于execve这个系统调用接口封装的。

路径

每个进程都有自己的工作路径,在根路径下的/proc/pid -al 的

 其中cwd是当前进程的工作目录。

child("cwd");修改工作目录

之前写的shell,cd的时候,路径没有变化---->cd的时候,目录不改变的主要原因是子进程执行完cd命令之后,推出了,继续用的父进程工作路径。

fork()-->子进程执行的  cd->子进程有自己的工作目录 ->更改的是子进程的目录!-->子进程执行完毕-->继续用的父进程,即shell。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值