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

进程终止

有 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 种非正常终止方式:

  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 来调用。

函数原型:

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 的资源限制。

[APUE] 进程环境的更多相关文章

  1. (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

  2. 《UNIX环境高级编程》(APUE) 笔记第七章 - 进程环境

    7 - 进程环境 Github 地址 1. main 函数 C 程序总是从 main 函数 开始执行: int main(int argc, char *argv[]); \(argc\) 为命令行参 ...

  3. Unix编程第7章 进程环境

    准备雄心勃勃的看完APUE,但是总感觉看着看着就像进入一本字典,很多地方都是介绍函数的用法的,但是给出例子远不及函数介绍的多.而且这本书还是个大部头呢.第7章的讲的进程环境,进程是程序设计中一个比较重 ...

  4. Linux进程环境

    Linux下C程序都是main开始的,main函数的原型是: int main(int argc, char **argv) 其中argc是命令行参数的数目,argc是指向参数的各个指针所构成的数组. ...

  5. Unix环境高级编程(五)进程环境

    本章主要介绍了Unix进程环境,包含main函数是如何被调用的,命令行参数如何传递,存储方式布局,分配存储空间,环境变量,进程终止方法,全局跳转longjmp和setjmp函数及进程的资源限制. ma ...

  6. linux_api之进程环境

    本篇索引: 1.引言 2.main函数 3.进程的终止方式 4.exit和_exit函数 5.atexit函数 7.环境表 8.C程序程序空间布局 9.存储空间的手动分配 10.库文件 1.引言 一个 ...

  7. Linux/UNIX之进程环境

    进程环境 进程终止 有8种方式使进程终止,当中5中为正常终止,它们是 1)      从main返回 2)      调用exit 3)      调用_exit或_Exit 4)      最后一个 ...

  8. APUE(7)---进程环境

    一.main函数 C程序总是从main函数开始执行.main函数的原型是: int main(int argv, char *argv[]); 当内核执行C程序时,在调用main前先调用一个特殊的启动 ...

  9. apue学习笔记(第七章 进程环境)

    本章将了解进程的环境. main函数 C程序总是从main函数开始执行,main函数的原型是: int main(int argc,char *argv[]); 其中,argc是命令行参数的数目,ar ...

随机推荐

  1. 使用gitlab-runner本地验证.gitlab-ci.yml

    背景 在gitlab上配置新项目的CI的时候,需要编写项目的 .gitlab-ci.yml 文件. 每次修改 .gitlab-ci.yml 文件之后都要执行git push让GitLab去构建来验证当 ...

  2. 一劳永逸,解决基于 keep-alive 的后台多级路由缓存问题

    用过 vue-element-admin 的同学一定很清楚,路由的配置直接关系侧边栏导航菜单的展示,也得益于这种设计思路,几乎大部分后台框架都采用这个方案,当然也包括了我写的 Fantastic-ad ...

  3. WIN7远程桌面连接提示:“发生身份验证错误。要求的函数不受支持”

    问题 WIN7远程桌面连接–"发生身份验证错误.要求的函数不受支持" 最近WIN7升级补丁后发现远程桌面无法连接了,报"发生身份验证错误.要求的函数不受支持"的 ...

  4. [WPF] 让第一个数据验证出错(Validation.HasError)的控件自动获得焦点

    1. 需求 在上一篇文章 <在 ViewModel 中让数据验证出错(Validation.HasError)的控件获得焦点>中介绍了如何让 Validation.HasError 的控件 ...

  5. JDBC UPDATE误区

    1 package com.lykion; 2 3 import java.sql.Connection; 4 import java.sql.PreparedStatement; 5 import ...

  6. MongoDb学习(四)--Repository----语法关键字

    表7.查询方法支持的关键字 关键词 样品 逻辑结果 After findByBirthdateAfter(Date date) {"birthdate" : {"$gt& ...

  7. ORACLE的还原表空间UNDO写满磁盘空间,解决该问题的具体步骤

    产生问题的原因主要以下两点:1. 有较大的事务量让Oracle Undo自动扩展,产生过度占用磁盘空间的情况:2. 有较大事务没有收缩或者没有提交所导制:说明:本问题在ORACLE系统管理中属于比较正 ...

  8. HP PROLIANT DL388 GEN10 (故障3019)SPP损坏

    HP PROLIANT DL388 GEN10 (故障3019)SPP损坏 1. 开机硬件自检,提示错误ERROR 3019: 2. 根据服务器版本GEN10下载最新固件SPP,可找服务商或者HP售后 ...

  9. 【C++】《C++ Primer 》第五章

    第五章 语句 一.简单语句 表达式语句:一个表达式末尾加上分号,就变成了表达式语句. 空语句:只有一个单独的分号,记得注释说明提高代码可读性. 复合语句(块):用花括号 {}包裹起来的语句和声明的序列 ...

  10. LeetCode662 二叉树最大宽度

    给定一个二叉树,编写一个函数来获取这个树的最大宽度.树的宽度是所有层中的最大宽度.这个二叉树与满二叉树(full binary tree)结构相同,但一些节点为空. 每一层的宽度被定义为两个端点(该层 ...