從0到1實現Shell!Linux進程程序替換詳解

目錄

  • 從0到1實現Shell!Linux進程程序替換詳解 🚀
    • 引言:為什么進程需要"變身術"?
    • 一、程序替換:進程的"換衣服"魔法 🔄
      • 1.1 什么是程序替換?
      • 1.2 程序替換的原理:內存中的"乾坤大挪移"
      • 1.3 exec函數族:六種"換裝"姿勢 💃
      • 1.4 動手試試:讓進程"變身"執行ls命令
    • 二、fork+exec:Shell的"分身+換裝"秘籍 🧙?♂?
      • 2.1 為什么需要fork?
      • 2.2 fork+exec的經典組合
    • 三、手把手實現迷你Shell:命令行解釋器 🛠?
      • 3.1 Shell的工作流程
      • 3.2 實現步驟詳解
        • 步驟1:打印個性化提示符(帶簡化路徑)
        • 步驟2:讀取命令行輸入(帶空命令處理)
        • 步驟3:解析命令行參數(更簡潔的循環方式)
        • 步驟4:執行命令(標準fork+execvp流程)
      • 3.3 完整代碼:終極版迷你Shell!
      • 3.4 測試我們的終極版Shell!
    • 四、常見問題與進階方向 🚀
      • 4.1 為什么`DirName`函數要特殊處理根目錄?
      • 4.2 可以添加哪些高級功能?
    • 總結:從"玩具"到"工具"的進化

在這里插入圖片描述
🌟個人主頁 :L_autinue_Star
?
🌟當前專欄:c++進階


從0到1實現Shell!Linux進程程序替換詳解 🚀

引言:為什么進程需要"變身術"?

小伙伴們好呀!👋 在之前的博客里,我們聊了進程的概念和控制,知道了進程就像一個個獨立的"工作單元",在操作系統中忙碌地跑來跑去。但你有沒有想過:如果一個進程想"跳槽"去執行另一個程序,該怎么辦呢? 🤔

比如我們在終端輸入ls命令時,bash進程是怎么突然變成ls進程的?今天咱們就來揭開這個神秘面紗——聊聊進程程序替換,最后再手把手教你實現一個迷你版Shell!是不是超期待?😎

一、程序替換:進程的"換衣服"魔法 🔄

1.1 什么是程序替換?

想象一下:你正在扮演奧特曼打小怪獸(當前進程執行代碼),突然接到導演通知:“下一場演迪迦!”(需要執行新程序)。你不需要換個人(創建新進程),只需要當場換衣服、換劇本(替換代碼和數據)——這就是程序替換!

專業點說:用磁盤上的新程序,完全替換當前進程的代碼段和數據段,從新程序的main函數開始執行。進程ID不變,但"靈魂"已經煥然一新~

1.2 程序替換的原理:內存中的"乾坤大挪移"

在這里插入圖片描述

進程的地址空間就像一個"舞臺":

  • 原來的程序(如bash)在舞臺上表演(代碼段、數據段)
  • 調用exec函數后,新程序(如ls)會把原來的"道具"(代碼/數據)全部清走,換上自己的"行頭"
  • 但舞臺本身(進程PCB、PID)沒變,只是表演者換了

1.3 exec函數族:六種"換裝"姿勢 💃

Linux給我們提供了6個exec開頭的函數,統稱exec函數族。它們就像不同款式的"換裝魔法棒",用法略有不同但效果一致~

函數名特點栗子
execl參數是列表形式execl("/bin/ls", "ls", "-l", NULL)
execlp自動搜索PATH,不用寫全路徑execlp("ls", "ls", "-l", NULL)
execle自己傳環境變量execle("./myprog", "myprog", NULL, myenv)
execv參數是數組形式char* argv[] = {"ls", "-l", NULL}; execv("/bin/ls", argv)
execvp數組形式+自動搜PATHexecvp("ls", argv)
execve系統調用接口,最底層(其他函數最終調用它)

敲黑板:這些函數如果成功,就不會返回(因為代碼段已經被替換了!);只有失敗才返回-1。

1.4 動手試試:讓進程"變身"執行ls命令

💻 代碼示例

#include <unistd.h>
#include <stdio.h>int main() {printf("變身前:我是進程%d\n", getpid());// 用execlp執行ls -l(p表示自動搜PATH)execlp("ls", "ls", "-l", NULL);  // 注意最后一個參數必須是NULL!// 如果執行到這里,說明execlp失敗了perror("變身失敗");  // 打印錯誤原因return 1;
}

運行結果:
在這里插入圖片描述

🎉 看到了嗎?進程從打印"變身前"變成了執行ls -l!如果把execlp換成execl("/bin/ls", "ls", "-l", NULL)效果一樣~

二、fork+exec:Shell的"分身+換裝"秘籍 🧙?♂?

2.1 為什么需要fork?

細心的小伙伴會問:"如果程序替換會覆蓋當前進程,那bash自己豈不是就消失了?"🤔

沒錯!所以Shell執行命令時,會先fork一個子進程,然后在子進程中執行程序替換。這樣父進程(bash本身)就能安然無恙,繼續等待下一個命令~

這就像:餐廳服務員(bash)接到訂單(命令)后,不會自己去廚房做菜(執行程序),而是叫一個廚師(子進程)去做,自己繼續接待客人~

2.2 fork+exec的經典組合

💻 代碼示例

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>int main() {pid_t pid = fork();  // 創建子進程if (pid == 0) {  // 子進程printf("子進程%d:我要變身ls啦!\n", getpid());execlp("ls", "ls", "-l", NULL);exit(1);  // 如果execlp失敗,退出子進程} else if (pid > 0) {  // 父進程printf("父進程%d:等待子進程完成...\n", getpid());wait(NULL);  // 等待子進程退出(避免僵尸進程)printf("父進程%d:子進程干完活啦!\n", getpid());}return 0;
}

運行結果:
在這里插入圖片描述

三、手把手實現迷你Shell:命令行解釋器 🛠?

3.1 Shell的工作流程

一個簡易的Shell需要做三件事:

  1. 讀取命令:從終端讀取用戶輸入(如ls -l
  2. 解析命令:把命令拆分成可執行程序和參數(如程序"ls"參數"-l")
  3. 執行命令:fork子進程,在子進程中執行程序替換

就像餐廳點餐流程:記錄訂單(讀命令)→ 分析菜品 (解析)→ 廚師做菜(執行)

3.2 實現步驟詳解

步驟1:打印個性化提示符(帶簡化路徑)

專業的Shell會顯示用戶名@主機名 簡化路徑(如[user@localhost ~]# )。我們新增DirName函數提取路徑最后一部分:

#include <string>
using namespace std;#define FORMAT "[%s@%s %s]# "  // 提示符格式宏// 提取路徑最后一部分(如"/home/user" → "user")
string DirName(const char *pwd) {string dir = pwd;if (dir == "/") return "/";  // 根目錄特殊處理auto pos = dir.rfind("/");   // 查找最后一個斜杠return dir.substr(pos + 1);  // 返回斜杠后的部分
}// 生成并打印提示符
void PrintCommandPrompt() {char prompt[COMMAND_SIZE];snprintf(prompt, sizeof(prompt), FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());printf("%s", prompt);fflush(stdout);  // 確保提示符立即顯示
}
步驟2:讀取命令行輸入(帶空命令處理)
bool GetCommandLine(char *out, int size) {char *c = fgets(out, size, stdin);  // 讀取一行輸入if (c == NULL) return false;        // 處理Ctrl+D退出out[strlen(out)-1] = '\0';          // 去掉換行符return strlen(out) > 0;             // 過濾空命令
}
步驟3:解析命令行參數(更簡潔的循環方式)
#define MAXARGC 128
char* g_argv[MAXARGC];  // 參數數組
int g_argc = 0;         // 參數個數bool CommandParse(char *commandline) {g_argc = 0;g_argv[g_argc++] = strtok(commandline, " ");  // 第一個參數while ((g_argv[g_argc++] = strtok(nullptr, " ")));  // 循環提取后續參數g_argc--;  // 修正最后一個NULL的計數return true;
}
步驟4:執行命令(標準fork+execvp流程)
int Execute() {pid_t id = fork();if (id == 0) {  // 子進程execvp(g_argv[0], g_argv);  // 執行程序替換exit(1);                    // 替換失敗才會執行}// 父進程等待子進程waitpid(id, nullptr, 0);return 0;
}

3.3 完整代碼:終極版迷你Shell!

💻 myshell.cpp(支持簡化路徑顯示+模塊化設計):

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>#define COMMAND_SIZE 1024  // 命令最大長度
#define MAXARGC 128        // 參數最大個數
#define FORMAT "[%s@%s %s]# "  // 提示符格式:[用戶名@主機名 路徑]#// 全局參數數組
char* g_argv[MAXARGC];
int g_argc = 0;// 獲取用戶名(從環境變量)
const char* GetUserName() {const char* name = getenv("USER");return name ? name : "None";
}// 獲取主機名(從環境變量)
const char* GetHostName() {const char* hostname = getenv("HOSTNAME");return hostname ? hostname : "None";
}// 獲取當前工作目錄(從環境變量)
const char* GetPwd() {const char* pwd = getenv("PWD");return pwd ? pwd : "None";
}// 提取路徑最后一部分(簡化顯示)
std::string DirName(const char* pwd) {std::string dir = pwd;if (dir == "/") return "/";  // 根目錄特殊處理size_t pos = dir.rfind("/");return (pos != std::string::npos) ? dir.substr(pos + 1) : dir;
}// 生成命令提示符
void MakeCommandLine(char cmd_prompt[], int size) {snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
}// 打印命令提示符
void PrintCommandPrompt() {char prompt[COMMAND_SIZE];MakeCommandLine(prompt, sizeof(prompt));printf("%s", prompt);fflush(stdout);  // 刷新緩沖區,確保提示符顯示
}// 獲取用戶輸入的命令
bool GetCommandLine(char* out, int size) {char* c = fgets(out, size, stdin);if (!c) return false;  // 處理Ctrl+D退出out[strlen(out) - 1] = '\0';  // 移除換行符return strlen(out) > 0;       // 忽略空命令
}// 解析命令行參數
bool CommandParse(char* commandline) {g_argc = 0;g_argv[g_argc++] = strtok(commandline, " ");  // 第一個參數while ((g_argv[g_argc++] = strtok(nullptr, " ")));  // 循環提取剩余參數g_argc--;  // 修正最后一個NULL的計數return true;
}// 執行命令
int Execute() {pid_t id = fork();if (id == 0) {  // 子進程execvp(g_argv[0], g_argv);  // 執行程序替換perror("command not found");  // 替換失敗時提示exit(1);} else if (id > 0) {  // 父進程waitpid(id, nullptr, 0);  // 等待子進程結束}return 0;
}int main() {while (true) {// 1. 打印命令提示符PrintCommandPrompt();// 2. 獲取命令行輸入char commandline[COMMAND_SIZE];if (!GetCommandLine(commandline, sizeof(commandline)))continue;// 3. 解析命令行參數CommandParse(commandline);// 4. 執行命令Execute();}return 0;
}

3.4 測試我們的終極版Shell!

編譯運行:
在這里插入圖片描述
我這里并未將dirname函數接入方便和原生的shell更好區別

? 終極版特性亮點

  • 智能路徑顯示:自動提取路徑最后一部分(如/home/user/Desktop顯示為Desktop),提示符更清爽!
  • 模塊化設計:拆分為MakeCommandLineGetCommandLine等函數,代碼可讀性UP!
  • 健壯性提升:過濾空命令輸入,處理Ctrl+D優雅退出
  • 錯誤提示:命令不存在時顯示command not found

四、常見問題與進階方向 🚀

4.1 為什么DirName函數要特殊處理根目錄?

如果當前路徑是/(根目錄),rfind("/")會返回0,substr(1)會得到空字符串。所以需要單獨判斷,確保根目錄顯示為/而不是空白~

4.2 可以添加哪些高級功能?

這些高級功能我們將在后續文章中逐步實現,包括內置命令(如cd/exit)、輸入輸出重定向、管道等核心特性,敬請期待哦!🚀

總結:從"玩具"到"工具"的進化

今天我們不僅學習了:

  • 程序替換的核心原理(exec函數族的使用)
  • fork+exec的經典組合(Shell的實現基石)
    還親手實現了一個帶智能路徑顯示的模塊化Shell

這個Shell雖然簡單,但已經包含了真實Shell的核心骨架。進程管理是Linux系統編程的靈魂,而親手實現Shell能幫你打通"進程→程序替換→用戶交互"的任督二脈!👊

有問題歡迎在評論區留言哦~ 下次見!😉

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/91449.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/91449.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/91449.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

暑期算法訓練.2

目錄 6.力扣 11.盛水最多的容器 6.1 題目解析&#xff1a; 6.2 算法思路&#xff1a; 6.2.1 暴力解法&#xff1a; 6.2.2 優化算法&#xff1a; 6.3 代碼演示&#xff1a; ?編輯 6.4 總結反思&#xff1a; 7.力扣 611.有效的三角形個數 7.1 題目解析&#xff1a; 7.2…

華為OD 消消樂游戲

1. 題意 游戲規則&#xff1a;輸入一個只包含英文字母的字符串&#xff0c;字符串中的兩個字母如果相鄰且相同&#xff0c;就可以消除。 在字符串上反復執行消除的動作&#xff0c;直到無法繼續消除為止&#xff0c;此時游戲結束。 輸出最終得到的字符串長度。 輸入 輸入原始…

小白學HTML,操作HTML文件篇(2)

目錄 一、添加多媒體 1.添加網頁圖片 2.添加網頁音頻 3.添加網頁視頻 二、創建容器 1. 標簽 2.布局 三、創建表格 1.表格標簽 2.添加表格表頭 3.添加表格標題 一、添加多媒體 在 HTML 網頁中可以輕松地使用標簽來添加圖片、音頻、視頻等多媒體&#xff0c;而這些多媒體并…

微服務架構中實現跨服務的字段級權限統一控制

結合集中式權限管理、分布式上下文傳遞、動態策略執行等技術 ??一、核心架構設計?? ??1. 分層控制模型?? ??網關層??:統一校驗用戶身份與基礎權限,攔截非法請求。 ??服務層??:基于用戶權限動態過濾數據字段,實現業務級控制。 ??策略中心??:集中管理權…

【實現100個unity特效之27】使用unity的ShaderGraph實現一個帶裁剪邊緣光的裁剪效果(2d3d通用)

文章目錄普通裁剪效果1、創建一個Lit Shader Graph2、ShaderGraph前置配置3、添加節點4、效果5、修改裁剪方向帶邊緣色的裁剪1、在裁剪的基礎上添加裁剪邊緣光2、邊緣的亮度3、修改裁剪方向4、效果5、我們可以代碼控制它的變化&#xff0c;如下2D3D游戲通用專欄推薦完結普通裁剪…

Android Scoped Storage適配完全指南

Android Scoped Storage適配完全指南關鍵詞&#xff1a;Android、Scoped Storage、適配、存儲權限、文件訪問摘要&#xff1a;本文將全面介紹Android Scoped Storage的相關知識&#xff0c;從背景出發&#xff0c;詳細解釋核心概念&#xff0c;闡述其原理和架構&#xff0c;給出…

Typecho集成PHPMailer實現郵件訂閱功能完整指南

文章目錄 Typecho使用PHPMailer實現文章推送訂閱功能詳解 1. 背景與需求分析 1.1 為什么選擇PHPMailer 1.2 功能需求 2. 環境準備與配置 2.1 安裝PHPMailer 2.2 數據庫設計 3. 核心功能實現 3.1 郵件服務封裝類 3.2 訂閱功能實現 3.2.1 訂閱表單處理 3.2.2 確認訂閱處理 3.3 文…

無線-二層組網-直接轉發

文章目錄無線二層組網直接轉發&#x1f3e1;作者主頁&#xff1a;點擊&#xff01; &#x1f916;Datacom專欄&#xff1a;點擊&#xff01; ??創作時間&#xff1a;2025年07月16日08點00分 無線二層組網 直接轉發 本地轉發中所有的沿途都需要配置對應VLAN的通過&#xff…

gin go-kratos go-zero框架對比

Gin、Go-Kratos 和 Go-Zero 是 Go 語言中三種常見的服務框架&#xff0c;它們在定位、設計理念、復雜度和適用場景上差異較大。下面我們從功能定位、設計理念、優劣對比、使用建議等維度進行深入對比。&#x1f9ed; 一句話總結框架定位Gin輕量級、高性能的 HTTP 路由框架Go-Kr…

4G模塊 A7670發送英文短信到手機

命令說明ATi顯示產品的標志信息 ATCIMI查詢IMSI ATCICCID從SIM卡讀取ICCID ATCGSN查詢產品序列號 ATCPIN查詢卡狀態 ATCSQ查詢信號強度 ATCGATT查詢當前PS域狀態 ATCREG查詢GPRS注冊狀態 ATCEREG查詢4G注冊狀態 ATCGPADDR查詢PDP地址 ATCMGF選擇短信格式 ATCMGS發送短信流程第一…

歸并排序遞歸法和非遞歸法的簡單簡單介紹

基本思想&#xff1a; 歸并排序&#xff08;MERGE-SORT&#xff09;是建立在歸并操作上的一種有效的排序算法,該算法是采用分治法&#xff08;Divide and Conquer&#xff09;的一個非常典型的應用。將已有序的子序列合并&#xff0c;得到完全有序的序列&#xff1b;即先使每個…

webrtc之子帶分割下——SplittingFilter源碼分析

文章目錄前言一、頻帶分割過程1.SplittingFilter的創建2.頻帶分割整體流程1&#xff09;分割時機2&#xff09;分割規則3&#xff09;分割核心代碼3.頻帶合并二、算法實現1.實現原理介紹2.All pass QMF系統源碼1&#xff09;提高精度2&#xff09;經過串聯全通濾波器3&#xff…

Java運維之Tomcat升級

Tomcat升級準備工作 下述所有過程中,包含了兩種升級方式,一種是備份舊版本的 bin 和 lib,將新版本的 bin 和 lib 對舊版本進行覆蓋;另一種是直接備份舊版本的Tomcat包,運行新版本,將舊版本的配置文件(conf/ * )和應用(webapps/ * )等同步到新版本。 1. 到官網下載指…

MySQL的可重復讀隔離級別實現原理分析

MySQL 的 可重復讀&#xff08;Repeatable Read, RR&#xff09; 隔離級別主要通過 多版本并發控制&#xff08;Multi-Version Concurrency Control, MVCC&#xff09; 和 鎖機制&#xff08;特別是間隙鎖&#xff09; 來實現的。其核心目標是&#xff1a;在一個事務內&#xf…

利用Java自定義格式,循環導出數據、圖片到excel

利用Java自定義格式&#xff0c;循環導出數據、圖片到excel1、自定義格式循環導出數據1.1.設置格式1.1.1、居中樣式1.1.2、應用樣式到合并區域1.1.3、合并單元格1.1.4、設置列寬1.2、寫入數據1.2.1、創建標簽頭部1.2.2、寫入標簽內容2、自定義格式循環導出圖片2.1、設置格式并插…

SAP學習筆記 - 開發45 - RAP開發 Managed App New Service Definition,Metadata Extension

上一章講了在 Data Model View ( CDS View for BO Structure )基礎上創建 Projection View ( CDS View for BO Projection )。 SAP學習筆記 - 開發44 - RAP開發 Managed App 建 Projection View&#xff0c;Provider Contract&#xff0c;用 redirected to 設定父子關系-CSDN博…

React強大且靈活hooks庫——ahooks入門實踐之高級類hook(advanced)詳解

什么是 ahooks&#xff1f; ahooks 是一個 React Hooks 庫&#xff0c;提供了大量實用的自定義 hooks&#xff0c;幫助開發者更高效地構建 React 應用。其中高級類 hooks 是 ahooks 的一個重要分類&#xff0c;專門用于處理一些高級場景&#xff0c;如受控值、事件發射器、性能…

計算機網絡——數據鏈路層(25王道最新版)

數據鏈路層前言數據鏈路層的功能封裝成幀&#xff08;組幀&#xff09;字符計數法字節填充法零比特填充法違規編碼法小節差錯控制檢錯編碼奇偶校驗碼CRC校驗碼&#xff08;循環冗余校驗碼&#xff09;基本思想如何構造如何檢錯糾錯糾錯編碼海明校驗碼設計思路求解步驟&#xff…

【PTA數據結構 | C語言版】字符串替換算法

本專欄持續輸出數據結構題目集&#xff0c;歡迎訂閱。 文章目錄題目代碼題目 請編寫程序&#xff0c;將給定主串 s 中的子串 sub_s 替換成另一個給定字符串 t&#xff0c;再輸出替換后的主串 s。 輸入格式&#xff1a; 輸入給出 3 個非空字符串&#xff0c;依次為&#xff1a…

事物生效,訂單類內部更新訂單

代碼如下以下代碼1不生效&#xff0c;2生效解決方案1&#xff0c;外層方法加注解&#xff0c;內層不加2&#xff0c;不要拆分方法&#xff0c;把更新訂單操作放在帶事物的大方法中3&#xff0c;拆方法&#xff08;內部&#xff09;&#xff0c;注入自己&#xff0c;用代理對象調…