先考虑一个问题, main函数为什么要有返回值?返回值为什么是0?返回值给谁了?

🚩进程终止

进程退出场景:

  • 程序结束,结果正确
  • 程序结束,结果错误
  • 程序异常结束

怎么判断程序结束后正确与否?
🚩:用return返回值返回退出码

🚩退出码可以用来判断进程结束状态,0代表success,其他数字代表不同的错误,所以main函数返回0
🚩返给谁了?:进程的父进程

异常就是代码没跑完,所以它的退出码无意义,我们也不用关心,我们要去关注为什么异常

$?

linux指令,返回最近一次进程结束的退出码

int main() {
    printf("%s","hello\n");                                                                                                                            
    return 0;
 }

在这里插入图片描述
注意:$?会替换上次退出码,直接用相当于给了0指令,然后就找不到,
要想查看上次退出码,用 echo $?

int main()
{
      int a=10;
      a/=0;
      printf("%s\n",strerror(errno));
      char* mem=(char*)malloc(1000*1000*1000*4); 
      return 0;
}

在这里插入图片描述
136代表浮点数错误

int main()
{
      char* mem=(char*)malloc(1000*1000*1000*4); 
      if(mem==NULL)
      {
      printf("%d: %s\n",errno,strerror(errno));
      }
      return 0;
}

在这里插入图片描述
查看所有退出码信息

for(int i=0;i<=200;i++)
         {
             printf("%d:%s",i,strerror(i));
         }

我们也可以自己写退出码体系

const char* myerror[]={
         "success",                                                                        
         "error1",                                   
         "error2",                          
         "error3"  
   };

exit

作用:直接退出程序,返回退出码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
using namespace std;
void show()
{
    printf("hello\n");
    printf("hello\n");
    exit(6);
}

int main()
{
      show();
      exit(9);
      return 0;
}


在这里插入图片描述
在函数里也直接退出程序了,退出码是6而不是9

_exit

exit实际在执行时还会刷新缓冲区,再执行_exit(),缓冲区会再之后文件细讲
_exit()函数不刷新缓冲区,直接结束程序


#include <unistd.h>
 void exit(int status);
 int main()
 {
    printf("hello");
    exit(0);
 }
运行结果:
 [root@localhost linux]# ./a.out
 hello[root@localhost linux]#
 
int main()
 {
    printf("hello");
    _exit(0);
 }
运行结果:
 [root@localhost linux]# ./a.out
 [root@localhost linux]# 

在这里插入图片描述

🚩进程等待

子进程已结束而父进程还没回收子进程就会造成僵尸进程所以进程等待是必要的

进程等待:父进程通过wait,waitpid主动回收子进程资源,获取子进程退出码

wait

阻塞调用暂停父进程的动作,等待任意一个子进程结束

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);
// waitpid(-1,&status,0)

status结构:

31…16 15…8 7…0
保留 信号值 退出码

是一个32位图结构,我们只关注低16位,其中低8位存储退出码,高八位存储信号值
正常退出保存退出码,被信号杀死用信号值

如果不在乎怎么退出的,也可以传NULL,

#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
using namespace std;
void  Runchild()
{
      int cnt=3;
      while(cnt--)
      {
          printf("我是一个子进程:pid:%d ppid:%d\n",getpid(),getppid());
      }
      //exit(10);
}
int main()
{
    for(int i=0;i<10;i++)
    {
        pid_t id=fork();
        if(id==0)
        {
           Runchild();
           exit(i);
        }
        printf("我是父进程:pid:%d ppid:%d\n",getpid(),getppid());
        sleep(1);
    }
    sleep(5);
    for(int i=0;i<10;i++)
    {
        //pid_t id=wait(NULL);
        int status=0;
        pid_t id=waitpid(-1,&status,0);
        printf("wait %d success exit:%d\n",i,WEXITSTATUS(status));
    }
    return 23;
}

我是父进程:pid:3299 ppid:1974
我是一个子进程:pid:3300 ppid:3299
我是一个子进程:pid:3300 ppid:3299
我是一个子进程:pid:3300 ppid:3299
我是父进程:pid:3299 ppid:1974
我是一个子进程:pid:3301 ppid:3299
我是一个子进程:pid:3301 ppid:3299
我是一个子进程:pid:3301 ppid:3299
我是父进程:pid:3299 ppid:1974
我是一个子进程:pid:3302 ppid:3299
我是一个子进程:pid:3302 ppid:3299
我是一个子进程:pid:3302 ppid:3299
我是父进程:pid:3299 ppid:1974
我是一个子进程:pid:3303 ppid:3299
我是一个子进程:pid:3303 ppid:3299
我是一个子进程:pid:3303 ppid:3299
我是父进程:pid:3299 ppid:1974
我是一个子进程:pid:3304 ppid:3299
我是一个子进程:pid:3304 ppid:3299
我是一个子进程:pid:3304 ppid:3299
我是父进程:pid:3299 ppid:1974
我是一个子进程:pid:3305 ppid:3299
我是一个子进程:pid:3305 ppid:3299
我是一个子进程:pid:3305 ppid:3299
我是父进程:pid:3299 ppid:1974
我是一个子进程:pid:3306 ppid:3299
我是一个子进程:pid:3306 ppid:3299
我是一个子进程:pid:3306 ppid:3299
我是父进程:pid:3299 ppid:1974
我是一个子进程:pid:3307 ppid:3299
我是一个子进程:pid:3307 ppid:3299
我是一个子进程:pid:3307 ppid:3299
我是父进程:pid:3299 ppid:1974
我是一个子进程:pid:3308 ppid:3299
我是一个子进程:pid:3308 ppid:3299
我是一个子进程:pid:3308 ppid:3299
我是父进程:pid:3299 ppid:1974
我是一个子进程:pid:3309 ppid:3299
我是一个子进程:pid:3309 ppid:3299
我是一个子进程:pid:3309 ppid:3299
wait 0 success exit:0
wait 1 success exit:1
wait 2 success exit:2
wait 3 success exit:3
wait 4 success exit:4
wait 5 success exit:5
wait 6 success exit:6
wait 7 success exit:7
wait 8 success exit:8
wait 9 success exit:9

第一个for()循环结束子进程处于僵尸状态,第二for()将它们回收

🚩waitpid

非阻塞状态,也就是父进程在等待子进程时候可以做其他事情,不用特意停下来等待子进程结束

#include <sys/types.h>
#include <sys/wait.h>

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

返回值等于子进程pid,说明回收成功
返回值等于0,说明子进程没结束,父进程可以做自己事情

  • pid如果是-1,等待任意进程,和wait类似
  • 如果是0,等待与父进程同一进程组的子进程
  • pid如果是大于0,等待pid为该数的进程
    status和wait一样,
  • options如果是0,默认行为,阻塞
  • 如果是WNOHANG,如果没有适合的子进程回收,直接返回0
#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
using namespace std;
void  Runchild()
{
      int cnt=3;
      while(cnt--)
      {
          printf("我是一个子进程:pid:%d ppid:%d\n",getpid(),getppid());
          sleep(1);
      }
      //exit(10);
}
int main()
{
    pid_t id=fork();
    if(id<0)
    {
       perror("fork");
       return -1;
    }
    else if(id==0)
    {
       Runchild();  
    }
    else 
    {
       int status=0;
       while(1)//轮询
       {
         int ret=waitpid(id,&status,WNOHANG);
         if(ret>0)
         {
            if(WIFEXITED(status))
            {
                printf("程序正常 退出码:%d\n",WEXITSTATUS(status));
            }   
            else
            {
                printf("程序异常\n");
                break;
            }
 	   
         } 
         else if(ret==0)
         {
            printf("wait---\n");
            usleep(60000);
         }
 	       else
         {
  	      printf("waitfailed\n");
 	        break;
 	       }
       }
    }
    return 23;
}
wait---
我是一个子进程:pid:4118 ppid:4117
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
我是一个子进程:pid:4118 ppid:4117
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
我是一个子进程:pid:4118 ppid:4117
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
wait---
程序正常 退出码:23
waitfailed

🚩进程替换

进程调用exec系列函数会替换程序该进程的数据代码(物理地址)会被完全替换执行新程序调用exec不创建子进程,所以pid不变,环境变量也不变

exec系列有6个函数,

#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
  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量

execl
第一个参数要替换程序的路径,第二个参数程序名称,后续命令行参数,最后以NULL结尾

 #include <iostream>
 2 #include <unistd.h>
 3 #include <stdio.h>
 4 using namespace std;
 5 int main()
 6 {
 7   printf("hello world\n");
 8   execl("/bin/ls","ls","-a","-l",NULL);                                  
 9   printf("hello worldhhh\n");
10   return 0;
11 }

[jib@hcss-ecs-c6ba replace]$ ./replace
hello world
total 28
drwxrwxr-x 2 jib jib 4096 Nov 2 21:52 .
drwxrwxr-x 15 jib jib 4096 Nov 2 21:37 …
-rw-rw-r-- 1 jib jib 66 Nov 2 21:48 makefile
-rwxrwxr-x 1 jib jib 8760 Nov 2 21:52 replace
-rw-rw-r-- 1 jib jib 204 Nov 2 21:52 replace.cpp

可以发现替换函数后面printf没有打印,替换了 ls -a -l NULL

问题:
第一个参数“/bin/ls” , 我们怎么知道替换程序名称的路径呢?
所以有了execlp,第一个参数是程序名称,但目的是找到对应的路径

execlp(“ls”,“ls”,“-a”,“-l”,NULL);

在这里插入图片描述

接着:也可以用传字符串数组代替命令行参数

 #include <iostream>                                                    
 #include <unistd.h>
 #include <stdio.h>
 using namespace std;
 int main()
 {
      char *const argv[]={"ps","-ef","NULL"};
      printf("hello world\n");
      //execl("/bin/ls","ls","-a","-l",NULL);
      //execlp("ls","ls","-a","-l",NULL);
      execvp("ps",argv);
      printf("hello worldhhh\n");
      return 0;
 }

带e的函数需要自己组装环境变量

#include <iostream>
#include <unistd.h>
#include <stdio.h>
using namespace std;
int main()
{
  //char *const argv[]={"ps","-ef",NULL};
  char *const argv[]={"env",NULL};
  char *const envp[]={"PATH=/bin:/usr/bin","hhh=111",NULL};
  printf("hello world\n");
  //execl("/bin/ls","ls","-a","-l",NULL);
  //execlp("ls","ls","-a","-l",NULL);
  //execvp("ps",argv);
  execve("/usr/bin/env",argv,envp);
  printf("hello worldhhh\n");
  return 0;
}

[jib@hcss-ecs-c6ba replace]$ ./replace
hello world
PATH=/bin:/usr/bin
hhh=111

注意:环境变量是重置,不是添加

其实其他5个函数最终都会调用execvp,execvp是最终的系统调用
在这里插入图片描述

🚩实现myshell

有了以上基础,我们可以做myshell程序,通过程序替换来让系统调用我们输入的指令

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/wait.h>
using namespace std;
char commandline[1024];
char pwd[1024];
int quit=0;
char* argv[1024];
int LastNode=0;
char myenv[1024];
void getpwd()
{
  getcwd(pwd,sizeof(pwd));
}
void interact(char* _commandline,int size)
{
  getpwd();
  printf("[%s@%s %s]# ",getenv("USER"),getenv("HOME"),pwd);
  char* s=fgets(_commandline,size,stdin);
  assert(s);
  (void)s;
  _commandline[strlen(s)-1]='\0';
}
int splitstring(char* _commandline,char * _argv[])
{
  int i=0;
  argv[i++]=strtok(_commandline," \t");
  while(_argv[i++]=strtok(NULL," \t"));
  return i-1;
}
void NormolExcute(char* _argv[])
{
  pid_t id=fork();
  if(id<0)
  {
    perror("forkfail");
    return;
  }
  if(id==0)
  {
    execvp(_argv[0],_argv);
    exit(40);
  }
  if(id>0)
  {
     int status=0;
     pid_t ret=waitpid(id,&status,0);
     if(ret==id)
     {
       LastNode=WEXITSTATUS(status);
     }
  }

}
int BuildExcute(char* argv[],int argc)
{
  if(argc==2&&strcmp(argv[0],"cd")==0)
  {
    chdir(argv[1]);
    getpwd();
    sprintf(getenv("PWD"),"%s",pwd);
    //setenv("PWD", pwd, 1);
    return 1;
  }
  else if(argc==2&&strcmp(argv[0],"export")==0)
  {
    strcpy(myenv,argv[1]);
    putenv(myenv);
    return 1;
  }
  else if(argc==2&&strcmp(argv[0],"echo")==0)
  {
    if(strcmp(argv[1],"$?")==0)
    {
    printf("%d\n",LastNode);
    return 1;
    }
    else if(*argv[1]=='$')
    {
    char* str=getenv(argv[1]+1);
    if(str)
    printf("%s\n",str);
    return 1;
    }
    else
    {
    printf("%s\n",argv[1]);
    return 1;
    }
  }
  if(strcmp(argv[0],"ls")==0)
  {
    argv[argc++]="--color";
    argv[argc]=NULL;
    return 0;
  }
  return 0;
}
int main()
{
  while(!quit)
  {

  interact(commandline,sizeof(commandline));
  
  int argc=splitstring(commandline,argv);
  if(argc==0)
  {
    continue;
  }
  
  int n=BuildExcute(argv,argc);
  //for(int i=0;argv[i];i++)
  //{
  //  printf("%s\n",argv[i]);
  //}
  
  if(!n)
  NormolExcute(argv);
  }
  return 0;
}


分为4部分,第一部分输入指令,第二部分分割字符串,第三部分判断是否是内建命令,第4部分执行普通命令

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐