0


【Linux】进程控制,手搓简洁版shell

头像⭐️个人主页:@小羊 ⭐️所属专栏:Linux 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~
动图描述

目录


1、进程创建

fork函数:从已经存在的进程中创建一个新进程。新进程为子进程,原进程为父进程。

进程调用

fork

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

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

写时拷贝 (懒拷贝,时间换空间)

在这里插入图片描述

数据在默认不修改的情况下是共享的,不各自拷贝一份是因为父子进程间的数据大部分是重复的,一般只有少量数据需要修改,因为各自拷贝一份浪费空间。

更新父进程页表项为只读—子进程继承—子进程写入—触发系统错误—系统触发缺页中断—系统检测—判定是否要写时拷贝—拷贝,修改,恢复权限。

创建出子进程,让子进程执行一些任务:

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

using namespace std;

enum 
{
    OK,
    OPEN_FILE_ERROR
};

vector<int> data;

int savebegin(){
    string name = to_string((unsigned int)time(nullptr));
    name +=".backup";
    FILE *pf = fopen(name.c_str(), "w");if(pf == nullptr){return OPEN_FILE_ERROR;}
    
    string datastr;for(auto d : data){
        datastr += to_string(d);
        datastr +=" ";}
    fputs(datastr.c_str(), pf);//将拿到的数据备份到文件中
    fclose(pf);return OK;}

void save(){
    pid_t id= fork();if(id ==0){
        //子进程备份数据
        int code = savebegin();
        exit(code);}
    
    int status =0;
    pid_t rid = waitpid(id, &status, 0);if(rid >0){
        int code = WEXITSTATUS(status);//进程退出码
        if(code ==0)
            cout <<"备份成功, exit code:"<< code << endl;else
            cout <<"备份失败,exit code:"<< code << endl;}else{
        perror("waitpid");}}

int main(){
    int cnt =1;while(true){
        data.push_back(cnt++);
        sleep(1);if(cnt % 10==0){
            save();}}return0;}

上面的代码中子进程每隔10秒备份一份数据。


2、进程终止

main函数的返回值->返回给父进程或系统。

在这里插入图片描述
进程终止的方式:

  1. main函数中的return:只有main函数中的return才能终止进程
  2. exit(库函数):在代码的任何地方,结束进程

在这里插入图片描述

  1. _exit(系统调用接口):

在这里插入图片描述
这是因为我们所说的缓冲区是语言级别的缓冲区(C/C++),所以

_exit

(系统调用接口)接触不到。


3、进程等待

  • 等待的时候,如果子进程不退出,父进程就会阻塞在wait函数内部。

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

我们只能通过系统调用获取退出信息。
在这里插入图片描述

  • waitwaitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

在这里插入图片描述

进程退出:

  1. 代码跑完,结果对,return 0
  2. 代码跑完,结果不对,return !0
  3. 进程异常,OS提前用信号终止进程,进程退出信息中也会记录退出信号

如果我们想看一个进程结果是否正确,前提这个进程退出信号为0,说明这个进程是正常跑完的,但结果是对还是不对需要看退出码来判断。
在这里插入图片描述
除了上面的位操作获取退出码,还可以使用系统提供的相关宏:

  • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真(查看进程是否是正常退出)
  • WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码(查看进程的退出码)

阻塞和非阻塞
非阻塞等待即让父进程在等待子进程的过程中去做一些自己的事。

typedef function<void()> task_t;

void LoadTask(vector<task_t>& tasks){
    tasks.push_back(PrintLog);
    tasks.push_back(DownLoad);
    tasks.push_back(BackUp);}

int main(){
    vector<task_t> tasks;
    LoadTask(tasks);//加载任务

    pid_t id= fork();if(id ==0){while(true){
            cout <<"我是子进程,pid:"<< getpid()<< endl;
            sleep(1);}
        exit(0);}while(true)//阻塞循环等待
    {
        sleep(1);
        pid_t rid = waitpid(id, nullptr, WNOHANG);if(rid >0){
            cout <<"等待子进程"<< rid <<"成功"<< endl;break;}elseif(rid <0){
            cout <<"等待子进程失败"<< endl;break;}else{
            cout <<"子进程尚未退出"<< endl;
            //父进程做一些自己的事
            for(auto& task : tasks){
                task();}}}}

4、进程程序替换

上面的子进程执行的都是父进程的部分代码,如果我们想让子进程执行一个全新的程序呢?
在这里插入图片描述

替换原理:
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种

exec

函数来执行另一个程序,当进程调用一种

exec

函数时,该进程的用户空间代码和数据完全被新程序替换,开始执行新程序。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
如果给上面的代码加上死循环,再让运行时读取指令,不就是一个简单的命令解释器吗?

程序替换不影响命令行参数和环境变量。


5、手写简洁版shell

现在写的shell没有维护自己的环境变量表,是继承自父shell,我们当然也可以维护自己shell的环境变量表。

#include <iostream>#include <cstring>#include <string>#include <stdlib.h>#include <stdio.h>#include <unistd.h>#include <sys/types.h>#include <sys/wait.h>
using namespace std;

const int basesize =1024;
const int argvnum =64;
const int envnum =64;

//全局的命令行参数表
char *gargv[argvnum];//命令行参数表
int gargc =0;//计数

//上一个进程的退出码
int lastcode =0;

//全局的shell工作路径
char pwd[basesize];
char pwdenv[basesize];

//myshell的环境变量表
char *genv[envnum];

string GetUserName(){
    string name = getenv("USER");return name.empty() ? "None": name;}
string GetHostName(){
    string hostname= getenv("HOSTNAME");return hostname.empty() ? "None":hostname;}
string GetPwd(){
    //从系统中获取
    if(getcwd(pwd, sizeof(pwd))== NULL){return"None";}
    snprintf(pwdenv, sizeof(pwdenv), "PWD=%s", pwd);
    putenv(pwdenv);//PWD=xxx  更新环境变量
    returnpwd;
//    string pwd= getenv("PWD");
//    return pwd.empty() ? "None":pwd;}

string LastDir(){
    //  /home/yjz/code/xxx
    string cur = GetPwd();if(cur =="/"|| cur =="None")return cur;
    size_t pos1 = cur.rfind("/");if(pos1 == string::npos)return cur;
    size_t pos2 = pos1 ==0 ? pos1 : pos1 - 1;
    size_t pos3 = cur.rfind("/", pos2);if(pos3 == string::npos)return cur;return cur.substr(pos3);}

string MakeCmdLine(){
    char cmd_line[basesize];
    snprintf(cmd_line, basesize, "%s@%s:~%s$ ", GetUserName().c_str(), GetHostName().c_str(), LastDir().c_str());return cmd_line;}

void PrintCmdLine(){
    printf("%s", MakeCmdLine().c_str());
    fflush(stdout);}

bool GetCmdLine(char buffer[], int size){
    char *result = fgets(buffer, size, stdin);//从标准输入流中读取输入指令
    if(result){
        buffer[strlen(buffer) - 1]='\0';//去掉输入指令时的回车键
        if(strlen(buffer)==0){returnfalse;//如果是空串直接返回
        }returntrue;}returnfalse;}

void ParseCmdLine(char buffer[], int len){
    //初始化
    memset(gargv, 0, sizeof(gargv));
    gargc =0;

    const char *delim =" ";
    gargv[gargc++]= strtok(buffer, delim);while(gargv[gargc++]= strtok(NULL, delim));
    gargc--;}

//void debug()
//{
//    printf("argc: %d\n", gargc);
//    for(int i =0; gargv[i]; i++)
//    {
//        printf("agrv[%d}: %s\n", i, gargv[i]);
//    }
//}

//执行解析好的命令,为了防止程序崩溃挂掉,让子进程执行
bool ExeCmd(){
    pid_t id= fork();if(id <0){returnfalse;}if(id ==0){
        //将myshell的环境变量表传给子进程
        execvpe(gargv[0], gargv, genv); 
        exit(1);}
    
    int status =0;
    pid_t rid = waitpid(id, &status, 0);if(rid >0){if(WIFEXITED(status)){
            lastcode = WEXITSTATUS(status);}else{
            lastcode =100;}returntrue;}returnfalse;}

void AddEnv(char *item){
    int index =0;while(genv[index]){
        index++;}
    genv[index]=(char*)malloc(strlen(item) + 1);
    strncpy(genv[index], item, strlen(item) + 1);
    genv[++index]= NULL;}

//在shell中,有些命令,必须由子进程执行
//有些命令,只能shell自己执行——内建命令
//shell自己执行命令,本质是shell调用自己的函数
bool CheckAndExeBuiltCmd(){if(strcmp(gargv[0], "cd")==0){
        //内建命令
        if(gargc ==2){
            chdir(gargv[1]);}else{
            lastcode =1;}returntrue;}elseif(strcmp(gargv[0], "export")==0){if(gargc ==2){
            AddEnv(gargv[1]);}else{
            lastcode =2;}returntrue;}elseif(strcmp(gargv[0], "env")==0){for(int i =0; genv[i]; i++){
            printf("%s\n", genv[i]);}
        lastcode =0;returntrue;}elseif(strcmp(gargv[0], "echo")==0){if(gargc ==2){if(gargv[1][0]=='$'){if(gargv[1][1]=='?'){
                    printf("%d\n", lastcode);
                    lastcode =0;}}else{
                printf("%s\n", gargv[1]);
                lastcode =0;}}else{
            lastcode =3;}returntrue;}returnfalse;}

//作为一个shell,获取环境变量应该从系I统的配置文件中读取
//这里直接从父shell中拷贝
void InitEnv(){
    extern char **environ;
    int index =0;while(environ[index]){
        genv[index]=(char*)malloc(strlen(environ[index]) + 1);
        strncpy(genv[index], environ[index], strlen(environ[index] + 1));
        index++;}
    genv[index]= NULL;}

int main(){
    InitEnv();
    char cmd_buffer[basesize];//获取指令缓冲区
    while(true){    
        PrintCmdLine();//1、命令行提示符
        if(!GetCmdLine(cmd_buffer, basesize))//2、获取用户命令
        {continue;}
        //printf("%s\n", cmd_buffer);
        ParseCmdLine(cmd_buffer, strlen(cmd_buffer));//3、分析命令
        //debug();if(CheckAndExeBuiltCmd()){continue;}
        ExeCmd();//4、执行命令
    }return0;}

本篇文章的分享就到这里了,如果您觉得在本文有所收获,还请留下您的三连支持哦~
头像

标签: linux android 运维

本文转载自: https://blog.csdn.net/2301_78843337/article/details/144024093
版权归原作者 _小羊_ 所有, 如有侵权,请联系我们删除。

“【Linux】进程控制,手搓简洁版shell”的评论:

还没有评论