Linux_地址空间_进程控制_进程创建_进程终止_进程等待_进程替换_简易shell_4


一、程序地址空间

在这里插入图片描述

程序地址空间,不是内存,就是进程地址空间,是操作系统上的概念。

1.地址空间验证

#include<stdio.h>
#include<stdlib.h>
int a;
int b = 10;
int main(int agrc ,char* agrv,char* env[])
{
  printf("code addr          :%p\n",main);//正文代码地址
  printf("init global addr   :%p\n",&b);//初始化变量地址
  printf("uninit global addr :%p\n",&a);//未初始化变量地址
  char* c =(char*) malloc(100);
  printf("heap addr          :%p\n",c);//堆地址
  printf("stack addr         :%p\n",&c);//栈地址
  for(int n = 0;env[n] ;n++)
  {
  printf("env[%d]            :%p\n",n,env[n]);//环境变量地址
  }

  return 0;
}

在这里插入图片描述
由此可以验证,进程地址是由正文代码,初始化变量,未初始化变量,堆,栈,环境变量依次增大的,而且在堆和栈之间有非常大的镂空。

2.验证堆和栈的增长方向

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

int main(int agrc ,char* agrv,char* env[])
{
  char* a1 =(char*) malloc(100);
  char* a2 =(char*) malloc(100);
  char* a3 =(char*) malloc(100);

  printf("heap addr          :%p\n",a1);//堆地址
  printf("heap addr          :%p\n",a2);//堆地址
  printf("heap addr          :%p\n",a3);//堆地址
  printf("stack addr         :%p\n",&a1);//栈地址
  printf("stack addr         :%p\n",&a2);//栈地址
  printf("stack addr         :%p\n",&a3);//栈地址

  return 0;
}

在这里插入图片描述
由此可以验证,堆区向上增长,栈区向下增长,堆栈相对而生。我们一般在C函数定义的变量,通常在栈上保存,那么先定义的一定是地址比较高的。

如何理解static变量?
函数内定义的变量用static修饰,本质是编译器会把该变量编译进全局数据区。

3.感知地址空间

下面代码内容是:设一个全局变量,fork创建个进程,分别在子进程和父进程查看变量和变量地址,2秒后子进程修改全局变量,看看变量和变量地址有和变化。

#include<stdio.h>
#include<unistd.h>
int g_val = 100;
int main()
{
    pid_t id = fork();
    int flg = 0;
    if(id==0)
    {
        while(1)
        {
            flg++;
            printf("我是子进程,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n\n",getpid(),getppid(),g_val,&g_val);
            sleep(1);
            if(flg==5)
            {
                printf("我是子进程,全局数据我已经改了,用户你注意查看!");
                g_val =200;
            }
        }
    }
    else
    {
        while(1)
        {
            printf("我是父进程,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n\n",getpid(),getppid(),g_val,&g_val);
            sleep(1);
        }
    }
    return 0;
}

在这里插入图片描述
父子进程读取同一个变量(因为地址一样),但是后续没有人修改的情况下,父子进程读取到的内容却不一样!!!

上面现象可以得出结论:
我们在C/C++中使用的地址,绝对不是物理地址!!!,如果是物理地址上述情况是不可能产生的!

那这个地址是什么?
虚拟地址,线性地址,逻辑地址。

为什么我的操作系统不让我直接看到物理内存呢?
内存就是一个硬件,不能阻拦你访问!只能被动的进行读取和写入。

每一个进程在启动的时候,都会让操作系统给他创建一个地址空间,该地址空间就是进程地址空间,操作系统就需要管理进程地址空间,管理就需要先描述再组织,进程地址空间,其实就是内核的一个数据结构,struct mm_struct

4.什么是地址空间

上面说过,进程具有独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰
所谓的地址空间,其实就是OS通过软件的方式,给进程提供一个软件视角,认为自己会独占系统的所有资源(内存)
在这里插入图片描述
页表是将程序加载到内存由程序变成进程之后由操作系统会给每一个进程构建一个页表结构。
通过页表,将父子进程的数据就可以通过写时拷贝的方式,进行了分离。

fork有两个返回值,pid_t id,同一个变量,怎么会有不同的值?
pid_t id是属于父进程栈空间中定义的变量,fork内部,return 会被执行两次,return的本质,就是通过寄存器将返回值写入接受返回值的变量中,当id =fork()的时候,谁先返回,谁就要发生写时拷贝,所以,同一个变量,会有不同的内容值,本质是因为大家虚拟地址是一样的,但是大家对应的物理地址是不一样的!

为什么要有虚拟地址空间?
直接让进程访问物理地址是不安全的,访问内存添加了一层软硬件层,可以对转化过程进行审核,非法的访问,就可以直接拦截了。
1.保护内存
2.进程管理,通过地址空间,进行功能模块的解耦
3.让进程或者程序可以以一种统一的视角看待内存

二、进程控制

1.进程创建

#include <unistd.h>
pid_t fork(void);
返回值:自进程中返回0,父进程返回子进程id,出错返回-1

#include<stdio.h>
#include<unistd.h>
int main()
{
    printf("我是进程,我的pid:%d\n",getpid());
    fork();
    printf("我依旧是进程,我的pid:%d\n",getpid());

    return 0;
}

在这里插入图片描述
fork之前父亲独立执行,fork之后,父子两个执行分别执行,一般情况,父子共享所有代码和数据,当一方试图写入,便以写时拷贝的方式各自一份。
在这里插入图片描述

注意,fork之后,谁先执行完全由调度器决定,子进程执行的后续代码!=共享的所有代码,只不过子进程只能从这里开始执行。

fork之后,操作系统做了什么?

进程 = 内核进程数据结构 + 进程的代码和数据

创建子进程的内核数据结构(struct task_struct + struct mm_struct + 页表)+代码继承父进程,数据以写时拷贝的方式,来进行共享。 由此保证进程的独立性。

为什么要写时拷贝?
创建子进程的时候,就把数据分开,不行吗?
1.父进程的数据,子进程不一定全用,即使使用,也不一定全部写入,会有浪费空间的嫌疑。
2.最理想的情况,只有会被父子修改的数据,进行分离拷贝,不需要修改的共享即可,但从技术上实现复杂。
3.如果fork的时候,就无脑拷贝数据给子进程,会增加for的成本(内存和时间)
所以最终采取写时拷贝,只会拷贝父子修改的,变相的,就是拷贝数据的最小成本,拷贝的成本依旧存在,这种叫做延迟拷贝策略,只有真正使用的时候才给,变相的提高了内存使用率。

fork常规用法
a.一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
b.一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

fork调用失败的原因
a.系统中有太多的进程
b.实际用户的进程数超过了限制

2.进程终止

常见进程退出:
1.代码跑完,结果正确
2.代码跑完,结构不正确
3.代码没跑玩,程序异常了

1、关于终止的正确认识:

main是一个程序入口函数,return ?,return 0;
a.return 0 是给谁返回的
b.为何是0?其他值可以吗?
进程代码跑完,结果是否正确,0:成功,非零:失败。如果失败,最想知道是,失败的原因,所以,非零标识不同的原因。

把main还是返回值,叫做进程退出码,表征进程退出的信息,给父进程读取的。

我将程序退出码写成return 123 命令行输入:

echo $?

在这里插入图片描述

在bash中,最近一次执行完毕时,对应进程的退出码!

2、关于终止常见做法

1.在main还是中return。
为什么其他函数不行?
main函数return代表进程退出,非main函数代表函数调用结束。
2.在自己的代码任意地点中,调用exit。
3.调用_exit
4.ctrl+c,信号终止

exit和_exit区别:

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

int main()
{
    printf("hello world");
    sleep(1);
    //_exit(1);
     exit(2);
    return 123;
}

在这里插入图片描述
在这里插入图片描述
exit终止进程,刷新缓冲区
_exit直接终止进程,不会有任何刷新操作

3、关于终止,内核做了什么?

进程 = 内核结构 + 进程代码和数据

task_struct 和mm_structc:
操作系统可能不会释放该进程的内核数据结构,会把他们放到内核数据结构缓冲池,当有新进程,对于进程数据初始化,减少了开辟空间的过程。

3.进程等待

1、为什么要进行进程等待

1.子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
2.父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
3.父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

2、进程等待 - wait - waitpid (系统接口)

pid_t wait(int *status):等待任意一个退出的子进程

int ret  = wait(NULL);

返回值(pid_t):
>0:等待子进程成功,返回值就是子进程的pid
<0:等待失败,当前没有子进程。


pid_t waitpid(pid_t pid, int *status, int options):
参数pid:
a.等待特定进程,pid参数填:特定进程的pid
b.等待任意进程pid参数填 -1

参数options:
0:表示阻塞等待
WNOHANG:非阻塞等待

参数status :
这个参数,是一个输出型参数,由操作系统填充。
如果传NULL表示不关心退出状态。
否则,操作系统会根据参数,将子进程的退出状态(退出码)反馈给父进程。
虽然返回的是整形,但要当做位图来看,细节如下:
在这里插入图片描述
正常终止:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
    pid_t id = fork();
    if(id==0)
    {
      int cnt  = 5;
        while(cnt)
        {
            printf("我是子进程,我还能活%ds,pid:%d\n",cnt--,getpid());
            sleep(1);
        }        

        printf("我准备退出了,我的退出码是123\n");
        exit(123);
    }
    else
    {
        int status;
        printf("我是父进程进程,我开始等待了:pid:%d\n",getpid());
        pid_t ret = waitpid(id,&status,0);
        if(ret<0)
        {
            printf("等待失败!\n");
        }
        else
        {
            printf("我是父进程等待成功,pid:%d,ret:%d,status:%d\n",getpid(),ret,(status>>8)&0xFF);
        }

    }
    return 0;
}

在这里插入图片描述

下面演示异常终止,发送信号终止进程:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
    pid_t id = fork();
    if(id==0)
    {

        while(1)
        {
            printf("我是子进程,我赖着不走了,除非你拿信号杀了我,pid:%d\n",getpid());
            sleep(1);
        }        

        printf("我准备退出了,我的退出码是123\n");
        exit(123);
    }
    else
    {
        int status;
        printf("我是父进程进程,我开始等待了:pid:%d\n",getpid());
        pid_t ret = waitpid(id,&status,0);
        if(ret<0)
        {
            printf("等待失败!\n");
        }
        else
        {
            printf("我是父进程等待成功,pid:%d,ret:%d,status:%d,退出信号:%d\n",getpid(),ret,(status>>8)&0xFF,status&0x7F);
        }

    }
    return 0;
}

在这里插入图片描述
注:一旦进程出现异常,只关注退出信号,退出码没有任何意义。

WEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)

WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

对于status可以用系统定义宏来判断,子进程是否正常退出,还是异常退出。

代码如下:

        int status;
        pid_t ret = waitpid(id,&status,0);
        if(ret<0)
        {
            //等待失败
        }
        else
        {
            //等待成功
            if(WIFEXITED(status))
            {
                //正常退出
                printf("进程退出码:%d\n",WEXITSTATUS(status));
            }
            else
            {
                //异常退出
            }
        }

在这里插入图片描述


当waitpid参数options填WNOHANG为非阻塞等待:
下面只写父进程代码

        while(1)
        {
            //父进程
            //基于非阻塞的轮询等待方案
            int status = 0;
            pid_t ret = waitpid(-1,&status,WNOHANG);
            if(ret>0)
            {
                printf("等待成功,%d,exit sig:%d,exit code:%d\n",ret,status&0x7F,(status>>8)&0XFF);
            }
            else if(ret ==0)
            {
                //等待成功,但是子进程没有退出
                printf("进程好了没,没有!那我干别的事了。父进程处理其他事情....\n");
                sleep(1);
            }
            else
            {
                //等待失败,暂时不处理。
            }

        }

在这里插入图片描述
父子进程都在运行,父进程不会因为等待而进入阻塞。

4.进程替换 - execve

1.进程替换是什么

子进程执行的是父进程的代码片段,如果我们想让创建出来的子进程,执行全新的程序?
进程替换,就可以实现。

2.为什么要进程替换

我们一般在服务器设计(Linux编程)的时候,往往需要子进程干两件种类的事情
1.让子进程执行父进程的代码片段(服务器代码)
2.让子进程执行磁盘中的一个全新的程序(shell,想让客户端执行对应的程序,通过我们的进程,执行其他人写的进程代码等待),c/c+±>c/c++/Python/shell/PHP/Java

程序替换的原理:
1.将磁盘中的程序,加载入内存结构
2.重新建立页表映射,谁执行程序替换,就重新建立谁的映射(子进程)
效果:让我们的父进程和子进程彻底分离,并让子进程执行一个全新的程序!

3.如何进程替换

1、见见猪跑———最基本的代码

在这里插入图片描述

int execl(const char *path, const char *arg, …);

我们如果想要执行一个全新的程序,我们需要做几件事:
1.先找到这个程序在哪里?——程序在哪
2.程序可能携带选项进行执行(也可以不携带)——怎么执行

:可变参数

参数 path
程序路径

参数 ** const char *arg, …**:
命令行怎么写(ls -a -l),这个参数就怎么填“ls”,“-l” “-a”,最后必须是NULL,标识【如何执行程序的】参数传递完毕

下面用自己写程序调用,系统写的程序 ls
第一步:
知道程序位置,使用which命令查找ls程序的位置。

在这里插入图片描述
第二步:
如何执行程序,ls -l -a 最后再加上NULL

#include<stdio.h>
#include<unistd.h>
int main()
{
  printf("我是进程%d\n",getpid());
  execl("/usr/bin/ls","ls","-l","-a",NULL);
  printf("我是个进程,我执行完毕,pid:%d\n",getpid());
  return 0;
}

在这里插入图片描述
观察上面代码,发现执行进程替换,execl后续的代码没有执行。为什么没有执行?
一旦替换成功,是将当前进程的代码和数据全部替换了,后面的printf也是代码,当然也就被替换掉了,这段代码就不存在了也就没有执行了!

程序替换用不用判断返回值?为什么?
不用判断返回值,因为只要成功了,就不会有返回值,而失败的时候,必然会继续向后执行!!最多通过返回值得到什么原因导致的替换失败!

2、引入进程创建
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
  printf("我是进程%d\n",getpid());
  pid_t id = fork();
  if(id==0)
  {
    //child
    //执行其他程序
    execl("/usr/bin/ls","ls","-a","-l",NULL);
    //只要执行exit,意味着execl的函数失败了
    exit(1);
  }
  int status = 0;
  int ret = waitpid(id,&status,0);
  if(ret>0)
  {
    printf("我是父进程,等待成功,子进程pid:%d,exit sig:%d,exit code:%d\n",id,status&0x7F,(status>>8)&0xFF);
  }
  else 
  {
    printf("我是父进程,等待失败!\n");
  }
  return 0;
}

在这里插入图片描述
子进程执行程序替换,会不会影响父进程?
不会(进程具有独立性)

为什么,如何做到的?
数据层面发生写时拷贝!当程序替换的时候,我们可以理解成为,代码和数据都发生了写时拷贝完成了父子的分离!

3、大量测试各种不同的接口

int execv(const char *path, char *const argv[]);

path:程序路径
argv:和最开始演示一样,不过把命令放入数组里。

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

int main()
{
  printf("我是进程%d\n",getpid());
  pid_t id = fork();
  if(id==0)
  {
    //child
    //执行其他程序
    char* const argv[] = {"ls","-a","-l",NULL};
    execv("/usr/bin/ls",argv);
    //只要执行exit,意味着execl的函数失败了
    exit(1);
  }
  int status = 0;
  int ret = waitpid(id,&status,0);
  if(ret>0)
  {
    printf("我是父进程,等待成功,子进程pid:%d,exit sig:%d,exit code:%d\n",id,status&0x7F,(status>>8)&0xFF);
  }
  else 
  {
    printf("我是父进程,等待失败!\n");
  }
  return 0;
}

在这里插入图片描述


int execlp(const char *file, const char *arg, …)

第一个参数写是是你想要执行的程序,执行指令的时候,在默认的搜索路径查找,也就是环境变量PAHT中查找,进程替换函数系列,命名带p的,可以不带路径,只传你要执行的哪一个程序即可(前提在PATH路径下可以找到,不清楚看我的环境变量文章)。

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
  printf("我是进程%d\n",getpid());
  pid_t id = fork();
  if(id==0)
  {
    //child
    //执行其他程序
    execlp("ls","ls","-a","-l",NULL);
    //只要执行exit,意味着execl的函数失败了
    exit(1);
  }
  int status = 0;
  int ret = waitpid(id,&status,0);
  if(ret>0)
  {
    printf("我是父进程,等待成功,子进程pid:%d,exit sig:%d,exit code:%d\n",id,status&0x7F,(status>>8)&0xFF);
  }
  else 
  {
    printf("我是父进程,等待失败!\n");
  }
  return 0;
}

在这里插入图片描述

请问这里有两个“ls”可以省略吗?含义一样吗?
不可以,含义不一样,前面的是你要执行的程序,后面是告诉程序如何执行。


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

v:就是用数组传如何执行 ,例如: char *const argv[] = {“ls”,“-a”,“-l”,NULL};
p:要执行程序不用带路径,在PATH中查找,直接传程序名例如 :“ls”

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
  printf("我是进程%d\n",getpid());
  pid_t id = fork();
  if(id==0)
  {
    //child
    //执行其他程序
    char* const argv[] = {"ls","-a","-l",NULL};
    execvp("ls",argv);
    //只要执行exit,意味着execl的函数失败了
    exit(1);
  }
  int status = 0;
  int ret = waitpid(id,&status,0);
  if(ret>0)
  {
    printf("我是父进程,等待成功,子进程pid:%d,exit sig:%d,exit code:%d\n",id,status&0x7F,(status>>8)&0xFF);
  }
  else 
  {
    printf("我是父进程,等待失败!\n");
  }
  return 0;
}

在这里插入图片描述


int execle(const char *path, const char *arg, …, char * const envp[]);

l:罗列如何执行,例如:“ls”,“-a”,“-l”
e:传环境变量
myexec.c

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
  printf("我是进程%d\n",getpid());
  pid_t id = fork();
  if(id==0)
  {
    //child
    //执行其他程序
    char* const myenv[] = {
      "MYPATH=自定义环境变量",
      NULL
    };
    execle("./mycmd","mycmd",NULL,myenv);
    
    //或者也可以传父进程的环境变量
    // extern char**environ;
    // execle("./mycmd","mycmd",NULL,environ);


    //只要执行exit,意味着execl的函数失败了
    exit(1);
  }
  int status = 0;
  int ret = waitpid(id,&status,0);
  if(ret>0)
  {
    printf("我是父进程,等待成功,子进程pid:%d,exit sig:%d,exit code:%d\n",id,status&0x7F,(status>>8)&0xFF);
  }
  else 
  {
    printf("我是父进程,等待失败!\n");
  }
  return 0;
}

mycmd.cpp

#include<iostream>
#include <stdlib.h>
using namespace std;
int main()
{
    cout<<"MYPATH="<<getenv("MYPATH")<<endl;
    return 0;
}

在这里插入图片描述
可以选择传自己定义的环境变量,也可以传父进程的环境变量。


总结

l(list) :表示参数采用列表
v(vector):参数用数组
p(path):有p自动搜索环境变量PATH
e(env):表示自己维护环境变量
在这里插入图片描述
以上是对系统接口的封装
在这里插入图片描述但execve为什么是单独?
因为它是系统接口!

4、替换自己的程序

mycdm.c:

#include<iostream>
using namespace std;
int main()
{
    cout<<"hello C++"<<endl;
    return 0;
}

myexec.c:

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
  printf("我是进程%d\n",getpid());
  pid_t id = fork();
  if(id==0)
  {
    //child
    //执行其他程序
    char* const argv[] = {"ls","-a","-l",NULL};
    execvp("ls",argv);
    //只要执行exit,意味着execl的函数失败了
    exit(1);
  }
  int status = 0;
  int ret = waitpid(id,&status,0);
  if(ret>0)
  {
    printf("我是父进程,等待成功,子进程pid:%d,exit sig:%d,exit code:%d\n",id,status&0x7F,(status>>8)&0xFF);
  }
  else 
  {
    printf("我是父进程,等待失败!\n");
  }
  return 0;
}

在这里插入图片描述

三、简易shell

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<assert.h>
#include<ctype.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define NUM 1024
#define SIZE 128
#define SEP " "

#define DROP_SPACE(s) do{while(isspace(*s))s++;}while(0)

char command_line[NUM];
char* command_args[SIZE];

#define NONE_REDIR -1
#define INPUT_REDIR 0
#define OUTPUT_REDIR 1
#define APPEND_REDIR 2

int g_redir_flag = NONE_REDIR;
char *g_redir_filename = NULL;

void CheckDir(char* commands)
{
    assert(command_line);
    //[start,end)
    char* start  =commands;
    char* end = commands+strlen(commands);
    // ls -a -l>log.txt
    while(start <end)
    {
        if(*start=='>')
        {
            if(*(start+1)=='>') 
            {
                //ls -a -l>>log.txt
                *start='\0';
                start+=2;
                g_redir_flag = APPEND_REDIR;
                DROP_SPACE(start);
                g_redir_filename =start;
                break;
            }
            else
            {
                //ls -a -l>log.txt
                *start = '\0';
                start++;
                g_redir_flag = OUTPUT_REDIR;
                DROP_SPACE(start);
                g_redir_filename =start;
                break;
            }
        }
        else if(*start=='<')
        {
            //输入重定向
            *start = '\0';
            start++;
            g_redir_flag = INPUT_REDIR;
            DROP_SPACE(start);
            g_redir_filename =start;
            break;           
        }
        else
        {
            start++;
        }
    }
}
int main()
{
    while(1)
    {
        g_redir_flag = NONE_REDIR;
        g_redir_filename =NULL;
        //1.显示提示符
        printf("[张三@我的主机名 当前目录]#");
        fflush(stdout);
        //2.获取用户输入
        memset(command_line,'\0',sizeof(command_line));
        fgets(command_line,NUM,stdin);
        //去掉回车
        command_line[strlen(command_line)-1] = '\0';
        //2.1 ls -a -l>log.txt or cat<file.txt or ls -a -l>>log.txt or ls-a -l
        //ls -a -l>log.txt ->  ls -a -l\0log.txt
        CheckDir(command_line);
        //3.切割字符串将"ls -a -l"切割成"ls","-a","-l"
        command_args[0]= strtok(command_line,SEP);
        int index = 1;
        //给ls添加颜色
        if(strcmp(command_args[0],"ls")==0)
        {
            command_args[index++] = "--color=auto";
        }

        while(command_args[index++]=strtok(NULL,SEP));
        if(strcmp(command_args[0],"cd")==0 && command_args[1]!=NULL)
        {
            chdir(command_args[1]);
            continue;
        }

        
        // //检查打印
        // int cnt = 0;
        // while(command_args[cnt]!=NULL)
        // {
        //     printf("%s\n",command_args[cnt++]);
        // }
        // printf(g_redir_filename);

        //5.创建子进程,让子进程执行进程替换,执行要执行的程序
        pid_t id = fork();
        if(id==0)
        {
            int fd = -1;
            switch(g_redir_flag)
            {
                case NONE_REDIR:
                break;
                case INPUT_REDIR:
                {
                    fd=open(g_redir_filename,O_RDONLY);
                    dup2(fd,0);
                    break;
                }
                case OUTPUT_REDIR:
                {
                    // printf("开始重定向\n");
                    // printf("filename: %s\n",g_redir_filename);
                    fd = open(g_redir_filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
                    dup2(fd,1);
                    break;
                }
                case APPEND_REDIR:
                {
                    fd = open(g_redir_filename,O_WRONLY|O_CREAT|O_APPEND,0666);
                    dup2(fd,1);
                    break;
                }
                default:
                printf("bug?\n");
                break;
            }      
            if(fd!=-1)
            {
                close(fd);
            }      
            //6.进程替换
            execvp(command_args[0],command_args);
            exit(1);
        }
        
        //获取进程替换的返回值
        int status  = 0;
        int ret = waitpid(id,&status,0);
        if(ret>0)
        {
            if(status&0x7F!=0||(status>>8)&0xFF!=0)
            {
                printf("执行失败\n");
            }
        }
        else
        {
            printf("等待失败!\n");
        }

 
    }
    return 0;
}

细节1,关于ls系统带颜色,我们的不带颜色:
使用which命令查询 ls 会发现,请看下图:
在这里插入图片描述
alias是一个命令,叫做起别名,平常我在命令行打出的 ls,运行的其实是ls --color=auto,如果直接调用 ls 也是不带颜色的,验证如下:
在这里插入图片描述
所以我们写的简易shell运行 ls 不带颜色的原因。当获取到要执行的命令,可以先判断是不是 ls 如果个是,就添加一个命令 “–color=auto”。

细节2,关于cd无法更改工作目录:
如果直接exec*执行cd,最多,只能让子进程进行路径切换,子进程是一运行就完毕的进程!我们在shell中,更希望父进程也就是shell本身执行,如果有些行为必须让父进程shell执行的,不想让子进程执行,只能是父进程自己实现对应的代码,由shell自己执行的命令,我们称之为内建命令(内置bind-in)。

注意:重定向也就是(>,>>,<)要看下一个章节才能完成。

最近更新

  1. TCP协议是安全的吗?

    2024-04-01 13:38:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-01 13:38:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-01 13:38:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-01 13:38:01       18 阅读

热门阅读

  1. flask+uwsgi+云服务器 部署服务端

    2024-04-01 13:38:01       22 阅读
  2. 【微服务篇】分布式事务方案以及原理详解

    2024-04-01 13:38:01       16 阅读
  3. 多线程(24)Future接口

    2024-04-01 13:38:01       15 阅读
  4. 设计模式之策略模式

    2024-04-01 13:38:01       12 阅读
  5. Spark数据倾斜解决方案

    2024-04-01 13:38:01       16 阅读
  6. 如何用Redis实现消息队列

    2024-04-01 13:38:01       19 阅读
  7. Codeforces Round 932 (Div. 2)(A,B,C,D)

    2024-04-01 13:38:01       16 阅读
  8. [蓝桥杯 2016 国 C] 赢球票

    2024-04-01 13:38:01       17 阅读
  9. 专升本-大数据

    2024-04-01 13:38:01       18 阅读