进程创建

fork函数初识

fork() 是Linux内核提供的系统调用,作用是从已存在的父进程中,创建一个全新的子进程。调用fork后,系统会复制父进程的PCB、地址空间、文件描述符、信号处理方式等大部分进程资源,生成一个和父进程几乎完全一致的子进程。

函数原型:

#include <unistd.h>
pid_t fork(void);

进程调用 fork ,当控制转移到内核中的 fork 代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork 返回,开始调度器调度

调用 fork() 之后,会分裂出两个完全独立的进程:父进程、子进程;两个进程都会从 fork 之后的代码开始,各自独立往下执行。

fork函数返回值

  • 子进程返回 0:子进程永远只有一个父进程,无需标识父进程ID,统一返回0
  • 父进程返回 子进程PID(正整数):一个父进程可以创建多个子进程,父进程通过子进程PID区分不同子进程
  • 调用失败返回 -1:子进程创建失败,不会生成新进程

写时拷贝

定义:fork 创建子进程时,只拷贝父进程的 PCB 和页表,不拷贝任何物理内存数据;父子进程共享同一块物理内存页,并标记为只读。只有当任意一方尝试写入 / 修改数据时,内核才单独拷贝一份新的物理内存页给该进程,实现地址空间隔离。

在这里插入图片描述
核心价值

  • 节省物理内存:不做无意义的全额拷贝,只有真正写的时候才分配新内存
  • 极大加快 fork 速度:只复制页表,不用拷贝大量数据,fork 瞬间完成
  • 保证进程独立性:逻辑上父子进程地址空间完全独立,底层用延迟拷贝优化性能

fork常规用法

  1. 父子进程执行不同代码逻辑:父进程负责监听、等待、管理资源,子进程负责执行具体业务任务,实现任务并行处理。例如,父进程等待客户端请求,生成子进程来处理请求
  2. 创建子进程后执行程序替换:子进程通过exec系列函数加载全新程序

fork调用失败的原因

  • 系统进程数达到上限
  • 系统内存资源不足

进程终止

进程终止的本质是操作系统回收进程占用的内存、文件等系统资源,销毁进程的代码、数据及相关内核数据结构,暂留 PCB 保存退出状态,等待父进程回收后彻底释放所有内核资源

进程退出场景

  • 正常退出 — 代码跑完,结果正确:进程完整执行完所有代码逻辑,程序正常走到末尾
    退出码固定为 0(操作系统约定:0 代表进程运行成功。)

  • 正常退出 — 代码跑完,结果错误:进程依旧完整跑完所有代码,没有崩溃、没有被强行杀死,但是业务逻辑执行失败、功能没达到预期
    退出码非 0 值(范围 1~255,不同数字可以用来标识不同错误原因)

  • 异常终止 — 代码没跑完,强制终止:程序代码没有执行完毕,中途直接结束
    没有自定义有效退出码,进程终止原因由 信号编号 标识

退出码

  • 进程退出码是子进程终止后存放在 PCB 中的执行状态信息,专门提供给父进程;父进程通过 wait/waitpid 读取退出码,用于判断子进程运行结果;若父进程不读取,子进程将滞留 PCB 成为僵尸进程
  • main 函数的返回值 = 进程的退出码
  • echo $? = 打印最近一个进程的退出时的退出码
  • Linux 错误码是系统调用或库函数调用失败时,内核通过全局变量 errno 设置的编号,每个编号对应一种错误原因
  • 退出码和错误码没有自动绑定关系,但程序员可以手动把 errno 设为退出码

示例:

#include <stdio.h>    
#include <string.h>    
#include <errno.h>    
    
int main()    
{    
    int i = 0;    
    for(; i < 200; i++)    
    {    
        printf("%d->%s\n", i, strerror(i));    
    }    
    
    FILE *fp = fopen("log.txt", "r");    
    if(fp == NULL)    
        return errno;                                                                                                              

    return 0;    
}

进程常见退出方法

从main函数返回

  • main 函数的返回值 = 进程的退出码
  • main函数结束,表示进程结束;普通函数的 return 仅代表函数返回
  • 底层:main 函数执行 return 后,会自动调用 exit() 函数

示例:

#include <stdio.h>
int main() {
    printf("进程正常退出\n");
    return 0; // 退出码0,代表成功
}

exit() 函数(C 标准库函数)

头文件 & 原型

#include <stdlib.h>
void exit(int status);
  • 代码任意位置都能调用(main 函数 / 普通函数 / 循环中),调用后直接终止进程
  • 参数 status = 进程退出码(0~255)
  • 是对系统调用 _exit()封装增强
  • 会刷新所有 IO 缓冲区

示例:

#include <stdio.h>    
#include <stdlib.h>    
    
void fun()    
{    
    printf("fun begin!\n");    
    exit(40);    
    printf("fun end!\n");    
}    
    
int main()    
{    
    fun();    
    printf("main!\n");                                                                                                             
    
    return 0;    
}

_exit() 函数(Linux 系统调用)

头文件 & 原型

#include <unistd.h>
void _exit(int status);
  • 代码任意位置调用,直接进入内核终止进程。
  • 参数 status = 进程退出码。
  • 不会刷新 IO 缓冲区

示例:

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

int main() {
    printf("Hello World"); // 无换行,数据在缓冲区
    _exit(0); // 暴力退出,缓冲区数据丢失,屏幕无输出
}

在这里插入图片描述

进程等待

进程等待:父进程通过系统调用(wait/waitpid),主动检测子进程的退出状态,回收子进程的 PCB 内核资源,让子进程彻底消亡的过程。

简单说:子进程退出后,父进程必须来「收尸」,这个收尸动作就是进程等待

进程等待的必要性

  • 解决僵尸进程,避免内存泄漏:子进程退出后,PCB资源不会自动释放,会保留退出状态信息,若父进程不等待回收,PCB 会一直占用内核内存 → 僵尸进程,僵尸进程无法被 kill 杀死,大量产生会造成内存泄漏
  • 获取子进程退出状态:父进程可以通过等待函数,获取子进程退出码,判断子进程是否正常执行完毕

进程等待的方法

Linux 仅提供两个系统调用实现进程等待

1. wait () 函数(阻塞式等待)

头文件 & 函数原型

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

pid_t wait(int *status);

核心功能阻塞式等待任意一个子进程退出,自动回收子进程资源

参数详解
status输出型参数(内核向用户传递数据):

  • NULL:父进程不关心子进程退出状态,只负责回收资源(最简单用法)
  • &status(整型变量地址):内核会把子进程的退出信息写入该变量,后续用宏解析

返回值详解

  • 成功:返回退出子进程的 PID(>0)
  • 失败:返回 -1(父进程没有子进程可等待)

核心特性

  • 阻塞等待:父进程调用后会暂停运行(卡住),直到子进程退出才继续执行;
  • 等待任意子进程:只要有一个子进程退出,函数立即返回

示例:

#include <stdio.h>    
#include <unistd.h>    
#include <sys/types.h>    
#include <sys/wait.h>    
    
int main()    
{    
    pid_t id = fork();    
    if(id == 0)    
    {    
        //子进程    
        int cnt = 5;    
        while(cnt)    
        {    
            printf("我是一个子进程, pid : %d, ppid : %d\n", getpid(), getppid());    
            sleep(1);    
            cnt--;    
        }    
        exit(0);    
    }    
    sleep(10);                                                                                                                     
    //父进程    
    pid_t rid = wait(NULL);    
    if(rid > 0)    
    {    
        printf("wait sucess, rid: %d\n", rid);    
    }    
    
    sleep(10);    
    return 0;    
}

2. waitpid() (万能等待)

wait()waitpid() 的简化封装,waitpid 功能完全覆盖 wait
头文件 & 函数原型

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

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

核心功能:支持指定等待某个子进程、非阻塞等待,灵活度拉满

三大参数详解
参数 1:pid_t pid → 指定等待的子进程

  • pid = -1:等待任意子进程(等价于 wait());
  • pid > 0:等待PID 等于该值的指定子进程
  • pid = 0 / pid < -1:等待进程组(极少使用)。

参数 2:int *status → 退出状态
wait 的参数完全一致,传 NULL 不关心状态

参数 3:int options → 等待模式

  • options = 0阻塞等待(和 wait 行为一致)
  • options = WNOHANG非阻塞等待(父进程不卡住,立刻返回)

返回值详解

  • 返回 > 0:成功回收子进程,返回子进程 PID;
  • 返回 0:仅非阻塞模式生效子进程还未退出
  • 返回 -1:调用失败(无子进程、参数错误)。

核心特性

  • 支持指定子进程等待;
  • 支持非阻塞等待(父进程可以边等边做其他事)

在这里插入图片描述

补充:strerror()(字符串转换函数)

  • 头文件:#include <string.h>
  • 函数原型:char *strerror(int errnum);
  • 作用:把 数字错误码 → 人类能看懂的英文错误描述字符串
  • 输入:错误码(如 2)
  • 返回值:指向错误信息字符串的指针(如 "No such file or directory"
  • 示例:
#include <stdio.h>
#include <string.h>
#include <errno.h>

int main()    
{    
    FILE *fp = fopen("log.txt", "r");  // 打开不存在的文件
    if(fp == NULL)  // 函数调用失败
    {
        // 打印错误码 + 文字描述
        printf("fopen 失败!错误码:%d,错误原因:%s\n", errno, strerror(errno));
        return 1;
    }
    return 0;    
}

子进程退出状态 status 参数

status不能简单的当作整形来看待,可以当作位图来看待,status 是 32 位整型,内核只使用 status 的 低 16 位 存储退出信息

我们把 status低 16 位(bit 0 ~ bit 15) 作为研究对象:
在这里插入图片描述
1. 子进程正常退出:
高 8 位 = 进程退出码(0~255),低 8 位 = 0

实战举例

  • 子进程 exit(1),退出码 = 1
  • 1 的二进制:00000001
  • 存入高 8 位,低 8 位填 0
  • 低 16 位最终二进制:00000001 00000000
  • 转换成十进制:256
    所以直接打印 status 会输出 256,而不是 1

手动解析

// 2. 获取退出码:右移8位,取高8位
(status >> 8) & 0xFF; 

2. 被信号杀死:
高 8 位 = 无意义(填 0),第 8 位 = core dump 标志(0/1),低 7 位 = 终止信号编号(1~64)

手动解析

// 2. 获取信号编号:取低7位
status & 0x7F;

解析 status 的 4 个核心宏函数
Linux 提供 4 个宏,专门用来拆封 status

宏函数 功能作用 配套使用宏
WIFEXITED(status) 判断子进程是否正常退出,正常返回 非0 WEXITSTATUS
WEXITSTATUS(status) 获取子进程正常退出的退出码(仅在 WIFEXITED 为真时使用) -
WIFSIGNALED(status) 判断子进程是否异常退出(被信号杀死),异常返回 非0 WTERMSIG
WTERMSIG(status) 获取杀死子进程的信号编号(仅在 WIFSIGNALED 为真时使用) -

示例:

#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)    
    {    
        //子进程    
        int cnt = 3;    
        while(cnt)    
        {    
            sleep(3);    
            printf("我是一个子进程, pid : %d, ppid : %d\n", getpid(), getppid());    
            sleep(1);    
            cnt--;    
            int *p = 0;    
            *p = 100;    
        }    
        exit(1);    
    }    
    //父进程    
    int status = 0;    
    pid_t rid = waitpid(id, &status, 0);    
    if(rid > 0)    
    {    
        if(WIFEXITED(status))    
            printf("wait sucess, rid: %d, status: %d, exit signal: %d\n", rid, WEXITSTATUS(status), status&0x7F);                  
        else     
            printf("子进程异常退出!\n");    
    }    
    else
    {
        printf("wait failed: %d: %s\n", errno, strerror(errno));
    }
    return 0;
}

阻塞等待与非阻塞等待

1. 阻塞等待(options=0
定义:
父进程调用等待函数后,暂停自身所有执行,放弃 CPU,进入休眠状态;直到子进程退出,内核唤醒父进程,父进程才恢复运行、回收子进程

2. 非阻塞等待(options=WNOHANG
定义:
父进程调用等待函数时,设置 WNOHANG 选项:

  • 子进程已退出 → 回收并返回子进程 PID
  • 子进程未退出 → 函数立刻返回 0,父进程不休眠、不卡死,继续执行自己的任务
  • 父进程可以轮询检查子进程状态

示例:

#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)    
    {    
        //子进程    
        int cnt = 3;    
        while(1)    
        {    
            sleep(3);    
            printf("我是一个子进程, pid : %d, ppid : %d\n", getpid(), getppid());    
            sleep(1);    
            cnt--;
        }    
        exit(1);    
    }     
    
    // 父进程    
    while(1)    
    {    
        int status = 0;                                                                                                            
        pid_t rid = waitpid(id, &status, WNOHANG);    
        if(rid > 0)    
        {    
            printf("wait sucess, rid: %d, status: %d, exit signal: %d\n", rid, WEXITSTATUS(status), status&0x7F);    
            break;
            }
        else if(rid == 0)
        {
            printf("本轮调用结束,子进程没有退出\n");
            sleep(1);
        }
        else 
        {
            printf("等待失败\n");
            break;                                                                                                                 
        }
    }
	return 0;
}

进程程序替换

通过 exec 系列函数,保留当前进程的壳(PID、PCB、文件描述符、内存结构),彻底替换进程的代码段、数据段、堆、栈,加载一个全新的可执行程序运行。

为什么需要进程程序替换?
fork 创建的子进程默认只能执行和父进程一模一样的代码,但实际场景中,我们需要子进程:

  • 执行系统命令(ls/pwd/cp
  • 执行另一个自己写的二进制程序
  • 执行独立的业务逻辑

此时就必须用程序替换,让子进程抛弃父进程的代码,执行全新程序

程序替换的原理

在这里插入图片描述

不创建新进程、不改变进程 PID、不销毁 PCB 的前提下,清空当前进程用户地址空间的旧代码、旧数据,把磁盘上一个新的可执行程序(ELF 文件)加载到当前进程的地址空间,重置运行上下文,从新程序入口开始执行。

简单来说:换代码、换数据、不换进程、不换 PID、不换 PCB

核心特性

  • 调用成功 → 无返回值原进程的代码、数据被彻底覆盖销毁,函数没有地方可以返回,直接执行新程序
  • 调用失败 → 才返回 -1:只有文件不存在、权限不足、路径错误时,才会退回原进程继续执行

exec系列函数

exec 函数,底层功能完全一致,仅传参方式、环境变量、路径搜索不同

命名魔法规则
函数名由 4 个字母 组合而成,每个字母代表一种传参规则:

字母 英文全称 含义
l list 列表传参:参数逐个手写(如 ls, -l, NULL
v vector 数组传参:参数放入 char* 数组,传入数组地址
p path 自动搜索 PATH 环境变量:不用写程序全路径
e env 支持自定义环境变量:覆盖系统默认环境变量

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
通用头文件

#include <unistd.h>

参数规则

  • 第一个参数:要执行的程序路径 / 命令名
  • 第二个参数argv[0]程序自身名称
  • 中间参数:命令选项(如 -l-a
  • 所有参数 / 环境变量,结尾必须加 NULL
  • 进程属性(PID、文件描述符、PCB)全程不变

1. execl

  • 原型:int execl(const char *path, const char *arg, ..., NULL);
  • 特性:列表传参 + 必须全路径 + 默认环境变量
  • 示例:
execl("/usr/bin/ls", "ls", "-l", "-a", NULL);

2. execlp(常用)

  • 原型:int execlp(const char *file, const char *arg, ..., NULL);
  • 特性:列表传参 + 自动搜 PATH + 默认环境变量
  • 示例:
execlp("ls", "ls", "-l", "-a", NULL);

3. execv

  • 原型:int execv(const char *path, char *const argv[]);
  • 特性:数组传参 + 全路径 + 默认环境变量
  • 示例:
char *argv[] = {"ls", "-l", NULL};
execv("/usr/bin/ls", argv);

4. execvp

  • 原型:int execvp(const char *file, char *const argv[]);
  • 特性:数组传参 + 自动搜 PATH + 默认环境变量
  • 示例:
char *argv[] = {"ls", "-a", "-l", NULL};
execvp(argv[0], argv);

5. execle

  • 原型:int execle(const char *path, const char *arg,..., NULL, char *envp[]);
  • 特性:列表传参 + 全路径 + 自定义环境变量
  • 示例:
char *env[] = {"MY_TEST=123", NULL};
execle("./test", "test", NULL, env);

6. execvpe

  • 原型:int execvpe(const char *file, char *const argv[], char *const envp[]);
  • 特性:数组传参 + 搜 PATH + 自定义环境变量
  • 示例:
char *argv[] = {"ls", "-l", NULL};
char *env[] = {"MY_ENV=hello", NULL};
execvpe("ls", argv, env);

综合示例:
要求被替换的子进程,使用全新的env列表:通过新增方式给子进程
other.cc文件

#include <iostream>    
#include <cstdio>    
#include <unistd.h>    
    
int main(int argc, char *argv[], char *env[])                                                                                      
{    
    std::cout << "hello C++, My Pid is: " << getpid() << std::endl;    
    
    for(int i = 0; i < argc; i++)    
    {    
        printf("argv[%d]: %s\n", i, argv[i]);    
    }    
    
    printf("\n");    
    
    for(int i = 0; env[i]; i++)    
    {    
        printf("env[%d]: %s\n", i, env[i]);    
    }    
    
    return 0;    
}

proc.c文件

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

char *const addenv[] = {
    (char*const)"MYVAL=123456789",
    (char*const)"MYVAL1=123456789",
    (char*const)"MYVAL2=123456789",
    NULL 
};

int main()
{
    printf("我的程序要运行了\n");

    if(fork() == 0)
    {
        printf("I am Child, My Pid is: %d\n", getpid());
        sleep(1);

        char *const argv[] = {
            (char*const)"other", 
            (char*const)"-a", 
            (char*const)"-b",
            (char*const)"-c",
            (char*const)"-d",
            NULL
        };
                                                                                                                                   
        for(int i = 0; addenv[i]; i++)
        {
            putenv(addenv[i]);
        }
        extern char **environ;
        exit(1);
    }

    waitpid(-1, NULL, 0);
    printf("我的程序运行完毕了\n");
    return 0;
}

7. execve(底层唯一系统调用)

  • 原型:int execve(const char *path, char *const argv[], char *const envp[]);
  • 特性:数组传参 + 全路径 + 自定义环境变量
  • 地位:内核只认它,所有库函数最终都调用它

自主Shell命令行解释器

Shell 整体运行框架原理

Shell 的生命周期只有一套无限循环逻辑,这是所有 Shell(bash/zsh/sh)的底层骨架:

启动 Shell → 打印命令提示符 → 获取用户命令 → 解析命令 → 执行命令 → 回到命令提示符(循环)

核心模块实现原理

  • 打印命令提示符: 调用系统函数获取 用户名、主机名、当前目录
  • 获取用户输入命令: 使用 fgets() 读取整行输入(要手动把末尾的 \n 替换为字符串结束符 \0
  • 命令解析:strtok() 字符串分割函数按空格切割命令,生成 argv 数组
  • 执行命令:
  1. 内建命令: 不创建子进程,由 Shell 进程自身直接执行系统调用(如:cdechoexport)
  2. 普通命令: fork() 创建子进程,子进程 execvp("ls", argv) 程序替换,执行 ls,父进程 wait() 阻塞等待子进程

在这里插入图片描述

补充1:putenv()

  • 头文件#include <stdlib.h>
  • 函数原型int putenv(char *string);
  • 作用:为当前进程 添加 / 修改 环境变量
    环境变量不存在 → 新增
    环境变量已存在 → 覆盖修改
    作用范围:仅当前进程
  • 参数规则:参数必须是固定格式字符串:"变量名=变量值"
  • 示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// 全局变量(永久有效,putenv安全使用)
char env_buf[] = "MYTEST=hello_putenv";

int main() {
    // 1. 设置环境变量
    putenv(env_buf);

    // 2. 获取环境变量(getenv 配套使用)
    printf("获取环境变量:%s\n", getenv("MYTEST"));
    return 0;
}

补充2:chdir()

  • 头文件#include <unistd.h>
  • 函数原型int chdir(const char *path);
  • 作用:修改当前进程的 当前工作目录 (CWD)
  • 参数
    path:目标路径(可以是绝对路径相对路径
    示例:"/home""..""~/Desktop"

实现源码

#include <iostream>                                                                                                                                  
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unordered_map>

#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "

// 下面是shell定义的全局数据

// 1. 命令行参数表
#define MAXARGC 128
char *g_argv[MAXARGC];
int g_argc = 0;

// 2. 环境变量表
#define MAX_ENVS 100
char *g_env[MAX_ENVS];
int g_envs = 0;

// 3. 别名映射表
std::unordered_map<std::string, std::string> alias_list;

// for test 
char cwd[1024];
char cwdenv[1024];

// last exit code 
int lastcode = 0;

const char *GetUserName()
{
    const char *name = getenv("USER");
    return name == NULL ? "None" : name;
}

const char *GetHostName()
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char *GetHostName()
{
    const char *hostname = getenv("HOSTNAME");
    return hostname == NULL ? "None" : hostname;
}

const char *GetPwd()
{
    //const char *pwd = getenv("PWD");
    const char *pwd = getcwd(cwd, sizeof(cwd));
    if(pwd != NULL)
    {
        snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);
        putenv(cwdenv);
    }
    return pwd == NULL ? "None" : pwd;
}

const char *GetHome()
{
    const char *home = getenv("HOME");
    return home == NULL ? "" : home;
}


void InitEnv()
{
    extern char **environ;
    memset(g_env, 0, sizeof(g_env));
    g_envs = 0;

    // 本来要从配置文件来
    // 1. 获取环境变量
    for(int i = 0; environ[i]; i++)
    {
        // 1.1 申请空间
        g_env[i] = (char*)malloc(strlen(environ[i])+1);
        strcpy(g_env[i], environ[i]);
        g_envs++;
    }                                                                                                                                                
    g_env[g_envs++] = (char*)"HAHA=for_test"; // for_test
    g_env[g_envs] = NULL;

    // 2. 导成环境变量
    for(int i = 0; g_env[i]; i++)
    {
        putenv(g_env[i]);
    }
    environ = g_env;
}

// command 
bool Cd()
{
    if(g_argc == 1)
    {
        std::string home = GetHome();
        if(home.empty()) return true;
        chdir(home.c_str());
    }
    else 
    {
        std::string where = g_argv[1];
        // cd - / cd ~
        if(where == "-")
        {
            // Todu
        }
        else if(where == "~")
        {
            // Todu
        }
        else 
        {
            chdir(where.c_str());
        }
    }
    return true;
}

void Echo()
{
    if(g_argc == 2)
    {
        // echo "hello world"                                                                                                                        
        // echo $?
        // echo $PATH
        std::string opt = g_argv[1];
        if(opt == "$?")
        {
        std::cout << lastcode << std::endl;
            lastcode = 0;
        }
        else if(opt[0] == '$')
        {
            std::string env_name = opt.substr(1);
            const char *env_value = getenv(env_name.c_str());
            if(env_value)
                std::cout << env_value << std::endl;
        }
        else 
        {
            std::cout << opt << std::endl;
        }
    }
}

std::string DirName(const char *pwd)
{
#define SLASH "/"
    std::string dir = pwd;
    if(dir == SLASH) return SLASH;
    auto pos = dir.rfind(SLASH);
    if(pos == std::string::npos) return "BUG?";
    return dir.substr(pos+1);
}

void MakeCommandLine(char cmd_prompt[], int size)
{
    snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(),DirName(GetPwd()).c_str());
    //snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(),GetPwd());
}

void PrintCommandPrompt()
{
    char prompt[COMMAND_SIZE];
    MakeCommandLine(prompt, sizeof(prompt));
    printf("%s", prompt);
    fflush(stdout);
}                                                                                                                                                    

bool GetCommandLine(char *out, int size)
{
    char *c = fgets(out, size, stdin);
    if(c == NULL) return false;
    return true;
}

// 3.  命令行分析 "ls -a -l" -> "ls" "-a" "-l"
bool CommandParse(char *commandline)
{
#define SEP " "
    g_argc = 0;
    // 命令行分析 "ls -a -l" -> "ls" "-a" "-l"
    g_argv[g_argc++] = strtok(commandline, SEP);
    while((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));
    g_argc--;
    return g_argc > 0 ? true : false;
}

void PrintArgv()
{
    for(int i = 0; g_argv[i]; i++)
    {
        printf("argv[%d]->%s\n", i, g_argv[i]);
    }
    printf("argc: %d\n", g_argc);
}

bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if(cmd == "cd")
    {
        Cd();
        return true;
    }
    else if(cmd == "echo")
    {
        Echo();
        return true;
    }
    else if(cmd == "export")
    {
                                                                                                                                                     
    }
    else if(cmd == "alias")
    {

    }
    return false;
}

int Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        // child
        execvp(g_argv[0], g_argv);
        exit(1);
    }
    int status = 0;
    // father
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        lastcode = WEXITSTATUS(status);
    }
    return 0;
}

int main()
{
    // shell启动的时候,从系统获取环境变量
    // 我们的环境变量信息应该从父shell统一来
    InitEnv();

    while(true)
    {
        // 1. 输出命令行提示符
        PrintCommandPrompt();

        // 2. 获取用户输入的命令
        char commandline[COMMAND_SIZE];
        if(!GetCommandLine(commandline, sizeof(commandline)))
            continue;

        // 3.  命令行分析 "ls -a -l" -> "ls" "-a" "-l"
        if(!CommandParse(commandline))                                                                                                               
            continue;
        //PrintArgv();

        // 4. 检测并处理内键命令
        if(CheckAndExecBuiltin())
        continue;

        // 5. 执行命令
        Execute();
    }
    //clenup();
    return 0;
}
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐