分享
 
 
 

UNIX高级环境编程(8)进程环境(Process Environment)- 进程的启动和退出、内存布局、环境变量列表

王朝学院·作者佚名  2016-05-26
窄屏简体版  字體: |||超大  

UNIX高级环境编程(8)进程环境(PRocess Environment)- 进程的启动和退出、内存布局、环境变量列表在学习进程控制相关知识之前,我们需要了解一个单进程的运行环境。

本章我们将了解一下的内容:

程序运行时,main函数是如何被调用的;命令行参数是如何被传入到程序中的;一个典型的内存布局是怎样的;如何分配内存;程序如何使用环境变量;程序终止的各种方式;跳转(longjmp和setjmp)函数的工作方式,以及如何和栈交互;进程的资源限制?

1 main函数main函数声明:

int main (int argc, char *argv[]);

参数说明:

argc:命令行参数个数argv:指向参数列表数组的指针main函数启动前:

C程序由内核执行,通过系统调用exec;main函数调用前,执行指定的启动路径(start-up routine);可执行文件认为此地址为程序的启动地址,该地址由链接器指定;启动路径从内核获取参数列表和环境变量,使得main函数可以在稍后被调用时可以获取这些变量。?

2 进程终止一共有8中终止进程的方式,5种正常终止和3种异常终止。

5种正常终止:

从main函数返回;调用exit;调用_exit或_Exit;最后一个线程返回;最后一个线程调用pthread_exit。3种异常终止:

调用abort;接收到一个信号;最后一个线程应答或者一个接收到一个退出请求启动地址(start-up routine)同样也是main函数的返回地址。

要获取该地址,可以通过以下的方式:

exit (main(argc, argv));

?

退出函数函数声明:

#include <stdlib.h>

void exit(int status);

void _Exit(int status);

#include <unistd.h>

void _exit(int status);

函数细节:

_exit和_Exit立刻返回到内核;exit函数返回内核前会进行一些清理环境工作;返回一个整数和调用exit函数,并传入该整数的作用是相同的:

exit(0);

return 0;

?

atexit函数函数声明

#include <stdlib.h>

int atexit(void (*func)(void));

函数细节

每个进程可以注册32个函数,这些函数可以在主函数调用exit时自动被调用通过atexit注册的退出时处理函数称为退出句柄(exit handlers)这些退出句柄的调用顺序为注册时的相反顺序exit函数第一次调用退出句柄时,会关闭所有打开的流如果主程序调用了exec系列函数,则所有注册的退出句柄都会被清空?

程序启动和终止流程图?

Example:#include "apue.h"

?

static void my_exit1(void);

static void my_exit2(void);

?

int

main(void)

{

? ? if (atexit(my_exit2) != 0)

? ? ? ? err_sys("can't register my_exit2");

?

? ? if (atexit(my_exit1) != 0)

? ? ? ? err_sys("can't register my_exit1");

? ? if (atexit(my_exit1) != 0)

? ? ? ? err_sys("can't register my_exit1");

?

? ? printf("main is done\n");

? ? return(0);

}

?

static void

my_exit1(void)

{

? ? printf("first exit handler\n");

}

?

static void

my_exit2(void)

{

? ? printf("second exit handler\n");

}

?执行结果:

?

3 命令行参数Example:#include "apue.h"

?

int

main(int argc, char *argv[])

{

? ? int ? ? i;

?

? ? for (i = 0; i < argc; i++)? ? ? /* echo all command-line args */

? ? ? ? printf("argv[%d]: %s\n", i, argv[i]);

? ? exit(0);

}

执行结果:

?

?

4 环境变量列表每个程序会接受一个环境变量列表,该列表是一个数组,由一个数组指针指向,该数组指针类型为:

extern char **environ;

例如,如果环境变量里有5个字符串(C风格字符串),如下图所示:

5 C程序的内存布局典型的C程序的内存布局如下图所示:

上图说明:

文本段(Text Segment),保存CPU将要执行的机器指令。文本段是可共享的,所以某个程序多次执行时,对应的文本段只需要在内存中存有一份拷贝。文本段是只读的(read-only),防止程序的指令被修改。已初始化数据段(initialized data segment),保存程序中被初始化的全局变量(定义在任何函数之外)。例如:int maxcount = 99; 全局变量变量maxcount被保存在初始化数据段。未初始化数据段(uninitialized data segment),也被称为BSS(block started by symbol),这个段中的数据在程序执行之前被内核初始化为0或者null。;例如定义一个全局变量(定义在任何函数之外),long sum[1000]; ?该变量保存在未初始化数据段中。栈(Stack):存储临时变量,函数相关信息。当一个函数被调用时,返回地址、调用者相关信息(如寄存器信息)会被保存在栈中。该被调用的函数会在栈上分配一部分空间保存它的临时变量。函数的递归调用也是应用这个原理。每一次函数调用自己,都会保存当前函数的信息,然后再栈上开辟一个新的空间用于保存该次函数的信息,和以前的函数并没有影响。堆(Heap):动态内存分配位置。堆的位置位于未初始化数据段和栈的中间。?

6 内存分配(Memory Allocation)有三个函数可以用于内存分配:

malloc:分配指定字节数的内存,未初始化。calloc:分配指定数目的对象大小的内存,内存初始化为0;realloc:增加或减小之前分配的内存。移动旧内存的内容到新的更大的内存块,多余的部分内存未初始化。函数声明:

#include <stdlib.h>

void *malloc(size_t size);

void *calloc(size_t nobj, size_t size);

void *realloc(void *ptr, size_t newsize);

void free(void* ptr);

函数细节:

三个函数返回的内存指针一定是内存对齐的,这样可以用来保存于不同的对象;free函数用于释放ptr指向的内存,被分配的内存放入内存池中用于下次的内存分配;realloc函数用于改变之前分配的内存的大小。比如运行时我们申请了一段内存用于存储512个元素的数组,后来发现内存大小不够,这时可以调用realloc。如果操作系统发现在当前内存的后面有足够的内存,则直接分配多余的内存到当前内存中,然后返回传入的指针(即直接扩展内存)。但是如果当前内存后面没有足够大小的空间,则系统重新分配一个足够大的内存,将旧内存块中得内容拷贝到新内存块中,然后返回新内存的地址。内存分配函数使用系统调用sbrk来实现。该系统调用的作用是扩展进程的堆。一般实际分配的内存块都比请求的要大,多出来的部分用来存储内存块大小、指向下一内存块的指针等信息。写覆盖信息记录区的错误是非常隐蔽而且严重的。?

7 环境变量(Environment Variable)环境变量的字符串形式:

name=value

?内核不关注环境变量,各种应用才会使用环境变量。

获取环境变量值使用函数getenv。

#include <stdlib.h>

char* getenv(const char* name);

// Returns: pointer to value associated with name, NULL if not found

修改环境变量的函数:

#include <stdlib.h>

int putenv(char* str);

int setenv(const char* name, const char* value, int rewrite);

int unsetenv(const char* name);

?函数细节:

函数putenv传入一个字符串,形式为name=value,加入到环境变量列表中。如果name已经存在,先删除旧的定义。函数setenv传入一个name和一个value,如果name已经存在,则参数rewrite决定是否覆盖旧的定义,如果rewrite为非零,则会覆盖旧的定义。函数unsetenv删除name的定义,如果name不存在,也不报错。??修改环境变量列表的过程是一件很有趣的事情

从上面的C程序内存布局图中可以看到,环境变量列表(保存指向环境变量字符串的一组指针)保存在栈的上方内存中。

在该内存中,删除一个字符串很简单。我们只需要找到该指针,删除该指针和该指针指向的字符串。

但是增加或修改一个环境变量困难得多。因为环境变量列表所在的内存往往在进程的内存空间顶部,下面是栈。所以该内存空间无法被向上或者向下扩展。

所以修改环境变量列表的过程如下所述:

如果我们修改一个已经存在的name:如果新的value的大小比已经存在的value小或者相当,直接覆盖旧的value;如果新的value的大小比已经存在的value大,则我们必须为新的value malloc一个新的内存空间,拷贝新value到该内存中,替换指向旧value的指针为指向新value的指针。如果我们新增一个环境变量:首先我们需要调用malloc为字符串name=value分配空间,拷贝该字符串到目标内存中;如果这是我们第一次添加环境变量,我们需要调用malloc分配一个新的空间,拷贝老的环境量列表到新的内存中,并在列表后新增目标环境变量。然后我们设置environ指向新的环境变量列表。如果这不是我们第一次新增环境变量,则我们只需要realloc多分配一个环境变量的空间,新增的环境变量保存在列表尾部,列表最后仍然是一个null指针。?

小结本篇介绍了进程的启动和退出、内存布局、环境变量列表和环境变量的修改。

下一篇将接着学习四个函数setjmp、longjmp、getrlimit和setrlimit。

?

?

参考资料:

《Advanced Programming in the UNIX Envinronment 3rd》

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有