《UNIX环境高级编程》第七章进程环境
7.2 main函数
1.C程序总是从main函数开始执行的,原型:int main(int argc,char *argv[]);
argc是命令行参数的个数
argc是指向参数的各个指针所构成的数组
2.内核执行C程序时,在调用main前先调用一个特殊的启动例程。可执行程序文件将此启动例程作为程序的起始地址。启动例程从内核取得命令行参数和环境变量值,然后为按照上述方式调用main函数做好安排。(这是由连接编辑器设置的,而连接编辑器则由C编译器调用)
启动例程有点像这样子:
exit(main(argc, argv));
7.3 进程终止
有8中方式使进程终止,其中5种是正常终止,分别是:
1)从main返回
2)调用exit
3)调用_exit或Exit
4)最后一个线程从其启动例程返回
5)从最后一个线程调用pthread_exit
异常终止有三种:
1)调用abort
2)接到一个信号
3)最后一个线程对取消请求做出相应
1.退出函数
3个函数用来正常终止一个程序:_exit和_Exit立即进入内核,exit会先执行一些清理处理,然后返回内核。
这3个函数都带一个整型参数,称为终止状态(或退出状态,exit status)。可以通过shell检查进程终止的状态。
下面三种情况进程终止状态是未定义的:
a.调用这写函数时不带终止状态
b.main函数执行了一个无返回值的return语句
c.main没有声明返回类型为整型
特殊情况:若main的返回类型是整型,并且main执行到最后一条语句时返回,那么进程是终止状态是0.
2.atexit函数
这个函数可以用来登记函数进给进程。登记的函数将由exit自动调用。
按照ISO C的规定,一个进程可以登记多至32个函数。先登记的后调用,同一函数登记多次则会被调用多次。
原型:
#include<stdlib.h>
int atexit(void(fun*)(void)); // 调用时传一个函数指针就可以了。
7.4 命令行参数
7.5 环境表
每个程序都接收到一张环境表。跟参数列表一样,环境表也是一个字符指针数组。其中每个指针包含一个以null结束的C字符串的地址。
全局变量environ则包含了该指针数组的地址。
打印环境表:
extern char** environ; void printEnv() { char **env = environ; while(*env) { printf("%s\n", *env); env++; } }
7.6 C程序的存储空间布局(这个是重点)
历史沿袭至今,C程序一直由下列几部分组成:
地址由低到高:
正文段(代码段):由CPU执行的机器指令部分。通常,正文段是可共享的。存放的有:代码、const全局变量、const静态变量、字符串字面值
数据段(已经初始化了):包含了程序中明确的赋初值的变量。例如:C程序任何函数之外的声明。
BSS段(未初始化数据段):未初始化的全局变量和静态变量
堆区:用来动态分配的内存,malloc出来的东西(由低向高扩散)
栈区:自动变量以及每次函数调用时所需保存的信息保存在此段中。(由高向低扩散)。主要有非静态的普通局部变量,函数参数,函数返回值,匿名变量。
命令行参数和环境变量:
可以用size查看正文段、数据段、bss段的长度(以字节为单位):
xcy@xcy-virtual-machine:~/test/unix$ sizea.out text data bss dec hex filename 1943 584 16 2543 9ef a.out xcy@xcy-virtual-machine:~/test/unix$
第4列表示十进制的3段总和,第5列表示十六进制的三段总和。
7.7 共享库
共享库使得可执行文件中不再需要包含公用的库函数,而只需要在所有进程都可引用的存储区中保存这种库例程的一个副本。
程序第一次执行或第一次调用某个库函数时,用动态链接方法将程序与共享库函数相链接。
这减少了每个可执行文件的长度,但是增加了一些运行时间开销。这种时间开销发生在该程序第一次被执行时。
还有一个优点:可以用库函数的新版本代替老版本而不需要对使用该库的程序重新连接编辑。(这么一看有点像动态库)
在不同的系统中,程序可能使用不同的方法说明是否要使用共享库。
例如:
xcy@xcy-virtual-machine:~/test/unix$ gcc -static hello.c // 阻止gcc使用共享库 xcy@xcy-virtual-machine:~/test/unix$ size a.out text data bss dec hex filename 783675 7532 9600 800807 c3827 a.out xcy@xcy-virtual-machine:~/test/unix$ gcc hello.c // gcc 默认使用共享库,可以看到正文和数据段的长度明显减小。 xcy@xcy-virtual-machine:~/test/unix$ size a.out text data bss dec hex filename 1943 584 16 2543 9ef a.out xcy@xcy-virtual-machine:~/test/unix$
7.8 存储空间分配
1)malloc:分配指定字节数的存储区。次存储区中的初始值不确定
2)calloc:为指定数量指定长度的对象分配存储空间。该空间中的每一位(bit)都初始化为0
3)realloc:增加或减少以前分配区长度。当增加长度时,可能需要将以前分配区的内容移到另一个足够大的区域,以便在尾端提供增加的存储区,而新增区域内的初始值是不确定的。
这三个分配函数所返回的指针一定是适当对齐的,使其可以用于任何数据对象。返回值都是void*
7.9 环境变量
环境变量字符串的形式是: name=value
UNIX 内核并不查看这些字符串,它们的解释完全取决于各个应用程序。
ISO C定义了一个函数getenv。可以用其取环境变量值。
还可以设置环境变量,比如改变环境变量、增加新的环境变量。
#include<stdlib.h>
int putenv(char *str); //取形式为name=value的字符串,将其放到环境表中。若name已经存在,则会删除之前的定义。
int setenv(const char *name, const char*value, int rewrite); // 设置环境变量。若name存在,rewrite表示是否删除现有的定义。
int unsetenv(const char *name); // 删除name的定义,即使不存在也不算出错。
注意:环境表和环境字符串通常占用的是进程地址空间的顶部,所以它不能在向高地址扩展了;同时也不能移动在它之下的各栈帧,所以也不能向低地址方向扩展。
那么是如何实现上述操作的呢?(这个也需要理解)
1)删除:比较简单,先在环境表中找到改指针,然后将所有后续指针都向环境表首部顺次移动一个位置。
2)修改:分两种情况
a:若新value的长度小于等于现有value的长度,那么就直接复制到原来的空间中就好了
b:假如新value更长,就需要先malloc为新字符串分配空间,然后将新字符串复制到该空间中,接着使环境表中的针对name的指针指向新分配区。
3)增加:先调用malloc为name=value字符串分配空间。接着:
a:如果是第一次增加一个新的name,就需要调用malloc为新的指针表分配空间。接着将原来的环境表分配到新的分配区,并将指向新name=value字符串的指针存放在改指针表的末尾,然后又存放一个null指针在最后。最后使environ指向新的指针表
b:如果不是第一次增加name,就知道已经用malloc在对中为环境表中分配了空间,就只要调用realloc,以分配比原空间多存放一个指针的空间,然后将指向新的name=value字符串的指针存放在该表尾,后面接一个空指针。
7.10 函数setjmp和longjmp
这两个函数可以实现跨越函数类型的跳转。
栈帧:每个被调用的函数在栈里都有自己的函数栈。每个函数栈就叫栈帧。(这个是我自己的理解)
这两个函数不是普通的C语言goto语句在一个函数内实施的跳转,而是在栈上调用若干调用帧,返回到当前函数调用路径上的某一函数。
详细的东西没有仔细看。有人看我再写这节的内容。
静态变量,程序在启动的时候,便为该变量分配了内存空间,程序中用extern和static关键标志,程序一开始执行的时候就已经存在了,但是不等于它们在整个程序中可用。
动态变量,也叫自动存储变量。c++把变量默认为自动存储。用static说明的局部变量只能在定义该变量的函数体中使用。不过与自动变量不同的是,static静态变量在第一次使用时进行初始化(默认初始值为0)。
函数退出时,系统保持该变量的值和存储空间。然后你下次调用这个函数时,static变量还是上次退出函数时的值。
7.11 函数getrlimit和setrlimit
每个进程都有一组资源限制,其中一些可以用getrlimit和setrlimit函数查询和更改
#include <sys/time.h>
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
在更改资源限制时,须遵循下列三条规则:
1)任何一个进程都可以将一个软限制值更改为小于或等于其硬限制值
2)任何一个进程都可降低其硬限制值,但是它必须大于等于其软限制值。这种降低对于普通用户而言是不可逆的。
3)只有超级用户进程才能提高硬限制值
下面的函数可以打印出各个限制,具体限制的定义就不列出来了:
#include<stdio.h> #include<sys/resource.h> #include"comm.h" // 自定义头文件,内容见下面 /* #ifndef __COMM_H__ #define __COMM_H__ #include<errno.h> #include<stdlib.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) #endif // __COMM_H__ */ #define doit(name) pr_limits(#name, name) static void pr_limits(char *name, int resource) { struct rlimit limit; unsigned long long lim; if(getrlimit(resource, &limit) < 0) ERR_EXIT("getrlimit"); 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("(indinite) "); } else { lim = limit.rlim_max; printf("%10lld", lim); } putchar((int)'\n'); } int main() { #ifdef RLIMIT_AS doit(RLIMIT_AS); // 进程总的可用存储空间的最大长度(字节) #endif doit(RLIMIT_CORE); // core文件的最大字节数。为0表示阻止创建core文件 doit(RLIMIT_CPU); // CPU时间的最大量值(秒)。超过此软限制时,向该进程发送SIGXCPU信号 doit(RLIMIT_DATA); // 数据段的最大字节长度 doit(RLIMIT_FSIZE); // 可用创建的文件的最大字节长度 #ifdef RLIMIT_MEMLOCK doit(RLIMIT_MEMLOCK); // 一个进程使用mlock(2)能够锁定在存储空间中的最大字节长度 #endif #ifdef RLIMIT_MSGQUEUE doit(RLIMIT_MSGQUEUE); // 进程为POSIX消息队列可分配的最大存储字节数 #endif #ifdef RLIMIT_NICE doit(RLIMIT_NICE); // 影响进程的调度优先级 #endif doit(RLIMIT_NOFILE); // 每个进程能打开的最多文件数 #ifdef RLIMIT_NPROC doit(RLIMIT_NPROC); // 每个实际用户ID可拥有的最大子进程数 #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); // 一个进程可排队的信号的最大数量 #endif doit(RLIMIT_STACK); // 栈的最大字节长度 #ifdef RLIMIT_SWAP doit(RLIMIT_SWAP); // 用户可消耗的交换空间的最大字节数 #endif #ifdef RLIMIT_VMEM // RLIMIT_AS的同义词 doit(RLIMIT_VMEM); #endif exit(0); }
在虚拟机中运行:乌班图14.04,64位的结果如下:
xcy@xcy-virtual-machine:~/test/unix$ ./a.out RLIMIT_AS (infinite) (indinite) RLIMIT_CORE (infinite) (indinite) RLIMIT_CPU (infinite) (indinite) RLIMIT_DATA (infinite) (indinite) RLIMIT_FSIZE (infinite) (indinite) RLIMIT_MEMLOCK 65536 65536 RLIMIT_MSGQUEUE 819200 819200 RLIMIT_NICE 0 0 RLIMIT_NOFILE 1024 4096 RLIMIT_NPROC 15636 15636 RLIMIT_RSS (infinite) (indinite) RLIMIT_SIGPENDING 15636 15636 RLIMIT_STACK 8388608 (indinite) xcy@xcy-virtual-machine:~/test/unix$