计算机 / 读书笔记 · 2021年12月19日 0

APIUE读书笔记chapter7

进程终止

进程终止的8种方式

  1. 从main中(自然)返回
  2. 调用exit
  3. 调用_exit或_Exit
  4. 最后一个线程自然退出
  5. 最后一个线程调用pthread_exit

异常终止的3种方式

  1. 调用abort
  2. 收到一个信号(signal)
  3. 最后一个线程响应撤销请求
#include <stdlib.h>

void exit(int status);

voiod _Exit(int status);

#include <unistd.h>

void _exit(int status);

main函数中的return status语句,等价于exit(status)语句。

atexit函数

对于ISO C,至少可以注册32个exit handlers被exit调用。调用的顺序与注册的顺序相反,且一个函数注册了多少次就会同样的调用多少次。

#include <stdlib.h>

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

命令行参数

环境变量列表

通过extern char **environ这个全局变量引用环境变量。
由于历史原因,多数UNIX系统为main函数提供了第三个参数代表环境变量:
int main(int argc, char *argv[], char *envp[]);
由于ISO C规定main函数有两个参数,而上面这种有三个参数的形式也不比通过全局变量来引用环境变量的方式好到哪去,所以POSIX.1规定应该使用environ全局变量而不是为main传入第三个参数。

C程序的内存布局

  • 代码段(Text segment)
  • 数据段(Initialized data segment,简称data segment),包含了程序中初始化了的(全局)变量。
  • bss段(block started by symbol),uninitialized data segment。在程序被执行前,此段中的数据被初始化为0或null指针。包含程序中未初始化的全局变量。
  • stack
  • heap,一般位于bss与栈之间

在32位Intel x86处理器上,Linux的text段从0x08048000开始(往上),stack则从0xC0000000(往下且不含)开始。
可以用size工具查看程序的各个段大小。

共享库

优点:

  • 同样的代码不用在每个程序中都保存一份;减少了单个程序的大小;
  • 可以单独更新共享库,而不需要重新链接可执行程序;

缺点:

  • 会带来运行时的一点点overhead(第一次运行该程序或者共享库里的函数被第一次调用时)

用gcc编译hello world例程时,加上-static选项就可以感受到用共享库与不用共享库时可执行程序大小的天壤之别了。

动态内存分配

标准malloc实现之外的几种动态内存分配实现:

  • libmalloc
    提供了mallopt,mallinfo等函数
  • vmalloc
    可以为不同类型的内存提供使用不同技术的alloctor。
  • quick-fit
    貌似是最早使用分离链表技术的动态内存分配器?
  • jemalloc
    适用于多处理器多线程场景下的动态内存分配器,貌似在和Android有关的什么地方看到过
  • TCMalloc
    Google搞的貌似很厉害的库?

alloca函数

从栈上开辟内存,当调用alloca的函数返回时alloca开辟的内存被自动释放。有些系统不提供alloca函数,不过一般的Unix/Linux系统都支持。

环境变量

最好使用getenv、putenv、setenv等函数获取、修改环境变量,而不是手动通过environ变量来直接操作。

#include <stdlib.h>

char *getenv(const char *name);

int putenv(char *str);

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

int unsetenv(const char *name);

其中注意putenv和setenv的区别:setenv的实现总会额外开辟空间来存放由参数组成的name=value形式的字符串,而putenv可能直接将传入的字符串指针加入到环境变量列表里了。所以传入putenv的字符串不能是在栈上开辟的(一旦相应的函数返回这个函数frame的空间就回收了),需要是在堆中开辟的。
而对于环境变量列表的实现,在需要的时候该环境变量列表会转移到堆中(参看原书213页,懒得赘述)。

setjmp与longjmp

goto只能在一个函数的内部进行跳转,而使用setjmp、longjmp我们可以进行跨stack frames的跳转(准确地说,是跳转到调用了setjmp的地方)。

#include <setjmp.h>

int setjmp(jmp_buf env);

void longjmp(jmp_buf env, int val);

在调用longjmp后,automatic、register和volatile等类型变量的值:
大多数实现不会回滚automatic和register类型变量的值,而标准也只说他们的值是不确定的。如果有确定不想要回滚的automatic变量,可以用volatile属性修饰。
在执行longjmp时,全局或者static变量不会被回滚。
如果想要写robust、portable的使用了non-local jump的程序,那么需要记住longjmp时,只有全局变量、static变量和volatile变量的值不会回滚,automatic变量和register变量则会视情况回滚或不回滚。

getrlimit和setrlimit函数

#include <sys/resource.h>

int getrlimit(int resource, struct rlimit *rlptr);

int setrlimit(int resource, const struct rlimit *rlptr);

struct rlimit {
	rlimit_t rlim_cur;	/* soft limit: current limit */
	rlimit_t rlim_max;	/* hard limit: maximum value for rlim_cur */
};

关于rlimit的3个规则:

  1. 进程可以更改soft limit,但是不能超过相应的hard limit;
  2. 进程可以修改hard limit为不小于soft limit的值,而且对普通用户而言,这种更改是不可逆的;
  3. 只有超级用户可以提升hard limit的值。

各rlimit的值由process 0建立,并且由子进程继承。
常用rlimit:RLIMIT_AS,RLIMIT_CORE,RLIMIT_CPU,RLIMIT_DATA,RLIMIT_FSIZE,RLIMIT_MEMLOCK,RLIMIT_MSGQUEUE,RLIMIT_NICE,RLIMIT_NOFILE,RLIMIT_NPROC,RLIMIT_NPTS,RLIMIT_RSS,RLIMIT_SBSIZE,RLIMIT_SIGPENDING,RLIMIT_STACK,RLIMIT_SWAP,RLIMIT_VMEM。

关于宏的一个小点:#name表示生成一个"name"字符串。