当前位置: 首页 > news >正文

apue笔记-进程环境、进程控制、进程关系

原文博客:https://nosae.top

进程环境

C程序总是从main函数开始执行,内核在调用main之前会调用一个特殊的启动例程,由这个例程去调用main。这个例程如果用C代码来写(一般是用汇编来写),那就是exit(main(argc, argv)),也就是用main的返回值作为程序的结束值。

如图所示,除了exit外,还有_exit和_Exit函数也可以直接退出程序,但是exit总是会先清理一些资源再调用_exit或者_Exit返回到内核,包括调用终止处理程序(由atexit注册的函数)以及关闭文件。

image-20250314224913766

每个程序都会接收到一张环境表,由指针environ指向该表,表中每一项是一个环境变量串。但是我们程序不会通过environ访问环境变量,而是getenv和putenv函数对环境变量进行访问。

image-20250316123507433

C程序的空间布局

  1. 正文段:也就是该程序的代码,由ISA指定的机器代码。
  2. 数据段(初始化数据段):任何函数外声明的、已经赋值的变量,比如int maxcount = 99;,将该变量以其初值放在数据段中。
  3. bss段(未初始化数据段):任何函数外声明的、未赋值的变量,比如long sum[1000];,根据约定内核会将这个段中的数据会初始化为0。
  4. 栈:保存函数调用时的上下文、在函数内声明的变量等。
  5. 堆:动态分配的内存。

image-20250316124527003

二进制文件中除了上述的段之外,还有其它类型的段,比如包含符号表的段、包含调试信息的段、包含动态共享库链接表的段等,但这些段都不装载到程序执行的内存中

共享库使得程序不用包含公共函数,减少二进制可执行文件的大小。当程序第一次调用某个库函数的时候,才会通过动态链接的方式和共享库进行连接。共享库的另一个优点是无需重新编译程序就能替换公共库函数的实现。

共享库

共享库(在 Linux 中通常为 .so 文件,Windows 中为 .dll,macOS 中为 .dylib)是一种可被多个程序同时共享的二进制代码库,其核心原理是代码复用和内存节省,共享库原理如下

  • 程序编译时,并不会将共享库的代码直接嵌入可执行文件,而是仅记录共享库的名称和所需符号(函数、变量等)的引用信息。
  • 程序启动时,共享库加载后,会被映射到进程的地址空间(通过内存映射 mmap 机制),但同一共享库在内存中仅加载一次,多个进程可共享同一份物理内存(通过页表映射到各自的虚拟地址空间),实现内存复用。动态链接器会完成符号重定位:将程序中对共享库符号的引用(如函数调用)绑定到内存中共享库的实际地址。

共享库的好处在于:

  • 节省磁盘空间:多个程序共享同一库文件,无需重复存储。
  • 节省内存:内存中仅保留一份共享库代码,被所有使用它的进程共享。
  • 便于更新:更新共享库后,所有依赖它的程序无需重新编译,直接使用新库(需保证接口兼容)。

在程序运行时删除共享库文件,通常不会立即导致程序崩溃,但可能引发后续异常:

  • 若共享库已被加载到内存并完成链接,程序运行时会直接使用内存中的代码和数据,此时删除磁盘上的库文件,进程仍可正常执行(因为内存中的映射未失效)
  • 若程序运行中需要重新加载共享库、程序重启、内存页回收,则会因无法从原文件读取数据而触发段错误

存储空间分配

ISO C说明了3个用于动态分配存储空间的函数:

  • malloc:分配指定字节数的空间
  • calloc:分配n个指定字节数的空间,并且置零
  • realloc:增加或减少之前分配的空间大小

malloc也有其它的替代品,比如google出品的tcmalloc、freebsd中的jemalloc等。

环境变量

环境变量有一些是登录时自动设置的,有些是由用户设置的。自动设置的环境变量比如:

$ echo $LINES
24
$ echo $PWD
/Users/nosae
$ echo $SHELL
/bin/zsh
$ echo $home
/Users/nosae

在当前进程设置的环境变量只会影响到当前进程和子进程,并不会影响父进程的环境变量表。

删除环境变量的命令是

unset 环境变量名

linux支持getenv、putenv、unsetenv等C函数来在程序中读写环境变量。

跨越山河的setjmp和longjmp

在C中,goto不能跨越函数进行跳转,而setjmp和longjmp可以做到。这两个函数在处理深层嵌套函数调用中的出错情况是非常有用的。

下面这个程序中,函数调用路径是main -> a -> b,假设说函数a其实是一大串的函数调用链a1->a2->a3....->b,并且调用链上每个函数都像a那样去判断下一个函数执行的返回值。实际上,当b出错返回的时候,我们是想让它直接返回到main的,但因为调用链是main->a1->a2...->b,而goto又不能跨函数,所以只能一层层往上返回。

int main() {a();...return 0;
}int a() {// 判断下一个函数执行失败的话,直接返回,不执行本函数剩下的的代码if (b() != 0) {return;}...
}int b() {...
}

此时我们借助setjmp和longjmp,实现直接从b“返回”到main中:

#include <setjmp.h>jmp_buf jmpbuffer;int main() {// setjmp设置longjmp跳转的位置,jmpbuffer存放调用longjmp时能用来恢复栈状态的所有信息// setjmp的返回值为上一次longjmp传入的第二个参数。由于程序可能有多处longjmp的调用,一般用它来判断本次跳转来自哪个longjmp。// 效果上类比goto的标签if (setjmp(jmpbuffer) != 0) {printf("error");exit(1);}a();...return 0;
}int a() {b();...
}int b() {if (some error occur) {// longjmp跳转到之前setjmp调用的位置,第二个参数将作为跳转回去后setjmp的返回值// 效果上类比gotolongjmp(jmpbuffer, 1);}...
}

当b执行过程中出错时,调用longjmp,就能直接回到main中setjmp被调用前的位置,也就是跳转回去后从setjmp开始继续往下执行。

但是setjmp/longjmp也存在一些问题,对于存储在寄存器中的变量,在longjmp之前赋值,随后在longjmp调用之后,该赋值可能会丢失。

setrlimit和getrlimit

用于限制进程资源的使用,比如docker中通过限制资源防止容器内进程影响宿主机、通过 RLIMIT_NPROC 限制用户能创建的最大进程数(防止 fork 炸弹)、通过 RLIMIT_CPU 限制进程使用的 CPU 总时间(防止恶意程序或死循环占用过多 CPU)等。

进程控制

http://www.fuzeviewer.com/news/3910/

相关文章:

  • 如何提升网站seo排名平台网站建设方案模板下载
  • 做网站用小公司还是大公司深圳高端包装盒设计
  • 平面图网站湛江网站模
  • 杭州笕桥网站建设无人在线直播免费观看
  • 天地做网站岳阳做网站费用
  • 建立自己的网站软件有备案增加网站
  • 九江县网站建设app软件开发定义
  • 网站外链怎么发小程序云开发收费
  • 做一个网站设计要多久兰州优化网站
  • 做京东电脑端首页链接的网站大庆室内设计公司排名
  • 陕西网站制作商烟台教育网站建设
  • 惠州专业网站建设中国新发展+世界新机遇
  • 南京哪公司建设网站网页设计制作费用多少
  • 雨默合肥做网站推广安卓软件开发工程师
  • 哪家手表网站上海网站建设收费标准
  • 一个网站两个域名备案网站建设有什么注意
  • 网站建设的技术有哪些内容上海嘉定网页设计
  • 怎么做外国网站卖东西angularjs开发网站模板
  • 可以免费做兼职的网站有哪些长沙网站优化方案
  • 淘宝网站后台怎么做wordpress 设置备案号
  • 宁波搭建网站公司网站建设公司如何转型
  • html网站开发例子网站怎么做营销策划
  • 网站建设中如何发布信息推广找客户信息的软件
  • 企业网站开发模型图重庆小潘seo
  • 网站开发课程内部培训wordpress设置会员下载
  • 在重庆_那里可以做诚信网站认证婚纱摄影网站报价
  • 阿里巴巴网站建设与维护石家庄做网站的口碑好
  • 建立网站的程序wordpress 群组插件
  • 代做网站公司有哪些wordpress缩略图不显示图片
  • 购物网站如何推广模板免费网站建设