进程控制(下)

进程控制(下)

在这里插入图片描述

进程程序替换

在这里插入图片描述

在这里插入图片描述

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

fork() 之后,⽗⼦各⾃执⾏⽗进程代码的⼀部分如果⼦进程就想执⾏⼀个全新的程序呢?进程的程序 替换来完成这个功能!

程序替换是通过特定的接⼝,加载磁盘上的⼀个全新的程序(代码和数据),加载到调⽤进程的地址空间 中!

替换原理

⽤fork创建⼦进程后执⾏的是和⽗进程相同的程序(但有可能执⾏不同的代码分⽀),⼦进程往往要调⽤⼀ 种exec函数以执⾏另⼀个程序。当进程调⽤⼀种exec函数时,该进程的⽤⼾空间代码和数据完全被新程 序替换,从新程序的启动例程开始执⾏。调⽤exec并不创建新进程,所以调⽤exec前后该进程的id并未改 变。

替换函数

其实有六种以exec开头的函数,统称exec函数:

#include <unistd.h>
 int execl(const char *path, const char *arg, ...);
 int execlp(const char *file, const char *arg, ...);
 int execle(const char *path, const char *arg, ...,char *const envp[]);
 int execv(const char *path, char *const argv[]);
 int execvp(const char *file, char *const argv[]);
 int execve(const char *path, char *const argv[], char *const envp[]);
函数解释
  • 这些函数如果调⽤成功则加载新的程序从启动代码开始执⾏,不再返回
  • 如果调⽤出错则返回-1
  • 所以exec函数只有出错的返回值⽽没有成功的返回值。
命名理解

这些函数原型看起来很容易混,但只要掌握了规律就很好记。

  • l(list) : 表⽰参数采⽤列表
  • v(vector) : 参数⽤数组
  • p(path):有p⾃动搜索环境变量PATH
  • e(env):表⽰⾃⼰维护环境变量
函数名参数格式是否带路径是否使用当前环境变量
execl列表不是
excelp列表
execle列表不是不是,须自己组装环境变量
execv数组不是
execvp数组
execve数组不是不是,须自己组装环境变量

exec调⽤举例如下:

#include <unistd.h>
 int main()
 {
 char *const argv[] = {"ps", "-ef", NULL};
 char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
 execl("/bin/ps", "ps", "-ef", NULL);
 // 带p的,可以使⽤环境变量PATH,⽆需写全路径
 
execlp("ps", "ps", "-ef", NULL);
 // 带e的,需要⾃⼰组装环境变量
 
execle("ps", "ps", "-ef", NULL, envp);
 execv("/bin/ps", argv);
 execvp("ps", argv);
 // 带p的,可以使⽤环境变量PATH,⽆需写全路径
 
// 带e的,需要⾃⼰组装环境变量
 
execve("/bin/ps", argv, envp);
 exit(0);
 }

事实上,只有execve是真正的系统调⽤,其它五个函数最终都调⽤execve,所以execve在man⼿册第2节, 其它函数在man⼿册第3节。这些函数之间的关系如下图所⽰。

下图exec函数簇⼀个完整的例⼦:

在这里插入图片描述

⾃主Shell命令⾏解释器

⽬标

  • 要能处理普通命令
  • 要能处理内建命令
  • 要能帮助我们理解内建命令/本地变量/环境变量这些概念
  • 要能帮助我们理解shell的允许原理

实现原理

考虑下⾯这个与shell典型的互动:

 [root@localhost epoll]# ls
 client.cpp  readme.md  server.cpp  utility.h
 [root@localhost epoll]# ps
 PID TTY          TIME CMD
 3451 pts/0    3514 pts/0    
00:00:00 bash 00:00:00 ps

⽤下图的时间轴来表⽰事件的发⽣次序。其中时间从左向右。shell由标识为sh的⽅块代表,它随着时 间的流逝从左向右移动。shell从⽤⼾读⼊字符串"ls"。shell建⽴⼀个新的进程,然后在那个进程中运 ⾏ls程序并等待那个进程结束。

在这里插入图片描述

然后shell读取新的⼀⾏输⼊,建⽴⼀个新的进程,在这个进程中运⾏程序并等待这个进程结束。

所以要写⼀个shell,需要循环以下过程:

  1. 获取命令⾏
  2. 解析命令⾏
  3. 建⽴⼀个⼦进程(fork)
  4. 替换⼦进程(execvp)
  5. ⽗进程等待⼦进程退出(wait)

根据这些思路,和我们前⾯的学的技术,就可以⾃⼰来实现⼀个shell了。

源码

实现代码:

 #include <iostream>
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
 #include <string>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <ctype.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;
 // 我的系统的环境变量
 
char *genv[envnum];
 // 全局的当前shell⼯作路径
  
char pwd[basesize];
 char pwdenv[basesize];
 // "    "file.txt
 #define TrimSpace(pos) do{\
 while(isspace(*pos)){\
 pos++;\
 }\
 }while(0)
 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(nullptr == getcwd(pwd, sizeof(pwd))) 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()
 {
 string curr = GetPwd();
 if(curr == "/" || curr == "None") return curr;
 // /home/whb/XXX
 size_t pos = curr.rfind("/");
 if(pos == std::string::npos) return curr;
 return curr.substr(pos+1);
 }
 string MakeCommandLine()
 {
 // [whb@bite-alicloud myshell]$ 
char command_line[basesize];
 snprintf(command_line, basesize, "[%s@%s %s]# ",\
 GetUserName().c_str(), GetHostName().c_str(), LastDir().c_str());
 return command_line;
 }
 void PrintCommandLine() // 1. 命令⾏提⽰符 
{
 }
 printf("%s", MakeCommandLine().c_str());
 fflush(stdout);
 bool GetCommandLine(char command_buffer[], int size)   // 2. 获取⽤⼾命令
 
{
 }
 // 我们认为:我们要将⽤⼾输⼊的命令⾏,当做⼀个完整的字符串
 
// "ls -a -l -n"
 char *result = fgets(command_buffer, size, stdin);
 if(!result)
 {
 return false;
 }
 command_buffer[strlen(command_buffer)-1] = 0;
 if(strlen(command_buffer) == 0) return false;
 return true;
 void ParseCommandLine(char command_buffer[], int len) // 3. 分析命令
{
    (void)len;
    memset(gargv, 0, sizeof(gargv));
    gargc = 0;
    // "ls -a -l -n"
    const char *sep = " ";
    gargv[gargc++] = strtok(command_buffer, sep);
    // =是刻意写的
 
    while((bool)(gargv[gargc++] = strtok(nullptr, sep)));
    gargc--;
 }
 void debug()
 {
    printf("argc: %d\n", gargc);
    for(int i = 0; gargv[i]; i++)
    {
        printf("argv[%d]: %s\n", i, gargv[i]);
    }
 }
 // 在shell中
 
// 有些命令,必须由⼦进程来执⾏
 
// 有些命令,不能由⼦进程执⾏,要由shell⾃⼰执⾏ --- 内建命令 built command 
bool ExecuteCommand()   // 4. 执⾏命令
 {
    // 让⼦进程进⾏执⾏
 
    pid_t id = fork();
    if(id < 0) return false;
    if(id == 0)
    {
        //⼦进程
 
        // 1. 执⾏命令
 
        execvpe(gargv[0], gargv, genv);
        // 2. 退出
 
        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(const 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] = nullptr;
 }
 // shell⾃⼰执⾏命令,本质是shell调⽤⾃⼰的函数
 
bool CheckAndExecBuiltCommand()
 {
    if(strcmp(gargv[0], "cd") == 0)
    {
        // 内建命令
 
        if(gargc == 2)
        {
            chdir(gargv[1]);
            lastcode = 0;
        }
        else
        {
            lastcode = 1;
        }
        return true;
    }
    else if(strcmp(gargv[0], "export") == 0)
    {
        // export也是内建命令
 
        if(gargc == 2)
        {
            AddEnv(gargv[1]);
            lastcode = 0;
        }
        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)
        {
            // echo $?
            // echo $PATH
            // echo hello
            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,获取环境变量应该从系统的配置来
 
// 我们今天就直接从⽗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] = nullptr;
 }
 int main()
 {
 InitEnv();
 char command_buffer[basesize];
 while(true)
 {
 PrintCommandLine(); // 1. 命令⾏提⽰符
 
// command_buffer -> output
 if( !GetCommandLine(command_buffer, basesize) )   // 2. 获取⽤⼾命令
 
{
 continue;
 }
 //printf("%s\n", command_buffer);
 ParseCommandLine(command_buffer, strlen(command_buffer)); // 3. 分析命令
 
if ( CheckAndExecBuiltCommand() )
 {
 continue;
 }
 ExecuteCommand();   // 4. 执⾏命令
 
}
 return 0;
 }

总结

函数和进程之间的相似性

exec/exit就像call/return

⼀个C程序有很多函数组成。⼀个函数可以调⽤另外⼀个函数,同时传递给它⼀些参数。被调⽤的函数 执⾏⼀定的操作,然后返回⼀个值。每个函数都有他的局部变量,不同的函数通过call/return系统进 ⾏通信。

这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux⿎励将这 种应⽤于程序之内的模式扩展到程序之间。如下图

在这里插入图片描述

⼀个C程序可以fork/exec另⼀个程序,并传给它⼀些参数。这个被调⽤的程序执⾏⼀定的操作,然后 通过exit(n)来返回值。调⽤它的进程可以通过wait(&ret)来获取exit的返回值。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

安全关系型数据库查询新选择:Rust 语言的 rust-query 库深度解析

在当今这个数据驱动的时代&#xff0c;数据库作为信息存储和检索的核心组件&#xff0c;其重要性不言而喻。然而&#xff0c;对于开发者而言&#xff0c;如何在保证数据安全的前提下&#xff0c;高效地进行数据库操作却是一项挑战。传统的 SQL 查询虽然强大&#xff0c;但存在诸…

读取电视剧MP4视频的每一帧,检测出现的每一个人脸并保存

检测效果还不错,就是追踪有点难做 import cv2 import mediapipe as mp import os from collections import defaultdict# pip install msvc-runtime# 初始化OpenCV的MultiTracker # multi_tracker = cv2.MultiTracker_create() # multi_tracker = cv2.legacy.MultiTracker_cre…

【AI系统】Transformer 模型小型化

Transformer 模型小型化 自 Vision Transformer 出现之后&#xff0c;人们发现 Transformer 也可以应用在计算机视觉领域&#xff0c;并且效果还是非常不错的。但是基于 Transformer 的网络模型通常具有数十亿或数百亿个参数&#xff0c;这使得它们的模型文件非常大&#xff0…

hhdb数据库介绍(10-43)

安全 密码安全管理 密码安全管理为用户提供了对计算节点数据库用户与存储节点的连接用户、备份用户的密码有效期监控提醒。到期后自动提示用户修改密码以提升系统的安全性。 数据库用户密码 &#xff08;一&#xff09;密码修改 用户可以在“安全->密码安全管理->数据…

MagicAnimate 技术浅析(五):视频融合策略浅析

视频融合策略&#xff08;Video Fusion Strategy&#xff09;是 MagicAnimate 中用于处理长视频动画生成的关键组件。它通过将长视频分解为多个重叠的片段&#xff0c;并在推理过程中对重叠帧的预测结果进行融合&#xff0c;确保生成的长视频动画在时间上平滑过渡&#xff0c;避…

【SNIP】《An Analysis of Scale Invariance in Object Detection – SNIP》

CVPR-2018 Singh B, Davis L S. An analysis of scale invariance in object detection snip[C]//Proceedings of the IEEE conference on computer vision and pattern recognition. 2018: 3578-3587. https://github.com/bharatsingh430/snip?tabreadme-ov-file 文章目录 …

GPS周和周内秒 UTC时 格林尼治时间

1.GPS周和周内秒介绍 GPS周和周内秒是全球定位系统&#xff08;GPS&#xff09;中用于时间表示的两个重要概念&#xff0c;它们共同构成了GPS时间系统。以下是对这两个概念的详细介绍&#xff1a; GPS周&#xff08;GPS Week&#xff09; GPS周是GPS系统内部所采用的时间单位…

探索JavaScript数组API:提升你的编程效率

大家好&#xff0c;今天我们来聊聊JavaScript中数组的常用API。数组是JavaScript中非常重要的一种数据结构&#xff0c;掌握数组的API对于提高编程效率具有重要意义。以下是一些实用的JavaScript数组API&#xff0c;让我们一起来看看吧&#xff01; 一、创建数组 1、使用Arra…

PHP Paypal支付restful API接口集成插件教程

最近在做一个PHP外贸独立站&#xff0c;想集成PayPal在线支付&#xff0c;于是就想把PayPal做成一个插件。下面就教大家如何一步步来开发PayPal整个流程&#xff0c;有需要的朋友点赞收藏&#xff0c;或下载本插件代码参考。 Paypal接口申请 必须是企业认证的帐号才能申请在…

Visual Studio开发lua脚本环境搭建

在Visual Studio上开发lua脚本环境搭建 1、下载lua的jdk安装&#xff0c;以及环境变量配置 下载LuaForWindows_v5.1.5-52.exe安装&#xff0c; 安装好之后&#xff0c;检查是否路径自动。 下载地址&#xff1a; https://github.com/rjpcomputing/luaforwindows/releases (1…

MySQL 性能优化详解

MySQL 性能优化详解 硬件升级系统配置优化调整buffer_pool数据预热降低日志的磁盘落盘 表结构设计优化SQL语句及索引优化SQL优化实战案例 MySQL性能优化我们可以从以下四个维度考虑&#xff1a;硬件升级、系统配置、表结构设计、SQL语句和索引。 从成本上来说&#xff1a;硬件升…

智已汽车x-signature 登录算法 签到

智已汽车x-signature 登录算法 签到 python代码成品

Android 使用 Canvas 和 Paint 实现圆角图片

学习笔记 效果展示: 全部代码: public class YuanActivity extends AppCompatActivity {private ActivityYuanBinding binding;Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 通过 DataBinding 获取布局文件binding …

掌控时间,成就更好的自己

在个人成长的道路上&#xff0c;时间管理是至关重要的一环。有效的时间管理能够让我们更加高效地完成任务&#xff0c;实现自己的目标&#xff0c;不断提升自我。 时间对每个人都是公平的&#xff0c;一天只有 24 小时。然而&#xff0c;为什么有些人能够在有限的时间里做出卓…

flask-socketio相关总结

flask-socketio是一个为flask应用程序添加的实时双向通信功能的扩展库&#xff0c;有了这个库&#xff0c;就可以在flask应用中应用websocket协议&#xff0c;帮助flask实现低延迟、双向的客户端、服务端通信。客户端通过任何SocketIO官方库&#xff0c;都能与服务器建立长连接…

YOLOv8改进,YOLOv8引入CARAFE轻量级通用上采样算子,助力模型涨点

摘要 CARAFE模块的设计目的是在不增加计算复杂度的情况下,提升特征图的质量,特别是在视频超分辨率任务中,提升图像质量和细节。CARAFE结合了上下文感知机制和聚合特征的能力,通过动态的上下文注意力机制来提升细节恢复的效果。 理论介绍 传统的卷积操作通常依赖于局部区域…

如何把阿里云ECS里的文件下载到本地(免登录免配置)

如何把阿里云ECS里的文件下载到本地&#xff08;免登录免配置&#xff09; 作为一个阿里云ECS的用户&#xff0c;Up时长会遇到希望把ECS里的文件下载到自己的个人电脑&#xff0c;然后在自己的电脑里面查看&#xff0c;保存或者发送给别人。最近发现阿里云新上了一个功能&…

【Notepad++】---设置背景为护眼色(豆沙绿)最新最详细

在编程的艺术世界里&#xff0c;代码和灵感需要寻找到最佳的交融点&#xff0c;才能打造出令人为之惊叹的作品。而在这座秋知叶i博客的殿堂里&#xff0c;我们将共同追寻这种完美结合&#xff0c;为未来的世界留下属于我们的独特印记。 【Notepad】---设置背景为护眼色&#xf…

【Axios】如何在Vue中使用Axios请求拦截器

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

在服务器上实现本地python文件的依赖

1、在python中&#xff0c;一个python文件就可以视为一个模块进行导入 2、使用import 导入时&#xff0c;若使用pip 下载过可以直接导入 3、假如是自己写的同项目中的文件会去sys.path 中查找 比如说 我现在 test 下有一个 python文件 运行 下面的代码 打印的数据如上图所示p…