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

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

动图描述

目录

    • 1、进程创建
    • 2、进程终止
    • 3、进程等待
    • 4、进程程序替换
    • 5、手写简洁版shell


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;elsecout << "备份失败,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();}}return 0;
}

上面的代码中子进程每隔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;}else if (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  更新环境变量return pwd;
//	string pwd = getenv("PWD");
//	return pwd.empty() ? "None" : pwd;
}string LastDir()
{//  /home/yjz/code/xxxstring 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){return false;//如果是空串直接返回}return true;}return false;
}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){return false;}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;}return true;}return false;
}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;}return true;}else if (strcmp(gargv[0], "export") == 0){if (gargc == 2){AddEnv(gargv[1]);}else{lastcode = 2;}return true;}else if (strcmp(gargv[0], "env") == 0){for (int i = 0; genv[i]; i++){printf("%s\n", genv[i]);}lastcode = 0;return true;}else if (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;}return true;}return false;
}//作为一个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、执行命令}return 0;
}

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

头像

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

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

相关文章

EasyDarwin搭建直播推流服务

学习链接 easydarwin官网 - 这里看介绍 easydarwin软件下载地址 - 百度网盘 easydarwin视频 B站 文章目录 学习链接使用下载EasyDarwin压缩包&#xff0c;并解压到目录启动EasyDarwin点播直播easyplayer.jsapidocffmpeg推流rtsp & ffplay拉流 使用 下载EasyDarwin压缩包…

云备份实战项目

文章目录 前言一、整体项目简介二、服务端环境及功能简介三、 客户端环境及功能简介四、服务端文件管理类的实现1. 获取文件大小&#xff0c;最后一次修改时间&#xff0c;最后一次访问时间&#xff0c;文件名称&#xff0c;以及文件内容的读写等功能2. 判断文件是否存在&#…

关于ConstarintLayout有关的点

目录 一、概述 二、过程。 1、介绍 主要特点 关键概念 使用示例 总结 2、我遇到的问题 问题&#xff1a; 可能的原因&#xff1a; 结论 一、概述 在学习过程中&#xff0c;发现对ConstarintLayout理解不够到位&#xff0c;下面是发现并解决问题过程。 二、过程。 1…

《数字图像处理基础》学习07-图像几何变换之最近邻插值法放大图像

目录 一&#xff0c;概念 二&#xff0c;题目及matlab实现 1&#xff0c;解题思路 2&#xff0c;matlab实现 1&#xff09;matlab思路 2&#xff09;完整代码 三&#xff0c;放大图像及matlab实现 一&#xff0c;概念 通过上一篇&#xff0c;我已经学习了使用最邻近插…

计网-子网划分

基于本视频观看做的笔记&#xff0c;帮助自己理解 子网掩码&#xff1a;用于识别IP地址中的网络号和主机号的位数 表示方法 第一种.32位二进制数字&#xff0c;在子网掩码中&#xff0c;网络号用”1“表示&#xff0c;主机号用”0“表示 e.g.:IP地址1.1.1.1的子网掩码是255…

【Solidity】入门指南:智能合约开发基础

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 Solidity入门指南&#xff1a;智能合约开发基础引言1. 开发环境搭建1.1 Remix I…

如何高效地架构一个Java项目

引言 Java是企业级应用开发的主流语言之一&#xff0c;而我们作为使用Java语言的程序员&#xff0c;职称有初级、中级、高级、资深、经理、架构&#xff0c;但我们往往只是慢慢通过经验的积累迭代了自己的等级&#xff0c;如果没有保持学习的习惯&#xff0c;大多数程序员会停留…

HTTP 探秘之旅:从入门到未来

文章目录 导言&#xff1a;目录&#xff1a;第一篇&#xff1a;HTTP&#xff0c;互联网的“快递员”第二篇&#xff1a;从点开网页到看到内容&#xff0c;HTTP 究竟做了什么&#xff1f;第三篇&#xff1a;HTTP 的烦恼与进化史第四篇&#xff1a;HTTP 的铠甲——HTTPS 的故事第…

c++:thread(线程)

1.基本使用 1.1创建线程 join()函数用于&#xff1a;主程序等待子线程执行完毕之后再继续 #include <iostream> #include <thread>void printHello() {std::cout << "hello world" << std::endl; }int main() {// 1.创建线程std::thread …

第六届金盾信安杯-SSRF

操作内容&#xff1a; 进入环境 可以查询网站信息 查询环境url https://114.55.67.167:52263/flag.php 返回 flag 就在这 https://114.55.67.167:52263/flag.php 把这个转换成短连接&#xff0c;然后再提交 得出 flag

SpringMVC(2)

前言 这一节我们终结springmvc 1. SSM整合配置 先导入坐标 先创建包 配置类 然后开始创建jdbc的config和mybatis的config&#xff0c;还有properties SpringConfig&#xff1a; jdbc.properties&#xff1a; JdbcConfig: MybatisConfig: 下面开始spring整合mvc&#…

零基础Python学习

1.环境搭建 1.1 安装运行环境python3.13 Welcome to Python.org 1.2 安装集成开发环境PyCharm PyCharm: the Python IDE for data science and web development 1.3 创建项目 && 设置字体 2.基础语法 2.1 常量与表达式 在python中整数除整数不会优化&#xff0c;所…

vue3项目创建方式记录

目录 创建vue3常用的方式有三种&#xff1a;一.使用vue cli创建二.使用vite创建三.使用vue3官方推荐创建方式&#xff08;create-vue&#xff09; 创建vue3常用的方式有三种&#xff1a; 一.使用vue cli创建 vue create 项目名二.使用vite创建 vite是下一代前端开发与构建工…

用MATLAB符号工具建立机器人的动力学模型

目录 介绍代码功能演示拉格朗日方法回顾求解符号表达式数值求解 介绍 开发机器人过程中经常需要用牛顿-拉格朗日法建立机器人的动力学模型&#xff0c;表示为二阶微分方程组。本文以一个二杆系统为例&#xff0c;介绍如何用MATLAB符号工具得到微分方程表达式&#xff0c;只需要…

SpringAi整合大模型(进阶版)

进阶版是在基础的对话版之上进行新增功能。 如果还没弄出基础版的&#xff0c;请参考 https://blog.csdn.net/weixin_54925172/article/details/144143523?sharetypeblogdetail&sharerId144143523&sharereferPC&sharesourceweixin_54925172&spm1011.2480.30…

Android电视项目焦点跨层级流转

1. 背景 在智家电视项目中&#xff0c;主要操作方式不是触摸&#xff0c;而是遥控器&#xff0c;通过Focus进行移动&#xff0c;确定点击进行的交互&#xff0c;所以在电视项目中焦点、选中、确定、返回这几个交互比较重要。由于电视屏比较大&#xff0c;在一些复杂页面中会存…

yolo辅助我们健身锻炼

使用软件辅助健身能够大大提升运动效果并帮助你更轻松地达成健身目标。确保每次锻炼都更加高效且针对性强,精确记录你的训练进度,帮助你更清晰地看到自己的进步,避免无效训练。 借助YOLO11的尖端计算机视觉技术,跟踪和分析锻炼变得异常简单。它可以无缝检测和监控多种锻炼…

Flume 与 Kafka 整合实战

目录 一、Kafka 作为 Source【数据进入到kafka中&#xff0c;抽取出来】 &#xff08;一&#xff09;环境准备与配置文件创建 &#xff08;二&#xff09;创建主题 &#xff08;三&#xff09;测试步骤 二、Kafka 作为 Sink数据从别的地方抽取到kafka里面】 &#xff08;…

SRS搭建直播推流服务

学习链接 5分钟教你搭建SRS流媒体服务器 - B站视频 SRS Stack 入门B站合集视频 - SRS官方教程 SRS官网 SRS官网文档 ossrs/srs github SRS for window - 可以安装windows版本的srs&#xff0c;SRS 5.0.89正式支持Windows&#xff0c;每个5.0的版本都会提供安装包 文章目录…

css—轮播图实现

一、背景 最近和朋友在一起讨论的时候&#xff0c;我们提出了这样的一个提问&#xff0c;难道轮播图的效果只能通过js来实现吗&#xff1f;经过我们的一系列的争论&#xff0c;发现了这是可以通过纯css来实现这一效果的&#xff0c;CSS轮播图也是一种常见的网页展示方式&#x…