[ APUE ] 第七章 进程环境


1.进程终止的方式

八种终止方式,其中五种为正常终止

  1. 从main返回
  2. 调用exit
  3. 调用_exit 或_Exit
  4. 最后一个线程从启动例程返回
  5. 从最后一个线程调用 pthread_exit

异常终止有三种方式

  1. 调用abort
  2. 接收到一个信号
  3. 最后一个线程对取消请求作出相应

启动例程差不多是长这样(C形式,但一般为汇编形式):exit(main(argc,argv)).

#include 
void _exit(int status);
#include 
void _Exit(int status);

exit和_Exit 是ISO C说明的 ,而_exit是由POSIX.1说明的

exit会对所有标准IO库的打开的流调用 fclose 函数,使得所有流的内容都被冲洗(写到文件)。

#include 
int atexit(void (*function)(void));

atexit 登记exit时要执行的函数。登记的顺序与执行顺序相反(类似于栈

一个函数若被登记多次,则会被多次执行。

4. 命令行参数

第0个命令行参数的即为程序名(带./的)。argv[argc] 一般设为NULL

5.环境表

全局遍历environ则包含了该指针数组的地址:

? extern char **environ

字符指针数组,以NULL结尾。

环境内容项由 “name=value” 这样的字符串组成。

一般使用 getenv 和 putenv 函数来访问特定的环境变量,但是当要查看整个环境时,还是要使用environ指针。

6. C程序存储空间布局

  1. 正文段:cpu执行的机器指令部分。可共享,只读, 防止被修改。

  2. 初始化数据段(数据段):包含了明确赋予初值的变量(已明确初始化且不为0的全局变量和静态变量)。

  3. 未初始化数据段(bss(”由符号开始的段 “)段):存放函数外声明(注意!这里是未初始化的或者初始化为0!)变量(全局变量和静态变量)。内核会将存入其中的变量初始化为0或NULL。

  4. 栈:局部变量和每次函数调用时所需保存的信息。调用函数时会保存返回地址和调用者状态信息(如栈指针寄存器内容等)。

  5. 堆:动态存储分配。堆位于未初始化数据段和栈之间。

bss段内容不存在程序文件中,因为它们会被自动(或已经)设置为0,那么显然为了节省空间这部分内容是可以不存储的。(害记得空洞么)具体的变量名对应的存储空间的逻辑位置由符号表来负责。其实还有包含调试信息和动态共享库链接表的段,这三部分并不装载到进程执行时的进程映像中。

7. 共享库

共享库可以使得可执行文件不需要包含公用的库函数,只需要在所有进程都可以引用的存储区中保存这种库例程的一个副本。程序第一次执行或者第一次调用某个库函数时,用动态链接将程。 序与共享库函数相链接。减少了每个可执行文件的长度,但增加了时间开销。

共享库的另一个优点是,可以使用库函数的新版本代替老版本而无需使用该库的程序重新链接编辑。(假定参数个数和类型没有发生变化)。

8. 存储空间分配

  1. malloc,堆上分配指定字节数存储空间
  2. calloc, 为指定数量(size)的指定长度(nobj)的对象分配存储空间。该空间中每一位bit都初始化为0.
  3. realloc(! 慎用),增加或者减少以前分配区的长度。当增加长度时,可能需要将以前分配区的内容移到另一个足够大的区域,以便在尾端提供增加的存储区,而新增区域内的初始值则不确定。(这个函数一般不使用)
#include 

void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
												成功返回非空指针,失败返回NULL
void free(void *ptr);

返回的指针一定是对齐的。

free释放ptr指向的存储空间,被释放的空间通常放入可用存储区池,以后可再分配。

9.环境变量

#include 

char *getenv(const char *name);
							找到则返回指向与name关联value的指针,未找到则返回NULL
int putenv(char *string);
							成功返回0,失败返回非0.
int setenv(const char *name, const char *value, int overwrite);
int unsetenv(const char *name);

putenv 取形式为name=value的字符串,将其放入环境表中。如果name已经存在,则先删除原来的定义。

setenv将name设置为value。如果环境中name已经存在,那么若overwrite为1时,删除现有定义,若overwrite为0,则不删除现有定义。

unsetenv删除env定义。即便不存在该定义也不出错。

注意:putenv的参数为char*,这里的字符串必须要堆上分配内存,他是直接插进环境表的,而setenv多一次复制操作,有副本。

10.setjmp和longjmp

非局部goto,在栈上跳过若干调用帧,返回到当前函数调用路径上的某个函数中。

#include 

int setjmp(jmp_buf env);
									直接调用返回0,若从longjmp返回,则返回非0
                                    这个非0是longjmp的第二个参数val。
void longjmp(jmp_buf env, int val);

调用setjmp时将本处环境恢复所需的信息存入jmp_buf类型数组env中并返回0,在其他函数中调用longjmp时以该env为第一个参数,跳回setjmp处。调用路径中间的函数的栈帧被抛弃了。

  1. 自动变量、寄存器变量的值。

    调用setjmp的函数在跳转后,其局部变量和寄存器变量的值是不确定的(所有标准都是这样)。如果不想让局部变量回滚,可以将其定义为具有volatile属性。对于全局变量或静态变量,其值在执行longjmp时保持不变。

  2. 自动变量的问题。

    一个函数的局部变量作为IO流缓冲时,如果该函数栈帧被抛弃,则该栈帧所在内容可能会被其他函数所使用,而标准IO库函数仍然将这部分存储空间作为该流的缓冲区,这就导致了冲突和混乱。一般使用动态分配内存或静态分配来解决这一问题。

    从图中可以发现局部变量优化后也不会回滚到原值,只有存放在寄存器的变量会。

    (对于某些系统,如linux,存放在内存的变量不会回滚,但存放在寄存器的变量会回滚,可能是因为寄存器内容在setjmp时被存到jmp_buf类型变量中了)。

11.getrlimit 和 setrlimit

#include 
#include 

int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
												成功返回0,失败返回非0

查询和修改 对于每个进程的一组资源限制。

习题:

7.1 这题作废。返回值仍为0。

7.2 程序处于交互运行方式时,标准输出设备通常处于行缓冲,当输出换行符时会输出。

若被重定向到一个文件而全缓冲,则当进行IO清理或缓冲区满的时候结果才被输出。

7.3 没办法

7.4 这里是指代空指针,可以防止程序解引用一个空指针。

7.5 typedef 函数指针。

7.6 calloc将分配空间内容置0,但是不保证和浮点的0/NULL相同。

7.7 执行的时候才有所谓的堆和栈

7.8 有用于调试的符号表信息。

7.9 没有使用共享库时,可执行文件大部分都被标准IO库占用。

7.10 返回指向栈变量的指针 ?? 真有你的.jpg