Linux进程概念(七):进程替换 自主shell的编写

news2024/11/22 19:19:23

目录

进程替换

六种替换函数

自主shell的编写

shell解析命令行字符串的过程

创建makefile

打印输出命令行

获取与分割命令行字符串

多次执行命令 

完善工作

完整代码


进程替换

原因:fork创建子进程后,执行的是和父进程相同的代码,但有时可能需要子进程执行不同的代码分支,故子进程往往要调用exec函数来执行另一个程序

基本概念:新程序的代码和数据替换(覆盖)原进程的代码和数据,对原进程的PCB会有稍许修改

注意事项:在一个进程替换后不会创建新进程 

单进程发生替换时: 

 多进程发生替换时:

  • 也是execl函数的使用 ,当然除了调用linux自身的程序,还可以调用自己写的程序,比如execl("自定义程序路径","自定义程序名称",NULL)

六种替换函数

基本概念:有六种以exec开头的函数,统称exec函数

包含头文件:<unistd.h>

返回值:调用成功则直接加载新程序代码不返回,调用失败则返回-1

注意事项:不管是数组还是列表都以NULL结尾,只有execve函数是系统调用接口,其余五个替换函数是execve函数的封装(为了支持不同的应用场景)

int execl(const char *path, const char *arg, ...);
//(要执行程序的路径,以命令列表的形式决定如何执行该程序)

int execlp(const char *file, const char *arg, ...);
//(要执行程序的名称,以命令列表的形式决定如何执行该程序),编译器自动寻找路径

int execv(const char *path, char *const argv[]);
//(要执行程序的路径,以字符指针数组的形式决定如何执行该程序)

int execvp(const char *file, char *const argv[]);
//(要执行程序的名称,以字符指针数组的形式决定如何执行该程序),编译器自动寻找路径

int execle(const char *path, const char *arg, ...,char *const envp[]);
//(要执行程序的名称,以字符指针数组的形式决定如何执行该程序,选择环境变量)

//系统调用接口:
int execve(const char *path, char *const argv[], char *const envp[]);
//(要执行程序的路径,以字符指针数组的形式决定如何执行该程序,选择环境变量)
l(list)参数采用命令列表的形式
v(vector)参数采用数组的形式
p(path)编译器自动搜索执行路径
e(env)用户自行维护环境变量

execv函数:

execvp函数(execlp函数用法相同):

execle函数(execlpe函数用法相同):

  • putenv:向环境变量表中新增环境变量
  • getenv:获取当前环境变量表中的环境变量

自主shell的编写

shell解析命令行字符串的过程

创建makefile

打印输出命令行

命令行 = [当前用户名 + 当前主机名 + 当前目录名] >

snprintf函数:

函数原型:int snprintf(char *str, size_t size, const char *format, ...)

参数:目标字符串,写入数据的最大长度(包括\0),要写入的字符串

返回值:预写入字符串的长度,而非实际写入字符串长度

功能:将一个或多个字符串格式化后写入指定数组

注意事项:指定数组要足够大,防止内存泄漏

获取与分割命令行字符串

fgets函数:

函数原型:char *fgets(char *str,int num,FILE *stream);

包含头文件:<stdio.h>

参数:要写入的字符数组,读取num个字符直至到达换行符或文件末尾,文件流指针

返回值:执行成功时返回读取的字符串的指针,如果到达文件末尾或发生错误,则返回NULL

功能:按行获取

注意事项:换行符会使 fgets 停止读取,但它仍被函数视为有效字符,并包含在复制到 str 的字符串中,同时读取结束后会自动添加\0

strtok函数:

函数原型:char * strtok(char * str,const char * delimiters);

包含头文件:<string.h>

参数:源字符串,分割标识字符串

返回值:分割后的字符串的首地址

功能:依据指定标志分割字符串

注意事项:分割标识是字符串不是字符,且在 strtok() 函数的后续调用中,需要将第一个参数设置为 NULL ,它可以让函数从上一次结束位置开始查找下一个匹配项(函数内的特殊实现)

多次执行命令 

完善工作

问题1:无法切换路径

原因:不能识别内建命令,cd指令还是子进程在执行,但实际应该是父进程执行的

chdir函数:

函数原型: int chdir(const char *path);

头文件:<unistd.h>

参数:path可以是绝对目录或者相对目录

返回值:成功返回0,失败返回-1

功能:改变当前工作路径

getcwd函数:

函数原型:char *getcwd(char *buf,size_t size);

头文件:<unistd.h>

参数:存放当前工作目录的绝对路径的字符指针,绝对路径大小

返回值:指向绝对路径的字符指针

功能:获取当前目录的绝对路径的大小 

在已经定义了<stdlib.h>头文件的前提下却仍然报错“implicit declaration of function ‘putenv’”则可以在包含头文件前定义_SVID_SOURCE 或 _XOPEN_SOURCE:

#define _XOPEN_SOURCE
#include <stdlib.h>

或者在编译时(使用-D),比如:

gcc -o output file.c -D_XOPEN_SOURCE

有点太复杂了懒得描述了,直接上源码 

完整代码

#define _XOPEN_SOURCE//用于处理putenv函数缺少声明的问题,且必须定义在stdlib.h之前
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>


#define SIZE 512 //命令行最多字符数
#define NUM 32  //命令行指令数组中最多的指令数
#define TAG " " //标识符
#define ZERO '\0' //处理双重回车问题
#define SkipPath(p) do{p += strlen(p)-1; while(*p != '/') p--;}while(0)//循环遍历到只打印工作路径的最右值
//该函数处理的是传入指针的指向,原来该指针指向一大串字符串,现在我们只让它指向其中最右的一个子串

char cwd[SIZE*2];//环境变量 
int lastcode = 0;//错误码
char *Argv[NUM];//命令行字符串数组

//获取当前用户名接口
const char * GetUserName()
{
    const char * name = getenv("USER");
    if(name == 0) return "None";
    return name;
}


//获取当前主机名接口
const char * GetHostName()
{
    const char * hostname = getenv("HOSTNAME");
    if(hostname == 0) return "None";
    return hostname;
}


//获取当前工作路径接口
const char * GetCwd()
{
    const char * cwd = getenv("PWD");
    if(cwd == 0) return "None";
    return cwd;
}


//组合命令行并打印
void MakeCommandLineAndPrint()
{
    char line[SIZE];
    const char *username = GetUserName();
    const char *hostname = GetHostName();
    const char *cwd = GetCwd();


    //以格式化的形式向命令行数组中写入
    SkipPath(cwd);//指向获取当前工作路径的最右侧的子串内容
    snprintf(line,sizeof(line),"[%s@%s %s]> ",username,hostname,strlen(cwd) == 1 ? "/" : cwd + 1);
    //如果子串长度为1,则cwd是\,如果不为1则打印/后的内容
    printf("%s",line);
    fflush(stdout);
    printf("\n");
}

//获取命令行字符串接口
int GetUserCommand(char usercommand[],size_t n)
{
    char * str = fgets(usercommand,n,stdin);
    if(str == NULL)return -1;
   usercommand[strlen(usercommand)-1] = ZERO;//输入命令行字符串时会多输入一个\n,即字符串+'\n'+'\0',这会导致多打一行,现在我们在获取完全部字符串后直接在该字符串后加上'\0'覆盖掉读取到的'\n'
    return strlen(usercommand);
}

//分割字符串接口
void SplitCommand(char usercommand[],size_t n)
{
    Argv[0] = strtok(usercommand,TAG);
    int index = 1;
    while((Argv[index++] = strtok(NULL,TAG)));//循环赋值并判断
}

//执行命令接口
void ExecuteCommand()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child
        execvp(Argv[0],Argv);//(要执行的程序,怎么执行该程序)
        exit(errno);//execvp函数替换失败后会返回一个错误码,退出的退出码就是该错误码
    }
    else
    {
        //father
        int status = 0;
        pid_t rid = waitpid(id,&status,0);//父进程阻塞等待
        if(rid > 0)
        {
            lastcode = WEXITSTATUS(status);//子进程的退出码lastcode
            if(lastcode != 0)printf("%s:%s:%d\n",Argv[0],strerror(lastcode),lastcode);
        }
    }
}

//获取家目录接口
const char * GetHome()
{
    const char * home = getenv("HOME");
    if(home == NULL) return "/";//找不到就放到根目录下
    return home;//根目录不为空则返回家目录
}


//执行内建命令cd
void Cd()
{
    const char *path = Argv[1];//选择怎么执行cd程序
    if(path == NULL)//如果只有一个单独的cd指令,则path存放家目录信息
        path = GetHome();
    chdir(path);//更改当前工作目录
    
    //刷新环境变量(即刷新[]最后的一个内容)
    char temp[SIZE*2];//临时缓冲区
    getcwd(temp,sizeof(temp));//获取当前工作目录的绝对路径
    snprintf(cwd,sizeof(cwd),"PWD=%s",temp);
    putenv(cwd);//向环境变量表中写入
}

//检测是否是内建指令
int CheckBuildin()
{
    int yes = 0;
    const char * enter_cmd = Argv[0];//指向数组中存放的要执行的程序名
    if(strcmp(enter_cmd,"cd") == 0)
    {
        yes = 1;
        Cd();//是内建命令就执行
    }
    else if(strcmp(enter_cmd,"echo") == 0 && strcmp(Argv[1],"$?") == 0) 
    {
        yes = 1;
        printf("%d\n",lastcode);//打印退出码
        lastcode = 0;
    }
    return yes;//返回判断结果
}


int main()
{
    int quit = 0;
    while(quit == 0)//多次执行命令
    {
        //1、获取命令行并打印
        MakeCommandLineAndPrint();

        //2、获命令行取字符串
        char usercommand[SIZE];
        int n = GetUserCommand(usercommand,sizeof(usercommand));
        if(n <= 0) return 1;//获取字符串失败或者获取字符串长度为0时退出,退出码为1
    
        //3、分割命令行字符串
        SplitCommand(usercommand,sizeof(usercommand));

        //4、检测是否是内建命令
        n = CheckBuildin();//这里的指令还是由父进程执行的
        if(n) continue;//如果是内建命令就在检测函数中执行完内建目命令后返回循环开始处,而不是执行普通命令

        //5、执行命令
        ExecuteCommand();//此时才会创建子进程
    }   
}

~over~

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

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

相关文章

Spring6 当中 获取 Bean 的四种方式

1. Spring6 当中 获取 Bean 的四种方式 文章目录 1. Spring6 当中 获取 Bean 的四种方式每博一文案1.1 第一种方式&#xff1a;通过构造方法获取 Bean1.2 第二种方式&#xff1a;通过简单工厂模式获取 Bean1.3 第三种方式&#xff1a;通过 factory-bean 属性获取 Bean1.4 第四种…

一例MFC文件夹病毒的分析

概述 这是一个MFC写的文件夹病毒&#xff0c;通过感染USB设备传播&#xff0c;感染后&#xff0c;会向c2(fecure.info:443)请求指令来执行。 样本的基本信息 Verified: Unsigned Link date: 19:52 2007/7/5 MachineType: 32-bit MD5: 4B463901E5858ADA9FED28FC5…

基于SpringBoot+Vue笔记记录分享网站设计与实现

项目介绍&#xff1a; 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代…

Docker-Compose单机多容器应用编排与管理

前言 Docker Compose 作为 Docker 生态系统中的一个重要组件&#xff0c;为开发人员提供了一种简单而强大的方式来定义和运行多个容器化应用。本文将介绍 Docker Compose 的使用背景、优劣势以及利用 Docker Compose 简化应用程序的部署和管理。 目录 一、Docker Compose 简…

闲话 Asp.Net Core 数据校验(三)EF Core 集成 FluentValidation 校验数据例子

前言 一个在实际应用中 EF Core 集成 FluentValidation 进行数据校验的例子。 Step By Step 步骤 创建一个 Asp.Net Core WebApi 项目 引用以下 Nuget 包 FluentValidation.AspNetCore Microsoft.AspNetCore.Identity.EntityFrameworkCore Microsoft.EntityFrameworkCore.Re…

leetcode51.N皇后(困难)-回溯法

思路 都知道n皇后问题是回溯算法解决的经典问题&#xff0c;但是用回溯解决多了组合、切割、子集、排列问题之后&#xff0c;遇到这种二维矩阵还会有点不知所措。 首先来看一下皇后们的约束条件&#xff1a; 不能同行不能同列不能同斜线 确定完约束条件&#xff0c;来看看究…

【Linux】yum、vim

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343&#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/qinjh_/category_12625432.html 目录 Linux 软件包管理器 yum 什么是软件包 查看软件包 如何安装软件 如何卸载软…

Apache Seata基于改良版雪花算法的分布式UUID生成器分析1

title: Seata基于改良版雪花算法的分布式UUID生成器分析 author: selfishlover keywords: [Seata, snowflake, UUID] date: 2021/05/08 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 Seata基于改良版雪花算法的分布式UUID生成器分析…

浅析扩散模型与图像生成【应用篇】(十八)——ControlNet

18. Adding Conditional Control to Text-to-Image Diffusion Models 现有的文生图模型如Stable Diffusion通常需要人工输入非常准确的提示词&#xff0c;而且生成的结果还是完全随机不可控制的&#xff0c;只能通过生成多个结果&#xff0c;再从中选取最佳方案。而ControlNet的…

【项目】仿muduo库One Thread One Loop式主从Reactor模型实现高并发服务器(Http测试板块)

【项目】仿muduo库One Thread One Loop式主从Reactor模型实现高并发服务器&#xff08;Http测试板块&#xff09; 一、使用Http网页界面1、main.cc原码和index.html原码2、运行结果&#xff08;1&#xff09;测试结果1&#xff1a;用index.html内部的代码&#xff08;2&#xf…

JSON.toJSONString() 输出 “$ref“:“$[0]“问题解决及原因分析

一、背景 在构建一个公共的批处理方法类的时候&#xff0c;在测试输出的时候&#xff0c;打印了" r e f " : " ref":" ref":"[0][0]"的内容&#xff0c;这让我比较疑惑。不由得继续了下去… 二、问题分析 首先&#xff0c;我们需要…

MySQL Binlog 闪回与分析

文章目录 前言1. 修改 event 实现闪回1.1 binlog 结构1.2 闪回案例1.3 方法总结 2. 解析文本闪回2.1 mysqlbinlog2.2 闪回案例2.3 方法总结 3. 在线订阅闪回3.1 mysql-replication3.2 binlog2sql3.3 方法总结 4. Binlog 分析方法4.1 分析场景4.2 辅助定位事务4.3 方法总结 5. 平…

性能监控之prometheus+grafana搭建

前言 Prometheus和Grafana是两个流行的开源工具&#xff0c;用于监控和可视化系统和应用程序的性能指标。它们通常一起使用&#xff0c;提供了强大的监控和数据可视化功能。 Prometheus Prometheus是一种开源的系统监控和警报工具包。它最初由SoundCloud开发&#xff0c;并于…

基于SSM+Jsp+Mysql的汽车租赁系统的设计与实现

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…

Python爬虫(入门版)

1、爬虫是什么 简单的来说&#xff1a;就是用程序获取网络上数据。 2、爬虫的原理 如果要获取网络上数据&#xff0c;我们要给爬虫一个网址&#xff08;程序中通常叫URL&#xff09;&#xff0c;爬虫发送一个HTTP请求给目标网页的服务器&#xff0c;服务器返回数据给客户端&am…

Linux详解:进程等待

文章目录 进程等待等待的必要性进程等待的方法waitwaitpid获取子进程status阻塞等待 与 非阻塞等待 进程等待 等待的必要性 子进程退出&#xff0c;父进程不进行回收的话&#xff0c;就可能造成僵尸进程&#xff0c;进而造成内存泄露 如果进程进入了僵尸状态&#xff0c;kill…

宝塔面板安装教程(linux)

宝塔官网地址 宝塔官网linux安装地址 针对Ubuntu系统的安装命令&#xff1a; wget -O install.sh https://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh ed8484bec 安装过程中&#xff0c;中途会出现一个 Y&N ? 的选项&#xf…

李沐62_序列到序列学习seq2seq——自学笔记

"英&#xff0d;法”数据集来训练这个机器翻译模型。 !pip install --upgrade d2l0.17.5 #d2l需要更新import collections import math import torch from torch import nn from d2l import torch as d2l循环神经网络编码器。 我们使用了嵌入层&#xff08;embedding l…

【笔记1】从零开始做一个男头的流程(超级详细)

目录 大体 眼窝 鼻子 脖子 耳朵 嘴巴1 颧骨 嘴巴2 眼睛 头 开始细化 大体 眼窝 嘴巴 鼻子 大体 注意&#xff01;&#xff01;先整体后局部&#xff0c;一开始不要加太多的线&#xff0c;尽量先用最少的线调整出一个大体的结构。 1.准备好参考图&#xff0c;在…

2024年的Java版本选择?java 17 安装

文章目录 2024年的Java版本选择&#xff1f;java 1.8 和 java17 什么区别&#xff1f;java 17 安装windows 11安装java 17C:\Program Files\Common Files\Oracle\Java\javapath是什么 2024年的Java版本选择&#xff1f; 3年前&#xff0c;java 1.8是市场主流&#xff08;还有一…