Linux进程替换与自定义shell详解
引言
进程替换和shell编程是Linux系统中极其重要的概念,它们不仅是系统内部工作的基础机制,也是系统管理员和开发者必备的技能。本文将深入探讨Linux中的进程替换原理、系统调用实现以及如何创建自定义shell,帮助你全面理解这些重要概念并掌握相关技术。
目录
1. [进程替换基础](#进程替换基础)
2. [进程创建与替换系统调用](#进程创建与替换系统调用)
3. [进程控制与状态管理](#进程控制与状态管理)
4. [Shell原理与设计](#Shell原理与设计)
5. [自定义Shell实现](#自定义Shell实现)
6. [进程间通信与管道](#进程间通信与管道)
7. [信号处理与作业控制](#信号处理与作业控制)
8. [环境变量与执行环境](#环境变量与执行环境)
9. [实用Shell技巧与调试](#实用Shell技巧与调试)
10. [总结与实践建议](#总结与实践建议)
1.进程替换基础
进程替换是指一个进程通过特定的系统调用,将自己的内存空间、代码段和数据段替换为另一个程序的过程。
进程模型概述
- 进程是程序的一次执行实例
- 每个进程拥有独立的地址空间、资源和状态
- Linux使用轻量级进程模型,进程创建和调度高效
进程状态转换
- 就绪态:等待CPU资源执行
- 运行态:正在CPU上执行
- 阻塞态:等待某个事件(如I/O操作)完成
- 终止态:进程执行完毕或异常退出
进程替换的作用
- 允许在不创建新进程的情况下执行新程序
- 保留原进程的PID、打开文件和权限等
- 实现命令解释器(shell)的核心功能
- 优化系统资源使用,减少进程创建开销
2.进程创建与替换系统调用
Linux提供了一系列系统调用,用于创建、替换和控制进程。
fork()系统调用
- 创建当前进程的副本(子进程)
- 父子进程具有相同的代码和数据(写时复制)
- 返回值区分父子进程:父进程获得子进程PID,子进程获得0
- 完全复制父进程的地址空间和资源
pid_t child_pid = fork();if (child_pid == 0) {// 子进程代码printf("我是子进程,PID: %d\n", getpid());} else if (child_pid > 0) {// 父进程代码printf("我是父进程,子进程PID: %d\n", child_pid);} else {// 错误处理perror("fork failed");}
exec系列系统调用
- 用新的程序替换当前进程的内存映像
- 进程ID保持不变,但运行的程序完全替换
- 不会自动返回到调用程序,除非执行失败
主要的exec函数变体:
- execl():使用可变参数列表指定命令行参数
- execv():使用参数数组
- execle():可以指定环境变量
- execve():内核级系统调用,其他exec函数都基于它实现
- execlp()/execvp():会在PATH中查找可执行文件
// 使用execl替换进程execl("/bin/ls", "ls", "-l", NULL);// 如果执行到这里,说明exec调用失败perror("execl failed");
wait()/waitpid()系统调用
- 等待子进程终止
- 回收子进程资源,防止僵尸进程
- 获取子进程退出状态
int status;pid_t pid = wait(&status);if (WIFEXITED(status)) {printf("子进程 %d 正常退出,退出码: %d\n", pid, WEXITSTATUS(status));}
system()函数
- 结合fork()、exec()和wait()的高级封装
- 创建shell执行命令并等待完成
- 简单但不够灵活,不适用于复杂情景
int ret = system("ls -l /tmp");printf("命令执行结果: %d\n", ret);
3.进程控制与状态管理
父子进程关系
- 子进程继承父进程的大部分属性
- 不继承的属性:进程ID、父进程ID、内存锁等
- 文件描述符可选择性地继承(通过close-on-exec标志控制)
进程组与会话
- 进程组:相关进程的集合,共享一个进程组ID
- 会话:一个或多个进程组的集合,通常对应一个终端会话
- 前台进程组:可接收终端输入的进程组
- 后台进程组:在终端中被挂起的进程组
孤儿进程与僵尸进程
- 孤儿进程:父进程先于子进程结束,子进程被init进程(PID 1)收养
- 僵尸进程:已终止但未被父进程回收的进程,仅存留在进程表中
- 大量僵尸进程会耗尽系统资源,应当避免
进程资源限制
- 使用ulimit命令或setrlimit()系统调用设置
- 可限制进程的文件大小、CPU时间、内存使用等
- 防止单个进程消耗过多系统资源
struct rlimit limit;limit.rlim_cur = 1024 * 1024; // 1MBlimit.rlim_max = 2 * 1024 * 1024; // 2MBsetrlimit(RLIMIT_DATA, &limit); // 限制数据段大小
4.Shell原理与设计
Shell的核心功能
- 命令解析与执行
- 环境变量管理
- 作业控制
- 脚本执行
- 提供用户界面
Shell执行流程
1. 读取命令(从终端或脚本)
2. 解析命令(分词、展开变量等)
3. 查找命令路径(内置命令或外部命令)
4. 创建进程执行命令
5. 等待命令完成
6. 返回提示符等待下一个命令
常见Shell类型
- Bourne Shell (sh):最初的Unix shell
- Bash (Bourne-Again Shell):Linux系统默认shell
- C Shell (csh):语法类似C语言
- Z Shell (zsh):功能强大的交互式shell
- Fish:用户友好的交互式shell
Shell内置命令
- 不需要创建新进程执行的命令(如cd、pwd、export等)
- 需要修改shell自身状态的命令
- 实现为shell程序代码的一部分,而非外部可执行文件
5.自定义Shell实现
基本Shell框架
- 命令读取循环(REPL - Read, Evaluate, Print, Loop)
- 命令解析器
- 命令执行器
- 内置命令处理器
// 一个简单shell的主循环void run_shell() {char line[1024];char *args[64];while (1) {printf("myshell> ");if (!fgets(line, sizeof(line), stdin)) break;if (parse_line(line, args) == 0) continue; // 空命令if (handle_builtin(args)) continue; // 处理内置命令execute_command(args); // 执行外部命令}}
命令解析实现
- 将输入字符串分割为命令和参数
- 处理引号、转义字符等特殊情况
- 变量替换和通配符展开
int parse_line(char *line, char **args) {int i = 0;char *token = strtok(line, " \t\n");while (token != NULL && i < 63) {args[i++] = token;token = strtok(NULL, " \t\n");}args[i] = NULL; // 参数列表以NULL结尾return i; // 返回参数数量}
命令执行实现
- 使用fork()创建子进程
- 子进程使用exec系列函数执行命令
- 父进程使用wait/waitpid等待子进程完成
void execute_command(char **args) {pid_t pid = fork();if (pid < 0) {perror("fork failed");} else if (pid == 0) {// 子进程执行命令execvp(args[0], args);perror("execvp failed"); // 如果执行到这里,说明exec失败exit(1);} else {// 父进程等待子进程完成int status;waitpid(pid, &status, 0);}}
内置命令处理
- 如cd、exit、help等需要由shell直接处理的命令
- 通常通过函数表或switch语句实现
int handle_builtin(char **args) {if (strcmp(args[0], "cd") == 0) {if (args[1] == NULL) {// 无参数,切换到主目录chdir(getenv("HOME"));} else {if (chdir(args[1]) != 0) {perror("cd failed");}}return 1; // 已处理内置命令}if (strcmp(args[0], "exit") == 0) {exit(0);}return 0; // 不是内置命令}
6.进程间通信与管道
管道原理
- 管道是进程间通信的基本机制
- 单向数据流,一端写入,另一端读取
- 在shell中用"|"符号连接命令
管道创建与使用
- pipe()系统调用创建匿名管道
- 管道有读端和写端两个文件描述符
- 通常配合fork()使用,实现父子进程通信
int pipefd[2];pipe(pipefd); // 创建管道pid_t pid = fork();if (pid == 0) {// 子进程:关闭读端,将标准输出重定向到管道写端close(pipefd[0]);dup2(pipefd[1], STDOUT_FILENO);close(pipefd[1]);execlp("ls", "ls", "-l", NULL); // 执行第一个命令} else {// 父进程:关闭写端,将标准输入重定向到管道读端close(pipefd[1]);dup2(pipefd[0], STDIN_FILENO);close(pipefd[0]);execlp("grep", "grep", "^d", NULL); // 执行第二个命令}
重定向实现
- 使用dup/dup2系统调用重定向文件描述符
- 实现输入重定向(<)、输出重定向(>)、追加重定向(>>)
// 输出重定向实现void redirect_output(char *filename) {int fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0644);if (fd < 0) {perror("open failed");return;}dup2(fd, STDOUT_FILENO); // 将标准输出重定向到文件close(fd);}
实现多重管道
- 需要创建多个管道并合理设置文件描述符
- 使用多个子进程,每个子进程执行一个命令
- 需要仔细管理进程关系和文件描述符继承
7.信号处理与作业控制
信号基础
- 信号是进程间通信的一种简单方式
- 用于通知进程发生了某个事件
- 每个信号有默认处理方式,进程可以自定义处理
常见信号
- SIGINT (Ctrl+C):中断进程
- SIGTERM:终止进程
- SIGKILL:强制终止进程(不可捕获)
- SIGCHLD:子进程状态变化
- SIGSEGV:段错误
- SIGSTOP/SIGTSTP (Ctrl+Z):停止进程
- SIGCONT:继续执行已停止进程
信号处理设置
- 使用signal()或更现代的sigaction()设置信号处理函数
- 可以忽略、使用默认处理或自定义处理函数
void handle_sigint(int sig) {printf("\n捕获到SIGINT信号,但不退出\n");}// 设置SIGINT信号处理函数signal(SIGINT, handle_sigint);
作业控制
- 允许用户在一个终端中管理多个进程
- 包括前台/后台运行、挂起/恢复进程等
- 使用进程组和终端控制实现
作业控制命令
- jobs:列出当前shell的作业
- fg:将作业带到前台
- bg:在后台继续执行作业
- Ctrl+Z:暂停前台作业
- 命令后加&:在后台启动命令
Shell中实现作业控制
- 维护作业表,记录进程组信息
- 适当处理SIGCHLD信号,更新作业状态
- 实现fg/bg等内置命令
// 在shell中实现后台执行int is_background = 0;// 检查命令行是否以&结尾if (args[arg_count-1][0] == '&') {is_background = 1;args[arg_count-1] = NULL; // 移除&符号}pid_t pid = fork();if (pid == 0) {// 子进程执行命令execvp(args[0], args);perror("execvp failed");exit(1);} else {// 父进程if (!is_background) {// 前台执行,等待子进程完成waitpid(pid, &status, 0);} else {// 后台执行,不等待printf("[%d] %s &\n", pid, args[0]);}}
8.环境变量与执行环境
环境变量基础
- 环境变量是进程的全局设置
- 子进程继承父进程的环境变量
- 用于配置程序行为和提供系统信息
重要环境变量
- PATH:可执行文件搜索路径
- HOME:用户主目录
- USER/LOGNAME:当前用户名
- SHELL:当前shell路径
- PWD:当前工作目录
- LANG/LC_*:语言和区域设置
- LD_LIBRARY_PATH:动态库搜索路径
环境变量操作
- getenv():获取环境变量值
- setenv()/putenv():设置环境变量
- unsetenv():删除环境变量
// 获取环境变量char *path = getenv("PATH");if (path) {printf("当前PATH: %s\n", path);}// 设置环境变量setenv("MY_VAR", "my_value", 1); // 1表示覆盖已有值// 删除环境变量unsetenv("MY_VAR");
Shell中实现环境变量
- 解析和替换命令中的环境变量引用($VAR)
- 实现export内置命令设置环境变量
- 管理子进程的环境变量传递
// 实现export命令if (strcmp(args[0], "export") == 0) {if (args[1]) {char *name = strtok(args[1], "=");char *value = strtok(NULL, "");if (name && value) {setenv(name, value, 1);}}return 1; // 命令已处理}
执行环境查找
- 如何在PATH中查找可执行文件
- 实现类似execvp()的功能
// 在PATH中查找可执行文件char *find_executable(char *cmd) {static char path[1024];char *PATH = getenv("PATH");if (!PATH) return NULL;char *path_copy = strdup(PATH);char *dir = strtok(path_copy, ":");while (dir) {snprintf(path, sizeof(path), "%s/%s", dir, cmd);if (access(path, X_OK) == 0) {free(path_copy);return path;}dir = strtok(NULL, ":");}free(path_copy);return NULL;}
9.实用Shell技巧与调试
调试自定义Shell
- 使用打印语句跟踪程序执行
- 检查返回值和错误信息
- 隔离测试各个功能模块
- 使用strace追踪系统调用
# 使用strace查看shell执行过程中的系统调用strace -f ./myshell
错误处理技巧
- 始终检查系统调用返回值
- 使用perror()或strerror()获取详细错误信息
- 实现错误日志功能
pid_t pid = fork();if (pid < 0) {perror("fork failed");// 错误处理}
功能扩展方向
- 命令历史记录
- 命令补全
- 别名支持
- 脚本解析器
- 更复杂的作业控制
// 实现命令历史#define HISTORY_MAX 100char *history[HISTORY_MAX];int history_count = 0;void add_to_history(char *cmd) {if (history_count < HISTORY_MAX) {history[history_count++] = strdup(cmd);} else {free(history[0]);for (int i = 0; i < HISTORY_MAX - 1; i++) {history[i] = history[i+1];}history[HISTORY_MAX-1] = strdup(cmd);}}
安全注意事项
- 避免缓冲区溢出
- 正确处理用户输入
- 检查文件和命令权限
- 防止命令注入攻击
10.总结与实践建议
核心概念回顾
- 进程替换是shell和其他程序的基础机制
- fork/exec模型支撑了Unix/Linux的设计理念
- 自定义shell是理解Linux系统工作原理的绝佳练习
实践项目建议
- 从最小可行的shell开始,逐步添加功能
- 先实现基本命令执行,再添加管道和重定向
- 后续可扩展更高级功能(作业控制、历史记录等)
- 尝试实现一些独特功能,如内置脚本语言
学习资源推荐
- 《Advanced Programming in the UNIX Environment》by W. Richard Stevens
- 《The Linux Programming Interface》by Michael Kerrisk
- Linux系统调用手册(man pages)
- 开源shell项目源码(bash、zsh等)
常用命令与工具
- ps:查看进程状态
- top/htop:监控系统进程
- strace:跟踪系统调用
- ltrace:跟踪库调用
- gdb:调试工具
- valgrind:内存检查工具
进程替换和自定义shell是理解Linux系统工作机制的关键。通过深入学习这些概念和实现自己的shell,能更好地帮你理解Linux系统的内部工作原理,还能提升系统编程能力和问题解决能力。希望本文能够帮助你扎实掌握这些重要概念,为你的Linux系统编程之旅提供有力支持。
相关文章:
Linux进程替换与自定义shell详解
引言 进程替换和shell编程是Linux系统中极其重要的概念,它们不仅是系统内部工作的基础机制,也是系统管理员和开发者必备的技能。本文将深入探讨Linux中的进程替换原理、系统调用实现以及如何创建自定义shell,帮助你全面理解这些重要概念并掌…...
【数据结构_4下篇】链表
一、链表的概念 链表,不要求在连续的内存空间,链表是一个离散的结构。 链表的元素和元素之间,内存是不连续的,而且这些元素的空间之间也没有什么规律: 1.顺序上没有规律 2.内存空间上也没有规律 *如何知道链表中包…...
Mybatis的简单介绍
文章目录 MyBatis 简介 1. MyBatis 核心特点2. MyBatis 核心组件3. MyBatis 基本使用示例(1) 依赖引入(Maven)(2) 定义 Mapper 接口(3) 定义实体类(4) 在 Service 层调用 4. MyBatis 与 JPA/Hibernate 对比 MyBatis 简介 MyBatis 是一款优秀的 持久层框…...
JavaScript 性能优化实战:深入探讨 JavaScript 性能瓶颈,分享优化技巧与最佳实践
在当今 Web 应用日益复杂的时代,JavaScript 性能对于用户体验起着决定性作用。缓慢的脚本执行会导致页面加载延迟、交互卡顿,严重影响用户留存率。本文将深入剖析 JavaScript 性能瓶颈,并分享一系列实用的优化技巧与最佳实践,助你…...
1g内存电脑sqlite能支持多少并发
1. SQLite的并发机制 写操作:默认使用串行锁,同一时间仅允许一个写操作(其他写/读需等待)。读操作:支持多并发读取,但受内存、磁盘I/O和配置限制。 2. 关键限制因素 (1)内存资源 …...
jetpack之jetpack的概括和其中组件的简单使用
注意⚠:此篇文章由deepseek大力支持!!!(╹ڡ╹ ) 主要是对不知道学什么,对各个组件一头雾水的jetpack新手准备的文章 不知道jetpack学什么,就看这篇文章!! 1. DataBindingÿ…...
音视频 五 看书的笔记 MediaCodec
MediaCodec 用于访问底层媒体编解码器框架,编解码组件。通常与MediaExtractor(解封装,例如Mp4文件分解成 video和audio)、MediaSync、MediaMuxer(封装 例如音视频合成Mp4文件)、MediaCrypto、Image(cameraX 回调的ImageReader对象可以获取到Image帧图像,可转换成YU…...
物联网|无人自助台球厅源码|哪些框架支持多设备连接?
在无人自助台球厅的智能化管理中,物联网(IoT)技术是核心支撑。如何实现不同设备(如智能门锁、环境传感器、支付终端、灯光控制系统等)的高效连接与协同工作,是系统开发的关键挑战。本文将带大家探讨支持多设…...
Python中NumPy的统计运算
在数据分析和科学计算领域,Python凭借其丰富的库生态系统成为首选工具之一,而NumPy作为Python数值计算的核心库,凭借其高效的数组操作和强大的统计运算功能,广泛应用于机器学习、信号处理、统计分析等场景。本文将系统介绍NumPy在…...
uniapp实现H5页面麦克风权限获取与录音功能
1.权限配置 在uni-app开发H5页面时,需要在manifest.json文件中添加录音权限的配置。具体如下: {"h5": {"permissions": {"scope.record": {"desc": "请授权使用录音功能"}}} }这段配置代码是用于向…...
两个树莓派如何通过wifi direct传输视频并显示
这里写自定义目录标题 在两台设备上安装必要软件Wi-Fi Direct接收端IP(自动发现或静态设置)设置摄像头参数显示初始化网络设置 系统架构概述 发送端树莓派:捕获视频(摄像头或视频文件)→ 编码 → 通过Wi-Fi Direct传输…...
ubuntu 系统安装Mysql
安装 mysql sudo apt update sudo apt install mysql-server 启动服务 sudo systemctl start mysql 设置为开机自启 sudo systemctl enable mysql 查看服务状态 (看到类似“active (running)”的状态信息代表成功) sudo systemctl status mysql …...
selenium快速入门
一、操作浏览器 from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By# 设置选项 q1 Options() q1.add_argument("--no-sandbo…...
Redis:线程模型
单线程模型 Redis 自诞生以来,一直以高性能著称。很多人好奇,Redis 为什么早期采用单线程模型,它真的比多线程还快吗? 其实,Redis 的“快”并不在于并发线程,而在于其整体架构设计极致简单高效,…...
Transformer模型解析与实例:搭建一个自己的预测语言模型
目录 1. 前言 2. Transformer 的核心结构 2.1 编码器(Encoder) 2.2 解码器(Decoder) 2.3 位置编码(Positional Encoding) 3. 使用 PyTorch 构建 Transformer 3.1 导入所需的模块: 3.2 定…...
Java常用安全编码的规范整理及工具
对Java安全编码的主要规范及要求的一些整理: 一、输入验证与数据校验 外部输入校验 对所有外部输入(如用户输入、文件、网络数据)进行合法性校验,采用白名单机制限制输入内容范围 。校验前对输入数据做归一化处理ÿ…...
重返JAVA之路——面向对象
目录 面向对象 1.什么是面向对象? 2.面向对象的特点有哪些? 3.什么是对象? 4.什么是类? 5.什么是构造方法? 6.构造方法的特性有哪些? 封装 1.什么是封装? 2.封装有哪些特点? 数据隐…...
Java设计模式全解析(共 23 种)
一、设计模式全解: Java 中的设计模式是为了解决在软件开发中常见问题的一些“最佳实践”总结。设计模式分为三大类,共 23 种经典模式: 1. 创建型模式(5 种) 用于对象的创建,解决对象实例化过程中的问题。…...
Python10天突击--Day 2: 实现观察者模式
以下是 Python 实现观察者模式的完整方案,包含同步/异步支持、类型注解、线程安全等特性: 1. 经典观察者模式实现 from abc import ABC, abstractmethod from typing import List, Anyclass Observer(ABC):"""观察者抽象基类""…...
springboot框架集成websocket依赖实现物联网设备、前端网页实时通信!
需求: 最近在对接一个物联网里设备,他的通信方式是 websocket 。所以我需要在 springboot框架中集成websocket 依赖,从而实现与设备实时通信! 框架:springboot2.7 java版本:java8 好了,还是直接…...
【玩泰山派】5、点灯,驱动led-(2)ubuntu18.04 升级python3.6到python3.7,安装pip3
文章目录 前言升级python3.71、安装 software-properties-common 包2、添加 deadsnakes PPA 源3、安装 Python 3.71. 安装 Python 3.72. 安装 Python 3.7 的开发包和虚拟环境支持(可选但推荐)3. 设置 Python 3.7 为默认版本4. 验证 Python 版本注意事项 …...
ES6学习03-字符串扩展(unicode、for...of、字符串模板)和新方法()
一、字符串扩展 1. eg: 2.for...of eg: 3. eg: 二。字符串新增方法 1. 2. 3. 4. 5....
c++中的this
在 C 中,this 是一个指向当前对象实例的指针,它隐式地存在于类的非静态成员函数中。以下是 this 的详细用法和常见场景: 1. 常见场景 明确成员归属:当成员变量与局部变量同名时,用 this-> 显式访问成员。当成员变量…...
目前状况下,计算机和人工智能是什么关系?
目录 一、计算机和人工智能的关系 (一)从学科发展角度看 计算机是基础 人工智能是计算机的延伸和拓展 (二)从技术应用角度看 二、计算机系学生对人工智能的了解程度 (一)基础层面的了解 必备知识 …...
Flutter 2025 Roadmap
2025 这个路线图是有抱负的。它主要代表了我们这些在谷歌工作的人收集的内容。到目前为止,非Google贡献者的数量超过了谷歌雇佣的贡献者,所以这并不是一个详尽的列表,列出了我们希望今年Flutter能够出现的所有令人兴奋的新事物!在…...
[数据结构]排序 --2
目录 8、快速排序 8.1、Hoare版 8.2、挖坑法 8.3、前后指针法 9、快速排序优化 9.1、三数取中法 9.2、采用插入排序 10、快速排序非递归 11、归并排序 12、归并排序非递归 13、排序类算法总结 14、计数排序 15、其他排序 15.1、基数排序 15.2、桶排序 8、快速排…...
第16届蓝桥杯c++省赛c组个人题解
偷偷吐槽: c组没人写题解吗,找不到题解啊 P12162 [蓝桥杯 2025 省 C/研究生组] 数位倍数 题目背景 本站蓝桥杯 2025 省赛测试数据均为洛谷自造,与官方数据可能存在差异,仅供学习参考。 题目描述 请问在 1 至 202504ÿ…...
记一次InternVL3- 2B 8B的部署测验日志
1、模型下载魔搭社区 2、运行环境: 1、硬件 RTX 3090*1 云主机[普通性能] 8核15G 200G 免费 32 Mbps付费68Mbps ubuntu22.04 cuda12.4 2、软件: flash_attn(好像不用装 忘记了) numpy Pillow10.3.0 Requests2.31.0 transfo…...
Android PowerManager功能接口详解
PowerManager 是 Android 系统中用于管理设备电源状态的核心服务,开发者可以通过它控制设备的唤醒、休眠、屏幕亮灭等行为。以下是对 PowerManager 核心功能接口的详细说明,包含使用场景、注意事项和代码示例。 1. 获取 PowerManager 实例 通过 Context…...
使用SSH解决在IDEA中Push出现403的问题
错误截图: 控制台日志: 12:15:34.649: [xxx] git -c core.quotepathfalse -c log.showSignaturefalse push --progress --porcelain master refs/heads/master:master fatal: unable to access https://github.com/xxx.git/: The requested URL return…...
