《Linux/UNIX系统编程手册》第6章 进程
关键词:getpid()、getppid()、environ、setjmp()、longjmp()等等。
本章将研究进程结构,并重点关注进程虚拟内存的布局及内容。还会对进程某些属性进行考察。
1. 进程和程序
进程是一个可执行程序的实例。
程序包含:
- 二进制格式标识:每个程序文件都包含用于描述可执行文件格式的元信息。
- 机器语言指令:对程序算法进行编码。
- 程序入口地址:标识程序开始执行时的起始指令位置。
- 数据:程序文件包含的变量初始值和程序使用的字面常量值。
- 符号表及重定位表:描述程序中函数和变量的位置及名称。
- 共享库和动态链接信息:程序文件所包含的一些字段,列出了陈旭运行时需要使用的共享库,以及加载共享库的动态链接器的路径名。
- 其他信息:程序文件还包含许多其他信息,用以描述如何创建进程。
从内核角度看,进程是由用户内存空间和一系列内核数据结构组成,其中用户空间包含程序代码及代码所使用的变量,而内核数据结构则用于维护进程状态信息。
2. 进程号和父进程号
获取进程号:
#include <unistd.h> pid_t getpid(void); Always successfully returns process ID of caller
查看当前系统支持的最大信号数,通过/proc/sys/kernel/pid_max一般为32768。
int pid_max = PID_MAX_DEFAULT; #define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)
查看进程父进程号:
#include <unistd.h> pid_t getppid(void); Always successfully returns process ID of parent of caller
3. 进程内存布局
下图展示各内存段在x86-32体系结构中布局:
每个进程所分配的内存有很多 部分祖晨,通常称之为段segment:
- 文本段(text):包含了进程运行的程序机器语言指令。
- 初始化数据段(data):包含了显式初始化的全局变量和静态变量。
- 未初始化数据段(bss):包含了未进行显式初始化的全局变量和静态变量。
- 栈(stack):一个动态增长和收缩的段,由栈帧(stack frames)组成。系统会为每个当前调用的函数分配一个栈帧。栈帧中存储了函数的局部变量、实参和返回值。
- 堆(heap):可在运行时动态进行内存分配的一块区域。堆顶端称作program break。
size可显示二进制可执行文件的文本段、初始化数据段、非初始化数据段的段大小。
PS:修改代码对比maps,是否和内存结构吻合?和size是否吻合?目的是通过maps变化,确定内存来自于何处?
4. 虚拟内存管理
虚拟内存的规划之一是将每个程序使用的内存切割成小型的、固定大小的页单元。
进程有效虚拟地址范围在其生命周期中变化可能会发生于如下场景:
- 由于栈向下增长超出之前曾达到的位置。
- 当在堆中分配或释放内存时,通过调用brk()、sbrk()、malloc()函数族来提升program break的位置。
- 当调用shmat()连接System V共享内存区时,或者调用shmdt()脱离共享内存区时。
- 当mmap()创建内存映射时,或者munmap()解除内存映射时。
5. 栈和帧
函数的调用和返回使栈的增长和收缩呈线性。每次调用函数时,会在栈上新分配一帧,每当函数返回时,再从栈上将此帧移去。
内核栈是每个进程保留在内核内存中的内存区域,在执行系统调用的过程中供内核内部函数调用使用。
每个用户栈帧包括如下信息:
- 函数实参和局部变量:这些变量都是在调用函数时自动创建的,C中成为自动变量。函数返回时将自动销毁这些变量。这也是自动变量与静态全局变量主要的语义区别:后者与函数执行无关,且长期存在。
- 函数调用的链接信息:每个函数都会用到一些CPU寄存器,会在被调用函数的栈帧中保存这些寄存器的副本。
6. 命令行参数(argc, argv)
每个C程序都必须有一个main()作为程序启动的起点。
执行程序时,命令行参数通过两个入参提供给main()函数。
第一个参数int argc,表示命令行参数的个数;第二个参数char *argv[]是一个指向命令行参数的指针数组,每参数都是以空字符结尾的字符串。
通过/proc/PID/cmdline文件可以读取任一进程的命令行参数;GNU C可使用program_invocation_name和program_invocation_short_name找到程序名称。
参数存储自己上限通过ARG_MAX限定,通过调用sysconf(__SC_ARG_MAC)确定上限值。
程序使用getopt()库函数解析命令行选项。
7. 环境列表
环境变量字符串都以名称=值形式定义。新进程在创建之时,会继承其父进程的环境副本。
可以通过export key=value来设置环境变量,也可以通过set key=value设置,通过unset key来撤销环境变量。
可以通过/proc/PID/environ检查任意进程的环境列表。
7.1 查看环境变量
C中可以通过char **environ访问环境列表。
通过指针遍历environ变量:
#include <stdio.h> #include <stdlib.h> extern char **environ; /* Or define _GNU_SOURCE to get it from <unistd.h> */ int main(int argc, char *argv[]) { char **ep; for (ep = environ; *ep != 0; ep++) puts(*ep); exit(0); }
还可以通过int main(int argc, char *argv[], char *envp[])第三个参数访问环境列表。
getenv()从进程环境中检索单个值:
#include <stdlib.h> char *getenv(const char *name); Returns pointer to (value) string, or NULL if no such variable
7.2 修改环境变量
putenv()向调用进程的环境中添加一个新变量,或者修改一个已经村侧的变量值。
#include <stdlib.h> int putenv(char *string); Returns 0 on success, or nonzero on error
setenv()可以替代putenv()向环境中添加一个变量。如果name标识变量在环境中已经存在,且参数overrite值为0,则setenv()不改变环境。如果overwrite为非0,则setenv()函数总是改变环境。
#include <stdlib.h> int setenv(const char *name, const char *value, int overwrite); Returns 0 on success, or –1 on error
unsetenv()从环境中移除由name参数标识的变量。
#include <stdlib.h> int unsetenv(const char *name); Returns 0 on success, or –1 on error
clearenv()清除整个环境变量。
#define _BSD_SOURCE /* Or: #define _SVID_SOURCE */ #include <stdlib.h> int clearenv(void) Returns 0 on success, or a nonzero on error
8. 执行非局部跳转:setjmp()和longjmp()
使用库函数setjmp()和longjmp()可执行非局部跳转。非局部跳转是指跳转的目标为当前执行函数之外的某个位置。
goto跳转只能在函数内部。
#include <setjmp.h> int setjmp(jmp_buf env); Returns 0 on initial call, nonzero on return via longjmp() void longjmp(jmp_buf env, int val);
setjmp()调用为后续由longjmp()调用执行的跳转确立了跳转目标。该目标正是程序发起setjmp()调用的位置。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)