[APUE] 进程环境

📓 APUE 一书的第七章学习笔记。java

进程终止

有 8 种方式可使得进程终止,5 种为正常方式:数组

  1. Return from main
  2. Calling exit()
  3. Calling _exit or _Exit
  4. Return of the last thread from its start routine
  5. Calling pthread_exit from the last thread

3 种非正常终止方式:bash

  1. Calling abort
  2. Receipt of a signal
  3. Response of the last thread to a cancellation request

退出函数

函数原型:架构

#include <stdlib.h> 
void exit(int status); 
void _Exit(int status); 
#include <unistd.h> 
void _exit(int status);

区别:_exit/_Exit 会当即进入内核;exit 先执行清理处理(对全部打开的 Stream 执行 fclose ),后进入内核。函数

exit(k) 至关于在 main 函数中 return k ,最近一个进程的退出码能够在 Shell 中使用 echo $? 获取。布局

回调函数 atexit

按照 ISO C 标准,一个进程最多能够登记 32 个回调函数,这些函数称为终止处理程序 (exit handler),由 exit 来调用。学习

函数原型:spa

int atexit(void (*func)(void));
// Returns: 0 if OK, nonzero on error

exit 调用终止处理函数的顺序与登记顺序相反(_exit/_Exit 则不会调用);若是登记屡次,也会被调用屡次。命令行

例子:指针

void test1() { printf("A "); }
void test2() { printf("B "); }
int main()
{
    atexit(test1);
    atexit(test1);
    atexit(test2);
    // 若是调用下面 2 个函数,则不会有输出
    // _exit(0), _Exit(0)
}
// Output: B A A

下图展现了一个 C 程序如何启动和终止的过程,也显示了 exit, _exit, _Exit, atexit 这 4 个函数的关系。

环境表

环境表 (Environment List), 与命令行参数 argv 同样,是一个 char* 数组。

下列程序能够打印全部的环境参数:

#include <stdio.h>
extern char **environ;
int main()
{
    int i;
    for (i = 0; environ[i] != NULL; i++)
        puts(environ[i]);
}

输出一大片:

TERM=xterm-256color
SHELL=/bin/bash
...
USER=sinkinben
LS_COLORS=rs=0:di=01;...
PATH=...
MAIL=/var/mail/sinkinben
PWD=/home/sinkinben/workspace/apue
HOME=/home/sinkinben
...
_=./a.out

C 存储布局

以下图所示,一个 C 程序由如下部分组成:

  • text 段:这是 CPU 执行的机器指令部分。text 段一般是只读(防止意外修改)和可共享的,即便是频繁执行的程序,它们的 text 段在内存中只会存在 1 个副本。(难怪个人 Mac 开机第一次调用 clang++ 会特别慢)
  • 初始化数据段(简称数据段):包括初始化的全局变量和 static 修饰的变量。
  • 未初始化的数据段(简称 bss 段):在程序开始执行前,由 exec 初始化为 0 或者空指针。(确实,我还觉得是随机值)
  • 栈:局部变量,函数调用。
  • 堆:动态内存分配。

这是一种典型的逻辑布局,但不是全部的实现都是如此,具体取决于实际的 OS 和硬件。对于 32 位 Intel x86 架构的 Linux,text 段从 0x80480000 开始,栈从 0xC0000000 开始向低地址增加。

能够经过 size 命令获取一个 C 程序的各个段大小,dec, hex 分别是前 3 个数字的总和的十进制和十六进制:

$ size /bin/bash 
   text    data     bss     dec     hex filename
 997958   36496   23480 1057934  10248e /bin/bash

共享库

共享库 (Shared Libraries): 对于一些经常使用的公共函数库,只须要在全部进程均可引用的存储区保存一份副本。程序第一次调用某个库函数的同时,用动态连接的方式将程序与库函数相连接。这就减小了可执行文件的大小,但增长了一些运行时开销。

使用 Shared Libraries 的另一个优势是:能够动态更新某个库的版本,而不须要从新对使用该库的程序从新连接。

使用 gcc -static 可指定生成的可执行文件使用静态连接。

$ gcc test.c ; size ./a.out 
   text    data     bss     dec     hex filename
   1099     544       8    1651     673 ./a.out
$ gcc test.c -static ; size ./a.out 
   text    data     bss     dec     hex filename
 823142    7284    6360  836786   cc4b2 ./a.out

动态内存分配

相关函数:

#include <stdlib.h>
void *malloc(size_t size);
void *calloc(size_t nobj, size_t size); 
void *realloc(void *ptr, size_t newsize);
// All three return: non-null pointer if OK, NULL on error
void free(void *ptr);

做用:

  • malloc: 申请指定字节数的内存,该内存的值不肯定。
  • calloc: 为指定数量,指定长度的对象分配内存,并初始化为 0 。
  • realloc: 增长或者减小 ptr 指向内存区的长度。当增长长度时,可能须要将以前的数据拷贝到另一个足够大的内存区(也有多是在原有基础上增长一段连续内存),新增区域的初始值不肯定。

环境变量

getenv

#include <stdlib.h>
char *getenv(const char *name);
// Returns: pointer to value associated with name, NULL if not found

获取环境变量。例子:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    puts(getenv("JAVA_HOME"));
}
// Output: /usr/local/java/jdk1.7

putenv, setenv, unsetenv

相关 API:

#include <stdlib.h> 
int putenv(char *str);
// Returns: 0 if OK, nonzero on error
int setenv(const char *name, const char *value, int rewrite); 
int unsetenv(const char *name);
// Both return: 0 if OK, −1 on error

改变当前进程以及后续产生的子进程的环境变量(其实是修改进程的环境表)。做用分别以下:

  • putenv: 把 name=value 的环境变量添加到环境表,若是 name 已存在,则删除原来的定义。
  • setenv: 将 name 设置为 valuerewrite = 0/1 表示是否覆盖已有的 name (若是有的话)。
  • unsetenv: 删除 name ,若是不存在则什么都不作。

getrlimit, setrlimit

每一个进程都有一组资源限制,能够经过 getrlimit, setrlimit 进行查询和修改:

#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlptr);
int setrlimit(int resource, const struct rlimit *rlptr);
// Both return: 0 if OK, −1 on error

resource 是形如 RLIMIT_CPU 的一组宏定义。

结构体 rlimit 的定义以下:

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

修改操做须要遵循如下规则:

  • cur <= max
  • 能够下降 rlim_max ,但必须大于等于 rlim_cur.
  • 只有超级用户进程能够提升 rlim_max

修改资源限制会应当当前进程和它的子进程,因此 Shell 中通常会内置 ulimit 命令来修改当前 Shell 的资源限制。

相关文章
相关标签/搜索