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

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

目录


1、进程创建

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

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

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

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

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

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

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

shell 复制代码
#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();
		}
	}
    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非零,提取子进程退出码(查看进程的退出码)

阻塞和非阻塞

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

shell 复制代码
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的环境变量表。

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/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)
		{
			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;
}

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

相关推荐
周杰伦_Jay1 天前
【智能体(Agent)技术深度解析】从架构到实现细节,核心是实现“感知环境→处理信息→决策行动→影响环境”的闭环
人工智能·机器学习·微服务·架构·golang·数据挖掘
江上清风山间明月1 天前
Android 系统超级实用的分析调试命令
android·内存·调试·dumpsys
百锦再1 天前
第12章 测试编写
android·java·开发语言·python·rust·go·erlang
wanhengidc1 天前
全面了解云手机的安全性
运维·服务器·游戏·智能手机·云计算
menge23331 天前
Linux DNS域名解析服务器练习
linux·运维·服务器
ytttr8731 天前
Landweber迭代算法用于一维、二维图像重建
人工智能·算法·机器学习
努力成为一个程序猿.1 天前
Clickhouse数据副本和分片
运维·clickhouse·debian
wsad05321 天前
CentOS 7 更换腾讯云 yum 源及 EPEL 源
linux·centos·腾讯云
wdfk_prog1 天前
[Linux]学习笔记系列 -- [kernel]kallsyms
linux·笔记·学习
hongjianMa1 天前
【论文阅读】Hypercomplex Prompt-aware Multimodal Recommendation
论文阅读·python·深度学习·机器学习·prompt·推荐系统