UNIX进程的环境
了解main函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int main (int argc , char *argv[] , char *envp[]) ;#include <stdio.h> extern char **environ ; int main (void ) { while (*environ) { printf ("%s\n" ,*environ); *environ++ ; } return 0 ; }
main启动过程
内核启动C程序时(使用exec启动,内核使程序执行的唯一方法是调用一个exec函数),在调用main前先调用一个特殊的起动例程(这个起动例程被指定为程序的起始地址),起动例程从内核取得命令行参数(argc , argv)和环境变量值(envp),然后为调用main函数作安排
程序结束时,起动例程将调用main函数返回。调用形式:
1 exit (main(argc , argv));
一个C程序的起动和终止
进程终止
终止方式
正常终止
从main返回
调用exit(需要先执行一些标准IO库的清除处理,然后进入内核)
调用_exit(立即进入内核)
异常终止
exit和_exit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdlib.h> void exit (int status) ;#include <unistd.h> void _exit(int status);
atexit函数用来登记终止处理程序(一个进程可以登记多至32个函数,这些函数由exit自动调用)
1 2 3 4 5 6 7 8 #include <stdlib.h> int atexit (void (*func)(void )) ;
atexit应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include "../../include/apue.h" #include "../../include/standard_error.h" static void my_exit1(void) , my_exit2(void) ; int main (void ) { if (atexit(my_exit1) != 0 ) { err_sys("can't register my_exit1" ); } if (atexit(my_exit2) != 0 ) { err_sys("can't register my_exit2" ); } if (atexit(my_exit2) != 0 ) { err_sys("can't register my_exit2" ); } 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" ); }
C程序的存储空间布局
C程序由下面几部分组成
.text
文本段,保存的是代码(程序代码在存储器中的副本),只读,通常可以共享
.data
初始化数据段,包含了程序中需要赋值的变量,为它们开辟空间存放,初始化后的全局变量和静态局部变量都放在这里,属于静态内存分配
.bss
未初始化数据段,在程序开始执行之前,内核将此段初始化为0,未初始化的全局变量和静态局部变量都放在这里(也可以放在data中,但是会增加文件大小),属于静态内存分配
.rodata
存放C中的字符串和#define定义的常量
heap
堆用来存放进程运行中被动态分配的内存段,可以动态扩张和缩减,我们使用malloc函数就是在堆中取空间,使用free函数释放这些分配的空间
在32位下,堆最大可分配2.9G(程序逻辑地址大小,是虚拟的内存空间大小)
stack
自动变量(用户创建的临时变量)以及每次函数调用时所需保存的信息都放在此段中
函数在被调用时,其参数会被压入栈中,调用结束后,函数返回值也会被存放到栈中
可以把栈看做寄存交换临时变量的内存区
comment
存放编译器版本信息
其他的
.ELF header: 它描述整个文件的基本属性,比如ELF文件版本,目标机器型号,程序入口地址等。
.debug段: 包含调试信息。
.line段:调试时的行号,即源代码行号与编译后指令的对应表。
.hash段:符号哈希表。
.strtab段:String Table :字符串表,用于存储ELF文件中用到的各种字符串。
下图显示了段的典型安排(正文段从0开始,栈顶从0x7fffffff开始)
静态库
静态库在linux上以.a结尾,在windows上以.lib结尾
静态库的代码在编译时就拷贝到应用程序中,成为程序的一部分。这样可以节省编译时间,并且程序不需要依赖就能使用。但是这使得程序体积很庞大。
创建静态库
my_lib.h
1 2 3 4 5 #ifndef _my_lib_h_ #define _my_lib_h_ void welcome (void ) ;void outstring (const char *str) ;#endif
my_lib.c
1 2 3 4 5 6 7 8 9 10 11 12 13 #include "my_lib.h" #include <stdio.h> void welcome (void ) { printf ("Welcome to libmylib\n" ); } void outstring (const char *str) { if (str != NULL ) { printf ("%s\n" ,str); } }
编译成静态库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 gcc -o my_lib.o -c my_lib.c ar rcs libmylib.a mylib.o
测试函数test.c
1 2 3 4 5 6 7 8 #include "my_lib.h" #include <stdio.h> int main (void ) { printf ("create and use library\n" ); welcome(); outstring("It's successful.\n" ); }
使用静态库
1 2 3 4 5 6 gcc -o test test.c -lmylib
共享库
共享库和动态库就是一个东西(在linux上叫共享对象库,如:libglibc.so.6;在windows上叫动态加载函数库,以.dll结尾)
用动态连接方法将程序和共享库连接,减少了文件大小,但增加了运行时开销
共享库的另一个优点是可以用库函数的新版代替旧版而不需要程序重新编辑连接
创建共享库
1 2 3 4 5 6 gcc -fPIC -o mylib.o -c my_lib.c gcc -shared -o libttt.so mylib.o gcc -fPIC -shared -o libttt.so my_lib.c
使用动态库
1 2 3 4 5 6 7 8 9 10 gcc -o main test.c ./libttt.so cp libttt.so /usr/lib/libttt.so gcc -o main test.c /usr/lib/libttt.so
存储器分配
存储空间动态分配函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <stdlib.h> void *malloc (size_t size) ;void *calloc (size_t nobj , size_t size) ;void *realloc (void *ptr , size_t size) ;void free (void *ptr) ;
环境变量
环境变量的形式是键值对
环境变量(char)=环境变量值(char)
环境变量函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <stdlib.h> char *getenv (const char *name) ;int putenv (const char *str) ;int setenv (const char *name , const char *value , int rewrite) ;void unsetenv (const char *name) ;
函数修改环境变量的过程
环境变量表在程序运行后就被加载到程序中。
位置
在进程存储空间的顶部,也就是栈之上。
删除字符串
找到指向该字符串的指针,然后将所有后续指针都向下移一个位置(向下移表示最上面的被抛弃)
增加字符串(由于栈以上空间已在进程存储空间顶部,无法扩充)
修改现存name
新value<=旧value , 只需要在原字符串空间写入新字符串
新value>旧value , 需要调用malloc函数为新字符串分配空间
增加一个name(需要调用malloc为name=value分配空间)
第一次增加一个新name,需要调用malloc为新的指针表分配空间,将原来的环境表复制到新分配区,并将新name=value的指针存在该指针表的表尾,然后将一个空指针存在气候,最后使environ指向新指针表。
如果不是第一次增加,则只需要调用realloc,重新分配原空间,将name=value存入表尾,后面跟一个空指针。
setjmp和longjmp函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 #include "../../include/apue.h" #include <setjmp.h> #define TOK_ADD 5 void do_line (char *) ;void cmd_add (void ) ;int get_token (void ) ;jmp_buf jmpbuffer ; int main (void ) { char line[MAXLINE]; if (setjmp(jmpbuffer) != 0 ) perror("error" ); while (fgets(line , MAXLINE , stdin ) != NULL ) do_line(line); exit (0 ); } char *tok_ptr ; void do_line (char *ptr) { int cmd ; tok_ptr = ptr ; while ((cmd = get_token()) > 0 ) { switch (cmd) { case TOK_ADD : cmd_add(); break ; } } } void cmd_add (void ) { int token ; token = get_token(); if (token < 0 ) { longjmp(jmpbuffer , 1 ); } } int get_token (void ) {}
**longjmp对自动,寄存器和易失变量的影响**
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <setjmp.h> #include "../../include/apue.h" static void f1 (int , int , int ) ;static void f2 (void ) ;static jmp_buf jmpbuffer ;int main (void ) { int count ; register int val ; volatile int sum ; count = 2 ; val = 3 ; sum = 4 ; if (setjmp(jmpbuffer) != NULL ) { printf ("after longjmp : count=%d,val=%d,sum=%d\n" ,count , val , sum); exit (0 ); } count = 97 ; val = 98 ; sum = 99 ; f1(count , val , sum); } static void f1(int i , int j , int k) { printf ("in f1(): counf=%d , val=%d , sum=%d\n" ,i,j,k); f2(); } static void f2(void ) { longjmp(jmpbuffer , 1 ); }
自动变量问题
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <stdio.h> #define DATAFILE "datafile" static char databuf[BUFSIZ]; FILE * open_data(void ) { FILE *fp ; if ((fp = fopen(DATAFILE , "r" )) == NULL ) { return NULL ; } if (setvbuf(fp , databuf , _IOLBF , BUFSIZ) != 0 ) { return NULL ; } return fp ; }
getrlimit和setrlimit函数
作用
用于查询和更改进程资源限制(在linux中以ulimit命令显现)
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <sys/time.h> #include <sys/resource.h> int getrlimit (int resource , struct rlimit *rlptr) ;int setrlimit (int resource , const struct rlimit *rlptr) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <sys/types.h> #include <sys/time.h> #include <sys/resource.h> #include "../../include/apue.h" #define doit(name) pr_limits(#name,name) static void pr_limits(char * , int ); int main(void ) { doit(RLIMIT_CORE); doit(RLIMIT_CPU); doit(RLIMIT_DATA); doit(RLIMIT_FSIZE); #ifdef RLIMIT_MEMLOCK doit(RLIMIT_MEMLOCK); #endif #ifdef RLIMIT_OFILE doit(RLIMIT_OFILE); #endif #ifdef RLIMIT_NPROC doit(RLIMIT_NPROC); #endif #ifdef RLIMIT_RSS doit(RLIMIT_RSS); #endif #ifdef RLIMIT_STACK doit(RLIMIT_STACK); #endif #ifdef RLIMIT_VMEM doit(RLIMIT_VMEM); #endif exit (0 ); } static void pr_limits(char *name , int resource) { struct rlimit limit ; if (getrlimit(resource , &limit) < 0 ) { err_sys("getrlimit error for %s" ,name); } printf ("%-14s" ,name); if (limit.rlim_cur == RLIM_INFINITY) { printf ("(infinity) " ); }else { printf ("%10ld " ,limit.rlim_cur); } if (limit.rlim_max == RLIM_INFINITY) { printf ("(infinity) " ); }else { printf ("%10ld " ,limit.rlim_max); } }
自言自语
暂时了解了在Unix下的一些关于进程的基础知识。