Linux系统编程 --- 进程控制

一、进程创建

        1、fork函数

        调用的时候,我们的fork函数会在操作系统内分配新的内存块和内核数据结构给子进程。将父进程部分数据结构内容拷贝至子进程。添加子进程到系统进程列表中。fork返回,开始调度器调度。

        2、fork之前和之后对比

        

        fork的时候程序由用户态转入内核态执行fork函数。执行完毕后返回用户态。

        3、子进程在修改和父进程共享的数据时会发生写时拷贝,下面时写时拷贝前后对应的页表和物理内存的变化

        

遗留问题为什么写数据的时候发生写时拷贝,而写代码的时候却不发生写时拷贝?

 那么要创建多个进程呢?来一段代码。

#include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #define N 5
  5 void ChildRun()
  6 {
  7   int cnt = 10;
  8   while(cnt--)
  9   {
 10 
 11      printf("I am ChildProcess pid:%d,ppid:%d\n",getpid(),getppid());
 12      sleep(1);
 13   }
 14 
 15 }
 16 int main(){
 17   //创建多个进程
 18   for(int i = 0;i < N;i++)
 19   {
 20     pid_t id = fork();
 21     if(id == 0)
 22     {                                                                                                                                                                                                        
 23       //子进程
 24       ChildRun();
 25       exit(0);
 26     }
 27     //父进程
 28   }
 29   sleep(1000);
 30   return 0;
 31 }

运行顺序不可知,完全由调度器决定。

二、进程终止

        1、为什么main函数总是会返回0,这个东西给谁了,为什么?(main函数的返回值)

        进程退出时的场景:代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止。这就相当于我们考试,考完成绩合格,考完成绩不合格,考试中发生某件事终止考试。第一种场景我们通常不关心,第二种场景程序员就会问为什么我们的进程跑完了没有得到预期结果呢?

        main函数中的return 0,是进程的退出码,表征进程的运行结果是否正确,0表示成功,可以被bash拿到。echo $ 查看退出码。

        一般而言我们的父进程关心子进程运行的情况,所以这个0返回给了父进程。可以用return的不同的返回值数字,来表征退出时的原因。

        那么父进程为什么要关心这个退出码呢?给用户提供方便。

        结论:main函数的返回值,本质表示进程运行完成时是否是正确的结果,如果不是,可以用不同的数字表示出错的原因。

$?:保存的是最近一次进程退出的时候的退出码。strerror函数可以把错误码转换为错误原因字符串。来的一段代码。

int main(){
   20   //创建多个进程
   21   //for(int i = 0;i < N;i++)
   22   //{
   23   //  pid_t id = fork();
   24   //  if(id == 0)
   25   //  {
   26   //    //子进程
   27   //    ChildRun();
   28   //    exit(0);
   29   //  }
   30   //  //父进程
   31   //}
   32   //sleep(1000);
W> 33   char* p = (char*)malloc(1000*1000*1000*4);
   34   if(p == NULL)
   35   {
   36     //errno为系统提供的错误码全局变量
   37     printf("malloc failed errno:%d,message:%s",errno,strerror(errno));
   38     return 1;
   39   }
   40   else{
   41     ;
   42   }
   43   return 0;
   44 }

系统提供的错误码和错误码描述是有对应关系的。我们可以自己设计一套退出码体系。来一段代码:

返回的的时候返回对应的下标就可以了。

C语言中会有一个errno全局变量。函数调用失败会设置errno错误码。

代码异常,进程的退出码就没有意义了,代码异常了代码可能就没有跑完。所以它就没有意义了。我们也就不关心退出码了。那么我们还要关心为什么异常了,以及这个进程发生了什么异常。

先关心是否异常,再关心代码的运行结果是否正确。

进程出现异常,本质是我们的进程收到了信号,证明我们可以用kill命令发送对应的信号

        2、终止进程的做法

        exit()函数在任意地方被调用,都表示调用进程直接退出,return 表示当前函数结束。

        和_exit()函数有什么区别呢?_exit()是纯正的系统调用,不会冲刷缓冲区,而exit()是C语言提供的一个库函数,它会执行它特有的工作,比如冲刷缓冲区等。

        

        我们printf一定是先把数据写入缓冲区中,合适的时候,再进行刷新!这个缓冲区绝对不会在Linux内核中。缓冲区在哪里基础IO会讲到。

三、进程等待

        1、是什么?

         通过系统调用wait/waitpid,来进行对子进程进行状态检测与回收的功能

        2、为什么?

        僵尸进程不可被杀死,只能通过等待来回收

        父进程派给子进程的任务完成的如何,我们需要知道,如果子进程运行完成,结果对还是不对,或者是否正常退出。

        我们僵尸进程无法被杀死,需要通过进程等待来杀掉它,进而解决内存泄漏问题。--- 必须解决

        通过进程等待,获得子进程的退出情况,知道我布置给子进程的任务,它完成的怎么样了。---要么关心,也可能不关心

        3、怎么办?

        原理:父进程通过调用wait/waitpid验证回收资源问题

        

        参数可以设为NULL。

        返回值 > 0等待成功,等待成功的子进程的pid

        wait是等待任意一个子进程的退出。

        代码:

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

int main()
{
  //创建子进程
  pid_t id = fork();
  if(id < 0)
  {
    perror("fork failed");
    return 1;
  }
  else if(id == 0)
  {
    //子进程
    int cnt = 5;
    while(cnt)
    {
      printf("我是子进程,pid:%d,ppid:%d\n",getpid(),getppid());
      sleep(1);
      cnt--;
    }
    exit(0);
  }
  else{
    //父进程
    int cnt = 5;
    while(cnt)
    {

      printf("我是父进程,pid:%d,ppid:%d\n",getpid(),getppid());
      sleep(1);
      cnt--;
    };
    //开始等待
    pid_t ret = wait(NULL);
    //等待成功
    if(ret == id)
    {
      printf("我是父进程,父进程等待成功child id:%d\n",id);
    }
    sleep(3);
  }
  return 0;
}

        

        我们有多个子进程呢?

        代码?

        

int main()
{
  for(int i = 0;i<10;i++)
  {
    pid_t id = fork();
    if(id == 0)
    {
      RunFunc();
      exit(1);
    }
  }
  sleep(10);
  //父进程等待
  //进行等待
  for(int i = 0;i<10;i++)
  {
    pid_t ret = wait(NULL);
    if(ret > 0)
    {
      //父进程等待
      printf("父进程等待成功的子进程为pid:%d\n",ret);
    }
  }
  return 0;
}

        wait当任意一个子进程退出的时候,wait回收子进程,如果任意一个子进程都不退出呢?

        如果子进程不退出,父进程默认在wait的时候,调用这个系统调用的时候,也就不返回,默认叫做阻塞状态。

        子进程的退出结果如何获取

        waitpid接口

        

         

       status参数是一个指针参数,1、这个参数为输出型参数,2、int是被当作几部分使用的。

 代码:

我们看到是256,那么我们子进程退出的时候有几种退出结果呢,父进程等待,期望获得子进程退出的哪些信息呢?

  有三种结果,就上上面进程退出的三种结果。

   关心哪些信息呢?1、子进程代码是否异常了,2、没有异常结果对吗,exitcode,不对因为上面呢,不同的退出码,表示不同的出错原因。

那么我们的status就需要划分区域

最低的7个bite为叫做异常终止时所对应的终止信号是什么。

次低8位代表退出状态。

00000000 00000000 00000001 00000000

2^8  = 256   

要得到这些数据就必须要进行位操作,与一个7F的十六进制数得到终止信号,右移8位按位与上0xFF

父进程要拿子进程的状态数据,为什么要用wait等系统调用呢?

由于进程的独立性,父进程不能看到status全局变量,就只能通过相应的系统调用。关键在于进程的独立性。

wait的原理是怎么样的

直接从子进程里的PCB里面拿到的,因为子进程退出的时候,代码和数据会被释放,但是我们的PCB还是存在的。所以可以直接拿到。用户是没有资格直接访问PCB的。所以必须调用系统调用接口。

那么进程什么时候会失败呢?那就是,子进程不是该父进程的子进程。父进程只能等待自己的子进程。

这里认识两个宏,WIFEXITED(status)判断是否出现异常

                             WEXITSTATUS(status)获取退出码。

代码:

int main()
{
  // 创建子进程
  pid_t id = fork();
  if (id < 0)
  {
    perror("fork failed");
    return 1;
  }
  else if (id == 0)
  {
    // 子进程
    int cnt = 5;
    while (cnt)
    {
      printf("我是子进程,pid:%d,ppid:%d\n", getpid(), getppid());
      sleep(1);
      cnt--;
    }
    exit(12);
  }
  else
  {
    // 父进程
    int cnt = 5;
    while (cnt)
    {

      printf("我是父进程,pid:%d,ppid:%d\n", getpid(), getppid());
      sleep(1);
      cnt--;
    };
    // 开始等待
    //子进程是否是异常退出子进程退出时状态
    int status = 0;
    //-1表示等待任意进程
    //阻塞等待
    pid_t ret = waitpid(-1,&status,0);
    // 等待成功
    if (ret > 0)
    {
      printf("我是父进程,父进程等待成功child id:%d,sgexit:%d,exitcode:%d\n",getpid(), status&0x7f,status>>8&0xff);
    }
    else if(ret < 0)
    {
      //父进程等待了不属于它的子进程。
      printf("等待不成功!\n");
    }
    sleep(3);
  }
  // 创建多个进程进行等待
  //  for(int i = 0;i<10;i++)
  //  {
  //    pid_t id = fork();
  //    if(id == 0)
  //    {
  //      RunFunc();
  //      exit(1);
  //    }
  //  }
  //  sleep(10);
  //  //父进程等待
  //  //进行等待
  //  for(int i = 0;i<10;i++)
  //  {
  //    pid_t ret = wait(NULL);
  //    if(ret > 0)
  //    {
  //      //父进程等待
  //      printf("父进程等待成功的子进程为pid:%d\n",ret);
  //    }
  //  }
  return 0;
}

加入宏后:

int main()
{
  // 创建子进程
  pid_t id = fork();
  if (id < 0)
  {
    perror("fork failed");
    return 1;
  }
  else if (id == 0)
  {
    // 子进程
    int cnt = 5;
    while (cnt)
    {
      printf("我是子进程,pid:%d,ppid:%d\n", getpid(), getppid());
      sleep(1);
      cnt--;
    }
    exit(12);
  }
  else
  {
    // 父进程
    int cnt = 5;
    while (cnt)
    {

      printf("我是父进程,pid:%d,ppid:%d\n", getpid(), getppid());
      sleep(1);
      cnt--;
    };
    // 开始等待
    //子进程是否是异常退出子进程退出时状态
    int status = 0;
    //-1表示等待任意进程
    //阻塞等待
    pid_t ret = waitpid(-1,&status,0);
    // 等待成功
    if (ret > 0)
    {
      //判断是否为异常退出
      if(WIFEXITED(status))
      {
        printf("进程是正常退出的,exitcode:%d\n",WEXITSTATUS(status));
      }
      else
      {
        printf("进程是异常退出的!\n");
      }
      //printf("我是父进程,父进程等待成功child id:%d,sgexit:%d,exitcode:%d\n",getpid(), status&0x7f,status>>8&0xff);
    }
    else if(ret < 0)
    {
      //父进程等待了不属于它的子进程。
      printf("等待不成功!\n");
    }
    sleep(3);
  }
  // 创建多个进程进行等待
  //  for(int i = 0;i<10;i++)
  //  {
  //    pid_t id = fork();
  //    if(id == 0)
  //    {
  //      RunFunc();
  //      exit(1);
  //    }
  //  }
  //  sleep(10);
  //  //父进程等待
  //  //进行等待
  //  for(int i = 0;i<10;i++)
  //  {
  //    pid_t ret = wait(NULL);
  //    if(ret > 0)
  //    {
  //      //父进程等待
  //      printf("父进程等待成功的子进程为pid:%d\n",ret);
  //    }
  //  }
  return 0;
}

第三个参数option是设置等方式的 0为阻塞等待。非阻塞轮询等待方式。

非阻塞轮询:

WNOHANG:非阻塞轮询+可以做自己的事情。

代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
void RunFunc()
{
  int cnt = 5;
  while (cnt)
  {
    printf("我是子进程:pid:%d,ppid:%d\n", getpid(), getppid());
    sleep(1);
    cnt--;
  }
}
int main()
{
  // 创建子进程
  pid_t id = fork();
  if (id < 0)
  {
    perror("fork failed");
    return 1;
  }
  else if (id == 0)
  {
    // 子进程
    int cnt = 5;
    while (cnt)
    {
      printf("我是子进程,pid:%d,ppid:%d\n", getpid(), getppid());
      sleep(1);
      cnt--;
    }
    exit(12);
  }
  else
  {
    // 父进程
    int cnt = 5;
    while (cnt)
    {

      printf("我是父进程,pid:%d,ppid:%d\n", getpid(), getppid());
      sleep(1);
      cnt--;
    };
    // 开始等待
    // 子进程是否是异常退出子进程退出时状态
    int status = 0;
    //-1表示等待任意进程
    // 阻塞等待
    // pid_t ret = waitpid(-1,&status,0);
    // 非阻塞轮询
    while (1)
    {
      pid_t ret = waitpid(-1, &status, WNOHANG);
      // 等待成功
      if (ret > 0)
      {
        // 判断是否为异常退出
        if (WIFEXITED(status))
        {
          printf("进程是正常退出的,exitcode:%d\n", WEXITSTATUS(status));
        }
        else
        {
          printf("进程是异常退出的!\n");
        }
        break;
        // printf("我是父进程,父进程等待成功child id:%d,sgexit:%d,exitcode:%d\n",getpid(), status&0x7f,status>>8&0xff);
      }
      else if (ret < 0)
      {
        // 父进程等待了不属于它的子进程。
        printf("等待不成功!\n");
        break;
      }
      else
      {
        // 非阻塞轮询
        printf("子进程还在运行中再等等!\n");
      }
    }
    //sleep(3);
  }
  // 创建多个进程进行等待
  //  for(int i = 0;i<10;i++)
  //  {
  //    pid_t id = fork();
  //    if(id == 0)
  //    {
  //      RunFunc();
  //      exit(1);
  //    }
  //  }
  //  sleep(10);
  //  //父进程等待
  //  //进行等待
  //  for(int i = 0;i<10;i++)
  //  {
  //    pid_t ret = wait(NULL);
  //    if(ret > 0)
  //    {
  //      //父进程等待
  //      printf("父进程等待成功的子进程为pid:%d\n",ret);
  //    }
  //  }
  return 0;
}

 

父进程一定要最后退出。

四、进程替换

1、单进程版 --- 最简单的程序替换

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
   printf("执行前pid:%d\n",getpid());
   execl("/usr/bin/ls","ls","-a","-l",NULL);                                                                                                                                                                  
   printf("执行后pid:%d\n",getpid());
}

2、进程替换的原理

3、多进程版 --- 验证各种程序替换接口

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
  // printf("执行前pid:%d\n",getpid());
  // execl("/usr/bin/ls","ls","-a","-l",NULL);
  // printf("执行后pid:%d\n",getpid());
  pid_t id = fork();
  if (id == 0)
  {
    printf("执行前pid:%d\n", getpid());
    execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
    printf("执行后pid:%d\n", getpid());
  }
  // 等待
  pid_t ret = waitpid(-1, NULL, 0);
  if (ret > 0)
  {
    printf("等待成功!\n");
  }
}

在操作系统看来,我们的程序也是可以发生写时拷贝的,在用户看来是不可以的。因为用户是受操作系统制约的,而程序替换是操作系统完成的,是操作系统自己修改内存中的程序和数据是可以发生写时拷贝的。父子进程发生写时拷贝,不是父子进程直接替换。

程序替换只进行进程的程序代码和数据的替换工作,不创建子进程。

补充:

        现象:程序替换成功之后,exec后续的代码不会被执行,替换失败才可能执行后续代码。exec系列的函数,只有失败返回值,没有成功返回值。

        小知识点:Linux中形成的可执行程序,是有格式的,ELF,有可执行程序的表头。可执行程序的入口地址。所以我们的进程就可以找到程序入口了。

程序替换的接口:

执行第一个程序的第一件事,是要找到这个程序

execlp 它会自己去PATH环境变量里寻找。

if (id == 0)
  {
    printf("执行前pid:%d\n", getpid());
    //execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
    execlp("ls","ls", "-a", "-l", NULL);
    printf("执行后pid:%d\n", getpid());
  }

execv v代表vector,第二个参数是一个字符串指针数组 

if (id == 0)
  {
    printf("执行前pid:%d\n", getpid());
    //execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
    //execlp("ls","ls", "-a", "-l", NULL);
    char* const my_argv[] =
    {
      "ls",
      "-a",
      "-l",
      NULL
    };
    execv("/usr/bin/ls",my_argv);
    printf("执行后pid:%d\n", getpid());
  }

execvp,v代表vector,第二个参数是一个字符串指针数组 ,它会自己去PATH环境变量里寻找。

 if (id == 0)
  {
    printf("执行前pid:%d\n", getpid());
    //execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
    //execlp("ls","ls", "-a", "-l", NULL);
    char* const my_argv[] =
    {
      "ls",
      "-a",
      "-l",
      NULL
    };
    //execv("/usr/bin/ls",my_argv);
    execvp("ls",my_argv);
    printf("执行后pid:%d\n", getpid());
  }

execle e代表环境变量

int main()
{
  extern char** environ;
  // printf("执行前pid:%d\n",getpid());
  // execl("/usr/bin/ls","ls","-a","-l",NULL);
  // printf("执行后pid:%d\n",getpid());
  putenv("PRIVITE = 123456");
  pid_t id = fork();
  if (id == 0)
  {
    printf("执行前pid:%d\n", getpid());
    //execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
    //execlp("ls","ls", "-a", "-l", NULL);
    char* const my_argv[] =
    {
      "ls",
      "-a",
      "-l",
      NULL
    };
    //execv("/usr/bin/ls",my_argv);
    //execvp("ls",my_argv);
    execle("/usr/bin/ls","ls","-a", "-l", NULL,environ);
    printf("执行后pid:%d\n", getpid());
  }
  // 等待
  pid_t ret = waitpid(-1, NULL, 0);
  if (ret > 0)
  {
    printf("等待成功!\n");
  }
}

execvpe。

int main()
{
  extern char** environ;
  // printf("执行前pid:%d\n",getpid());
  // execl("/usr/bin/ls","ls","-a","-l",NULL);
  // printf("执行后pid:%d\n",getpid());
  putenv("PRIVITE = 123456");
  pid_t id = fork();
  if (id == 0)
  {
    printf("执行前pid:%d\n", getpid());
    //execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
    //execlp("ls","ls", "-a", "-l", NULL);
    char* const my_argv[] =
    {
      "ls",
      "-a",
      "-l",
      NULL
    };
    //execv("/usr/bin/ls",my_argv);
    //execvp("ls",my_argv);
    //execle("/usr/bin/ls","ls","-a", "-l", NULL,environ);
    execvpe("ls",my_argv,environ);
    printf("执行后pid:%d\n", getpid());
  }
  // 等待
  pid_t ret = waitpid(-1, NULL, 0);
  if (ret > 0)
  {
    printf("等待成功!\n");
  }
}

传入命令行参数

也可以传入环境变量,没有传怎么会有的?

环境变量是什么时候传的?环境变量也是数据,创建子进程的时候,环境变量就已经被子进程继承下去了。 所以程序替换的时候,环境变量的信息不会被替换。我如果想给子进程传递环境变量,该怎么传递??

        1、新增环境变量 在自己父进程的地址空间中直接putenv()                

int main()
{
  extern char** environ;
  // printf("执行前pid:%d\n",getpid());
  // execl("/usr/bin/ls","ls","-a","-l",NULL);
  // printf("执行后pid:%d\n",getpid());
  putenv("PRIVITE = 123456");
  pid_t id = fork();
  if (id == 0)
  {
    printf("执行前pid:%d\n", getpid());
    //execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
    //execlp("ls","ls", "-a", "-l", NULL);
    char* const my_argv[] =
    {
      "ls",
      "-a",
      "-l",
      NULL
    };
    char*const myargv[] = {
            "otherExe",
            "-a",
            "-b",
            "-c",
            NULL
        };
    //execv("/usr/bin/ls",my_argv);
    //execvp("ls",my_argv);
    //execle("/usr/bin/ls","ls","-a", "-l", NULL,environ);
    //execvpe("ls",my_argv,environ);
    //execle("./Otherexe","Otherexe","-a", "-l", NULL,env);
    execle("./Otherexe","Otherexe","-a", "-l", NULL,environ);

    printf("执行后pid:%d\n", getpid());
  }
  // 等待
  pid_t ret = waitpid(-1, NULL, 0);
  if (ret > 0)
  {
    printf("等待成功!\n");
  }
}

自定义采用的是覆盖

 char*const myargv[] = {
            "otherExe",
            "-a",
            "-b",
            "-c",
            NULL
        };
    //execv("/usr/bin/ls",my_argv);
    //execvp("ls",my_argv);
    //execle("/usr/bin/ls","ls","-a", "-l", NULL,environ);
    //execvpe("ls",my_argv,environ);
    execle("./Otherexe","Otherexe","-a", "-l", NULL,env);
    printf("执行后pid:%d\n", getpid());

如果我们exec*能够执行系统命令,能不能执行我们自己的命令呢?可以的。

形成两个可执行程序。

无论是我们的可执行程序,还是脚本,为什么能跨语言调用呢?

那是因为所有的语言运行起来,本质都是进程。

int main()
{
  extern char** environ;
  // printf("执行前pid:%d\n",getpid());
  // execl("/usr/bin/ls","ls","-a","-l",NULL);
  // printf("执行后pid:%d\n",getpid());
  putenv("PRIVITE = 123456");
  pid_t id = fork();
  if (id == 0)
  {
    printf("执行前pid:%d\n", getpid());
    //execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
    //execlp("ls","ls", "-a", "-l", NULL);
    char* const my_argv[] =
    {
      "ls",
      "-a",
      "-l",
      NULL
    };
    char*const myargv[] = {
            "otherExe",
            "-a",
            "-b",
            "-c",
            NULL
        };
    //execv("/usr/bin/ls",my_argv);
    //execvp("ls",my_argv);
    //execle("/usr/bin/ls","ls","-a", "-l", NULL,environ);
    //execvpe("ls",my_argv,environ);
    //execle("./Otherexe","Otherexe","-a", "-l", NULL,env);
    execle("./Otherexe","Otherexe","-a", "-l", NULL,environ);

    printf("执行后pid:%d\n", getpid());
  }
  // 等待
  pid_t ret = waitpid(-1, NULL, 0);
  if (ret > 0)
  {
    printf("等待成功!\n");
  }
}

找到这个程序,如何执行这个程序,主要是要不要涵盖选项,涵盖哪些。命令行怎么写,我们就怎么传。

exec系列函数,是加载器。

4、总结

execve函数是Linux提供的系统调用,上面是C语言的库函数。它们是通过封装execve函数得来的。

5、自定义shell(50行代码)

     shell外壳程序,shell/bash也是一个进程,执行指令的时候本质就是自己创建子进程执行的。

   代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define EXIT_CODE 4
char pwd[LINE_SIZE];
char commandline[LINE_SIZE];
char *argv[ARGC_SIZE];
//自定义环境变量表
char myenv[LINE_SIZE];
//子进程的退出码
int lastcode = 0;
// 得到用户名,通过环境变量
const char *getusername()
{
    return getenv("USER");
}
// 得到主机名,通过环境变量
const char *gethostname1()
{
    return getenv("HOSTNAME");
}
// 得到当前路径
void getpwd()
{
    getcwd(pwd, sizeof(pwd));
}
// 命令行交互
void interact(char *cline, int size)
{
    getpwd();
    printf(LEFT "%s@%s %s" RIGHT "" LABLE " ", getusername(), gethostname1(), pwd);
    char *s = fgets(cline, size, stdin);
    // 在debug版本下才会起作用
    assert(s);
    // 避免一些编译器错误
    (void *)s;
    // 去除回车所以要-1
    cline[strlen(s) - 1] = '\0';
}
int splitstring(char *cline, char *_argv[])
{
    int i = 0;
    _argv[i++] = strtok(cline, DELIM);
    while (_argv[i++] = strtok(NULL, DELIM))
        ;
    // 返回数组大小
    return i - 1;
}
//普通指令的执行
void NormalExcute(char* _argv[])
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork failed\n");
        return;
    }
    else if(id == 0)
    {
        //执行指令
        //程序替换执行
        execvp(_argv[0],_argv);
        exit(EXIT_CODE);
    }
    else
    {
        int status = 0;
        pid_t rid = waitpid(id,&status,0);
        //等待成功
        if(rid == id)
        {
            lastcode = WEXITSTATUS(status);
        }
    }
}
//内建命令的执行
int buildcommand(char* _argv[],int _argc)
{
    if(_argc == 2 && strcmp(_argv[0],"cd") == 0)
    {
        chdir(_argv[1]);
        getpwd();
        sprintf(getenv("PWD"),"%s",pwd);
        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",lastcode);
            lastcode = 0;
        }
        else if(*_argv[1] == '$')
        {
            char* val = getenv(_argv[1] + 1);
            if(val) printf("%s\n",val);
        }
        else
        {
            printf("%s\n",_argv[1]);
        }
        return 1;
    }
    //特殊处理ls
    if(strcmp(_argv[0],"ls") == 0)
    {
        _argv[_argc++] = "--color";
        _argv[_argc] = NULL;
    }
    return 0;
}
int main()
{
    // 2、交互问题
    while (1)
    {
        interact(commandline, sizeof(commandline));
        // 3、字符串分割问题,解析命令行
        int argc = splitstring(commandline, argv);
        if (argc == 0)
            continue;
        // 4、指令的判断
        // 5、执行指令
        // (1)内建命令
        int n = buildcommand(argv,argc);
        // (2)普通命令
        if(!n) NormalExcute(argv);
    }
    return 0;
}

 所以,当我们进行登陆的时候,系统就是要启动一个shell进程。当用户登录的时候,shell会读取用户目录下的.bash_profile文件,里面保存了导入环境变量的方式!

  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值