1、进程的终止方式
通常情况下,进程有八种终止方式(5种正常终止 + 3种异常终止)
正常终止:
(1) main 的 正常 return
(2)调用 exit 退出
(3)调用 _exit 和 _Exit
(4)最后一个线程从启动历程返回
(5)最后一个线程调用pthread_exit退出
异常终止:
(1)调用 abort()
(2)接到一个信号
(3)最后一个线程 对 取消请求做出响应
1、有三种退出函数如下:
这三个函数都属于进程正常退出的函数,_exit 和 _Exit 会直接进入内核,exit会先执行一些清理处理,然后进入到内核。
exit 函数总会执行一个标准IO的清理关闭操作,对所有打开的文件流都会调用fclose().这三个函数都有一个整型参数,代表终止状态。
2、atexit 函数
可以登记一个在退出时执行的函数,有点析构函数的感觉。在ISO C里面规定了,一个进程最多只能登记32个函数。登记的函数无需参数,也没有返回值。atexit 也没有传参和处理返回值的方式。注意:exit 之后调用他们的顺序与登记时候的顺序相反,并且同一个函数被登记多次,也会被调用多次。下面是函数原型以及源码例子:
#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");
}
运行结果:
main is done
first exit handler
first exit handler
second exit handler
3、环境表
每一个程序都有一个环境表,它是以字符串数组形式呈现的,每一项都以 '\0' 结尾。其格式也是name=value, 如书中下表:
主函数中的参数形式如下:
4、C程序的存储空间布局
C语言的存储空间布局主要有:正文段、初始化数据段、未初始化数据段、栈、堆
(1) 正文段:CPU执行的机器指令部分。通常是可共享的,频繁执行的程序在存储器中也只需要一个副本。并且他是只读的,以防止被修改。
(2) 初始化数据段:通常称为数据段。主要存放一些已初始化的全局变量。
(3) 未初始化数据段:通常称为 bss 段(block started by symbol 有符号开始的块)。在程序开始时,内核会将这些数据初始化为0,或者是NULL指针。
(4) 栈。程序在运行时,编译器自动分配的一块连续存储区域,存放函数中的参数、局部变量等。不能动态申请、一切都靠编译器控制。如果超出了存储大小会出现栈溢出错误。入栈弹栈方式:后进先出
(5) 堆。可以动态申请的存储区。通常是大块存储。该区域需要手动申请手动释放,若不释放会造成内存泄漏。或是在程序结束后系统回收。
注意:
A 、static 局部变量,初始化的放在数据段,未初始化的放在 bss 段。数据段和bss段的区别就在于,是否初始化。在网上会有静态存储区一说,我觉得就是整个数据段 + bss段 + 代码段,数据段和bss段主要包含的是全局变量、静态局部变量、常量。
B、linux 中 可以使用size命令,查看各段长度:
书中有张图:
5、对存储空间进行动态分配
主要有三个函数:
malloc 和 free 不解释;
calloc :为指定的对象,分配一块相应个数的空间,并且将这段空间清零。
realloc:在一个存储空间的位置上,申请一块空间,可能小,可能大。如果大,并且影响到了其他内存,则会申请一块新区域将该区域的数据拷贝到这块新区域并返回指针。老区域会释放。
alloca 栈上的分配:
6、可跨函数跳跃的 setjmp 和 longjmp
C语言的 goto 只能实现函数内的跳跃,要跨函数的跳跃需要用到 setjmp 和 longjmp。书上举了一段代码的例子,他存储的地址如下:
在上述的案例中,假设 cmd_add 出现了错误,函数会打印错误信息后返回,一直到main函数。该例子cmd_add比main函数低两个层级,如果低五个层级的话,一层一层的返回就显得特别麻烦。为了解决这一问题,就是用了setjmp 和 longjmp。
goto是在函数内跳转;setjmp 和 longjmp 是在栈上跳转。
#include <setjmp.h>
int setjmp(jmp_buf env);
返回值:若直接调用,返回0;若从longjmp返回,则为非0
void longjmp(jmp_buf env, int val);
#include "apue.h"
#include <setjmp.h>static void f1(int, int, int, int);
static void f2(void);static jmp_buf jmpbuffer;
static int globval;int
main(void)
{
int autoval;
register int regival;
volatile int volaval;
static int statval;globval = 1; autoval = 2; regival = 3; volaval = 4; statval = 5;
if (setjmp(jmpbuffer) != 0) {
printf("after longjmp:\n");
printf("globval = %d, autoval = %d, regival = %d,"
" volaval = %d, statval = %d\n",
globval, autoval, regival, volaval, statval);
exit(0);
}/*
* Change variables after setjmp, but before longjmp.
*/
globval = 95; autoval = 96; regival = 97; volaval = 98;
statval = 99;f1(autoval, regival, volaval, statval); /* never returns */
exit(0);
}static void f1(int i, int j, int k, int l)
{
printf("in f1():\n");
printf("globval = %d, autoval = %d, regival = %d,"
" volaval = %d, statval = %d\n", globval, i, j, k, l);
f2();
}static void f2(void)
{
longjmp(jmpbuffer, 1);
}
运行结果:
in f1():
globval = 95, autoval = 96, regival = 97, volaval = 98, statval = 99
after longjmp:
globval = 95, autoval = 96, regival = 97, volaval = 98, statval = 99
将希望返回的位置调用一次 setjmp,然后通过 longjmp 可以跳跃回 setjmp 的地方。
在设置这个栈跳跃点的时候setjmp 返回的是0,
在调用 longjmp 跳跃到栈点时,setjmp 返回的是longjmp的二参,这样可以方便判断这个跳跃是从哪里来的。
setjmp 的参数是一个特殊类型的 jmp_buf 的变量,在调用 longjmp 时需要一个保存了栈信息的变量,就用 jmp_buf 数据类型保存栈的信息。
自动变量、寄存器变量、易变变量是否会出现值得回滚(也就是上例中的1、2、3、4、5)?
根据上例的结果看是不会回滚的,但是还要看情况,大多数的实现不会回滚自动变量和寄存器变量中的值,但这都不一定。如果有一个自动变量,我们不想让他回滚,那么我们可以加一个volatile 属性。声明为static 或者是 全局变量,在执行longjmp时保持不变。
注意:
1、如果将上述程序进行1级优化,得到的结果如下:
gcc -O testjmp.c
in f1():
globval = 95, autoval = 96, regival = 97, volaval = 98, statval = 99
after longjmp:
globval = 95, autoval = 2, regival = 3, volaval = 98, statval = 99
-O、-O1:优化编译需要更多时间,并且大型函数需要更多内存。使用-O选项,编译器会尝试减小代码尺寸减少执行时间,不执行任何需要大量编译时间的优化。
经过优化之后,自动变量 和 寄存器变量现在在寄存器中,在调用longjmp之后,恢复为之前setjmp的栈环境,这时如果一直在内存中的数据就会改变,在寄存器中的数据不改变。
2、书中举了一个局部变量作为缓冲区的错误例子:
7、getrlimit 函数 和 setrlimit 函数
注意:
进程资源的限制,会影响到子进程的继承。为了影响一个用户的所有后续进程,需将资源限制的设置构造在shell中,可以使用ulimit 或者 limit
#include "apue.h"
#include <sys/resource.h>#define doit(name) pr_limits(#name, name)
static void pr_limits(char *, int);
int
main(void)
{
#ifdef RLIMIT_AS
doit(RLIMIT_AS);
#endifdoit(RLIMIT_CORE);
doit(RLIMIT_CPU);
doit(RLIMIT_DATA);
doit(RLIMIT_FSIZE);#ifdef RLIMIT_MEMLOCK
doit(RLIMIT_MEMLOCK);
#endif#ifdef RLIMIT_MSGQUEUE
doit(RLIMIT_MSGQUEUE);
#endif#ifdef RLIMIT_NICE
doit(RLIMIT_NICE);
#endifdoit(RLIMIT_NOFILE);
#ifdef RLIMIT_NPROC
doit(RLIMIT_NPROC);
#endif#ifdef RLIMIT_NPTS
doit(RLIMIT_NPTS);
#endif#ifdef RLIMIT_RSS
doit(RLIMIT_RSS);
#endif#ifdef RLIMIT_SBSIZE
doit(RLIMIT_SBSIZE);
#endif#ifdef RLIMIT_SIGPENDING
doit(RLIMIT_SIGPENDING);
#endifdoit(RLIMIT_STACK);
#ifdef RLIMIT_SWAP
doit(RLIMIT_SWAP);
#endif#ifdef RLIMIT_VMEM
doit(RLIMIT_VMEM);
#endifexit(0);
}static void
pr_limits(char *name, int resource)
{
struct rlimit limit;
unsigned long long lim;if (getrlimit(resource, &limit) < 0)
err_sys("getrlimit error for %s", name);
printf("%-14s ", name);
if (limit.rlim_cur == RLIM_INFINITY) {
printf("(infinite) ");
} else {
lim = limit.rlim_cur;
printf("%10lld ", lim);
}
if (limit.rlim_max == RLIM_INFINITY) {
printf("(infinite)");
} else {
lim = limit.rlim_max;
printf("%10lld", lim);
}
putchar((int)'\n');
}
运行结果:
RLIMIT_AS (infinite) (infinite)
RLIMIT_CORE 0 (infinite)
RLIMIT_CPU (infinite) (infinite)
RLIMIT_DATA (infinite) (infinite)
RLIMIT_FSIZE (infinite) (infinite)
RLIMIT_MEMLOCK 65536 65536
RLIMIT_MSGQUEUE 819200 819200
RLIMIT_NICE 0 0
RLIMIT_NOFILE 1024 1048576
RLIMIT_NPROC 3611 3611
RLIMIT_RSS (infinite) (infinite)
RLIMIT_SIGPENDING 3611 3611
RLIMIT_STACK 8388608 (infinite)