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

news2024/11/25 10:30:44

文章目录

  • 一、程序地址空间
    • 1.地址空间验证
    • 2.验证堆和栈的增长方向
    • 3.感知地址空间
    • 4.什么是地址空间
  • 二、进程控制
    • 1.进程创建
    • 2.进程终止
      • 1、**关于终止的正确认识:**
      • 2、**关于终止常见做法**
      • 3、**关于终止,内核做了什么?**
    • 3.进程等待
      • 1、为什么要进行进程等待
      • 2、进程等待 - wait - waitpid (系统接口)
    • 4.进程替换 - execve
      • 1.进程替换是什么
      • 2.为什么要进程替换
      • 3.如何进程替换
        • 1、见见猪跑———最基本的代码
        • 2、引入进程创建
        • 3、大量测试各种不同的接口
    • int execv(const char *path, char *const argv[]);
    • int execlp(const char *file, const char *arg, ...)
    • int execvp(const char *file, char *const argv[]);
    • int execle(const char *path, const char *arg, ..., char * const envp[]);
    • **总结**
        • 4、替换自己的程序
  • 三、简易shell


一、程序地址空间

在这里插入图片描述

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

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)。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1561593.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【DETR系列目标检测算法代码精讲】01 DETR算法02 DETR算法数据预处理+图像增强+dataset代码精讲

今天这一节主要对DETR算法的数据预处理和数据增强部分的代码做逐行的精讲。 这一部分的代码主要的功能就是将COCO数据集中的原始图像和原始标注处理成能够输入到DETR网络中的图像和标注。 我首先采取任务流程逐行讲解的办法&#xff0c;然后再debug演示一下 准备 这个读取数…

<Linux> Linux环境开发工具

一、Linux软件包管理器 - yum 什么是软件包&#xff1a; 在Linux 下安装软件 , 一个通常的办法是下载到程序的源代码 , 并进行编译 , 得到可执行程序 . 但是这样太麻烦了, 于是有些人把一些常用的软件提前编译好 , 做成软件包 ( 可以理解成 windows 上的安装程序) 放在一…

Transformer的前世今生 day12(Transformer的三个问题)

Transformer的Decoder为什么要用掩码&#xff08;Masked Self-Attention&#xff09; 机器翻译中&#xff1a;源语句&#xff08;我爱中国&#xff09;&#xff0c;目标语句&#xff08;I love China&#xff09; 为了解决训练阶段和测试阶段不匹配的问题&#xff1a; 在训练阶…

多传感器标定——概述

文章目录 一、前言二、内容记录 一、前言 是对自动驾驶之心多传感器标定课程内容的记录&#xff0c;也是对一些被老师简单略过问题的自主学习。第一章是概述&#xff0c;将内容以问题的形式记录&#xff0c;并结合课上内容以及自己的项目经验给出回答 二、内容记录 车上会安装…

如何使用route-detect在Web应用程序路由中扫描身份认证和授权漏洞

关于route-detect route-detect是一款功能强大的Web应用程序路由安全扫描工具&#xff0c;该工具可以帮助广大研究人员在Web应用程序路由中轻松识别和检测身份认证漏洞和授权漏洞。 Web应用程序HTTP路由中的身份认证&#xff08;authn&#xff09;和授权&#xff08;authz&…

实验04_OSPF&RIP选路实验

实验拓扑 IP地址规划 拓扑中的 IP 地址段采用&#xff1a;172.16.AB.X/24。其中 AB 为两台路由器编号组合&#xff0c;例如&#xff1a;R3-R6 之间的 AB 为 36&#xff0c;X 为路由器编号&#xff0c;例如R3 的 X3所有路由器都有一个 loopback 0 接口&#xff0c;地址格式为&…

代码随想录算法训练营第二十七天| LeetCode 39. 组合总和、40.组合总和II、131.分割回文串

一、39. 组合总和 题目链接/文章讲解/视频讲解&#xff1a; https://programmercarl.com/0039.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C.html 状态&#xff1a;已解决 1.思路 这道题跟216. 组合总和 III - 力扣&#xff08;LeetCode&#xff09;题思路差不多&#xff0c;区别在于…

为什么感觉张宇 25 版没 24版讲得好?

很多同学反映&#xff1a;25版&#xff0c;讲得太散了, 知识点太多&#xff0c;脱离了基础班。 三个原因&#xff1a; 1. 25版改动很大&#xff0c;课程没有经过打磨&#xff1b; 2. 因为24考试难度增加&#xff0c;所以改动的总体思路是“拓宽基础”&#xff1a;即把部分强…

redis中bitmap的使用及场景,如何操作

一、概念 在Redis数据库中&#xff0c;Bitmap&#xff08;位图&#xff09;是一种特殊的数据结构&#xff0c;它不是一个独立的数据类型&#xff0c;而是基于String类型实现的。Bitmap主要用于存储大量二进制位&#xff08;0或1&#xff09;的数据&#xff0c;这些位可以代表不…

支付接口和数据库断言及封装

支付下单接口 请求方法&#xff1a; post 请求地址&#xff1a;http://shop.lemonban.com:8107/p/order/pay 请求参数&#xff1a;{“payType”:3,“orderNumbers”:“1733308182027309056”} 请求头部&#xff1a; {“Content-Type”:“application/json”,“Authorization…

HDMI 2.1b 规范解读

HDMI 规范 HDMI 2.1b 是最新版 HDMI 规范&#xff0c;支持一系列更高的视频分辨率和刷新频率&#xff0c;包括 8K60 和 4K120 以及高达 10K 的分辨率。同时支持动态 HDR 格式&#xff0c;带宽能力增加到 48Gbps HDMI。 新的超高速 HDMI 线缆支持 48Gbps 带宽。该线缆可确保提供…

在单通道彩图上踩的坑

使用labelme后&#xff0c;生成如图所示文件夹&#xff0c;其中JPEGImages是原图&#xff0c;SegmentationClassPNG是标签。 此时SegmentationClassPNG中的标签&#xff08;masks&#xff09;是只包含0和1的二进制文件&#xff0c;0表示背景,1表示要识别的物体类型。&#xff…

什么是ISP住宅IP?相比于普通IP它的优势是什么?

什么是ISP住宅IP&#xff1f; ISP住宅IP是指由互联网服务提供商&#xff08;ISP&#xff09;分配给住宅用户的IP地址。它是用户在家庭网络环境中连接互联网的标识符&#xff0c;通常用于上网浏览、数据传输等活动。ISP住宅IP可以是动态分配的&#xff0c;即每次连接时都可能会…

RabbitMQ高级-应用问题、集群搭建

1.消息补偿 消息可靠性保障&#xff1a;——消息补偿机制 需求&#xff1a;100%确保消息发送成功 2.幂等性保障 幂等性指一次和多次请求某一资源&#xff0c;对于资源本身应该具有同样的结果。也就是说&#xff0c;其任意多次执行对资源本身所产生的影响均与第一次执行的影响…

2024/3/31周报

文章目录 摘要Abstract文献阅读题目创新点实验数据研究区域数据和材料 方法XGBoost algorithmLong Short‑Term Memory AlgorithmEvaluation of the Model Accuracy 实验结果 深度学习XGBoost代码实现AdaBoostBoostingAdaBoost算法AdaBoost代码实现 总结 摘要 本周阅读了一篇基…

上海开放大学2024年春《过程控制技术》网上记分作业参考答案

答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 答案&#xff1a;更多答案&#xff0c;请关注【电大搜题】微信公众号 电大搜题 多的用不完的题库&#xff0c;支持文字、图片搜题&am…

SD-WAN组网面临的安全挑战?如何提供有效的安全措施

SD-WAN&#xff08;软件定义广域网&#xff09;技术的广泛应用&#xff0c;企业面临着越来越多的网络安全挑战。尽管SD-WAN带来了灵活性和效率的提升&#xff0c;但其开放性和基于云的特性也带来了一系列安全威胁。本文将探讨SD-WAN组网面临的安全挑战&#xff0c;并提供一些有…

1236. 递增三元组:做题笔记

目录 暴力 代码 二分 代码 前缀和 代码 推荐视频讲解 暴力 这道题说的是有三个元素数量相同的数组&#xff0c;想知道有多少个三元组满足&#xff1a;三个数分别来自 A B C数组且呈现递增。 我想的是既然要求递增&#xff0c;那就先把数组数据都排一下序&#xff0c;…

鸿蒙:滑动条组件Slider

滑动条组件&#xff0c;通常用于快速调节设置值&#xff0c;如音量调节、亮度调节等应用场景。 说明 该组件从API Version 7开始支持。 子组件 无 接口 Slider(options?: {value?: number, min?: number, max?: number, step?: number, style?: SliderStyle, direc…

如何使用potplayer在公网环境访问内网群晖NAS中储存在webdav中的影视资源

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-D7WJh3JaNVrLcj2b {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…