UNIX环境高级编程(第7章 进程环境)
2014-10-30 21:03
393 查看
1 C程序的启动与终止
C程序总是从main函数开始执行。main函数的原型:intmain(intargc, char *argv[]);当内核执行C程序时(使用一个exec函数),在调用main函数前先调用一个特殊的启动例程。可执行程序文件将此启动例程指定为程序的起始地址。启动例程从内核取得命令行参数和环境变量值,然后为按上述方式调用main函数做好安排。
启动例程一般是用汇编语言实现,作用是使得从main函数返回后立即调用exit函数。等价代码为:
exit(main(argc,argv));
1)main函数是被启动例程调用,故从main函数return时,仍返回到启动例程中,main函数的返回值被启动例程得到,并作为参数调用exit函数。
2)也可以在main函数中调用exit函数直接终止进程而不返回到启动例程。
内核执行程序的唯一方法是调用一个exec函数。进程自愿终止的唯一方法是显式或隐式地(通过调用exit)调用_exit或_Exit。进程也可非自愿地由一个信号使其终止。
进程终止的方式有8种,其中5种为正常终止:
1)从main返回,即return
2)调用exit
3)调用_exit或_Exit
4)最后一个线程从其启动例程返回
5)最后一个线程调用pthread_exit
异常终止的3种方式:
6)调用abort
7)接到一个信号并终止
8)最后一个线程对取消请求做出响应
1.1 exit函数
#include<stdlib.h> void exit(int status); void _Exit(int status); #include<unistd.h> void _exit(int status); 说明: 1) 参数:int status是终止状态 2) 功能:_exit和_Exit立即进入内核,exit则先执行一些清理处理(包括调用执行各终止处理程序,关闭所有标准I/O流等),然后进入内核。 |
1.2 atexit函数
exit函数的功能在进入内核前,会自动调用执行终止处理程序。一个进程最多可以登记32个终止处理程序(终止处理程序即是函数),可通过调用atexit函数来登记这些函数。#include <stdlib.h> int atexit(void (*func)(void)); //成功返回0,出错返回非0 |
#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");
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");
}
[root]# ./a.out main is done first exit handler second exit handler
2参数变量与环境变量
在历史上,大多数UNIX系统支持main函数带有3个参数,其中第二个参数是参数表,第三个参数是环境表。参数表与环境表都是字符指针数组。int main(intargc, char *argv[], char *envp[]);
对于第三个参数环境表envp,全局变量environ包含了改指针数组的地址:
extern char**environ;
因为ISO C规定main函数只有两个参数,而且第三个参数与全局变量environ相比也没用带来更多的益处,所以POSIX.1也规定应使用environ而不使用第三个参数。
2.1命令行参数
#include "apue.h"int main(int argc, char *argv[])
{
int i;
for (i = 0; i < argc; i++)
{
printf("argv[%d]: %s\n", i, argv[i]);
}
exit(0);
}
[root]# ./a.out arg1 TEST foo argv[0]: ./a.out argv[1]: arg1 argv[2]: TEST argv[3]: foo
2.2环境变量
通过export(1)命令可查看当前所有的环境变量。访问或设置环境变量不能直接使用全局变量environ,需要通过特定的函数:
#include<stdlib.h> char *getenv(const char *name); 返回值:指向与name关联的value的指针,若未找到则返回NULL |
#include<stdlib.h> int putenv(char *str); int setenv(const char *name, const char *value, int rewrite); int unsetenv(const char *name); 返回值:若成功返回0,若出错返回非0值。 |
3 C程序的存储空间布局
32位操作系统环境下,当创建一个进程时,都会为该进程分配一个4GB大小的虚拟进程地址空间。该空间主要分为以下几个部分:1)正文段:由CPU执行的机器指令部分。
2)初始化数据段:即数据段,包含了程序中需明确地赋初值的变量。任何函数外的赋值声明,如int
maxcount = 99;
3)未初始化数据段:即bss段,在程序开始执行之前,内核将此段中的数据初始化为0或空指针。任何函数外的未赋值的声明,如long
sum[1000];
4)栈:自动变量以及每次函数调用时所需保存的信息都存放在此段中。
5)堆:通常在堆中进行动态分配。
4函数间跳转
C语言的goto语句能在一个函数内实现跳转功能,但要在函数间跳转——即在栈上跳过若干调用帧、返回到当前函数调用路径上的某一个函数中,则需要使用setjmp和longjmp函数。头文件 | #include <setjmp.h> |
函数原型 | int setjmp(jmp_buf env); |
参数 | env:某种形式的数组,其中存放在调用longjmp时能用来恢复栈状态的所有信息。因需要在另一个函数中引用env变量,故需将env变量定义为全局变量。 |
返回 | 若直接调用则返回0,若从longjmp调用返回则返回非0值。 |
功能 | 在希望返回到的位置调用setjmp |
头文件 | #include <setjmp.h> |
函数原型 | void longjmp(jmp_buf env, int val); |
参数 | env:在调用setjmp时所用的env val:具有非0值,将是从setjmp处返回的值 |
返回 | 若直接调用则返回0,若从longjmp调用返回则返回非0值。 |
功能 | 返回到调用setjmp的位置 |
4.1自动、寄存器和易失变量
#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, regiva = %d, "
" volaval = %d, statval = %d\n",
globval , autoval, regival, volaval, statval);
exit(0);
}
globval = 95; autoval = 96; regival = 97; volaval = 98; statval = 99;
f1(autoval, regival, volaval, statval);
exit(0);
}
static void f1(int i, int j, int k, int l)
{
printf("in f1(): \n");
printf("globval = %d, autoval = %d, regiva = %d, "
" volaval = %d, statval = %d\n",
globval, i , j, k, l);
f2();
}
static void f2(void)
{
longjmp(jmpbuffer, 1);
}
[root]# gcc testjmp.c /*不进行任何优化的编译*/ [root]# ./a.out in f1(): globval = 95, autoval = 96, regiva = 97, volaval = 98, statval = 99 after longjmp: globval = 95, autoval = 96, regiva = 97, volaval = 98, statval = 99 [root]# gcc -O testjmp.c /*进行全部优化的编译*/ [root]# ./a.out in f1(): globval = 95, autoval = 96, regiva = 97, volaval = 98, statval = 99 after longjmp: globval = 95, autoval = 2, regiva = 3, volaval = 98, statval = 99
4.2自动变量的潜在问题
声明自动变量的函数已经返回后,不能再引用这些自动变量。#include <stdio.h> #define DATAFILE "datafile" FILE *open_data(void) { FILE *fp; char databuf[BUFSIZ]; if ((fp = fopen(DATAFILE, "r")) == NULL) return NULL; if (setvbuf(fp, databuf, _IOLBF, BUFSIZ) != 0) return NULL; return fp; }
问题:当open_data返回时,它在栈上所使用的空间将由下一个被调用函数的栈帧使用。但是,标准I/O库函数仍将使用其流缓冲区的存储空间。这就产生了冲突和混乱。
解决:在全局存储空间静态地(如static或extern)或者动态地(malloc)为数组databuf分配空间。
相关文章推荐
- UNIX环境高级编程 第7章 进程环境
- 进程环境 - UNIX环境高级编程-第7章
- UNIX环境高级编程第7章进程环境 命令行参数 环境表 存储布局
- UNIX环境高级编程-第7章- 进程环境
- 进程编程1 – Unix环境高级编程7章读书笔记
- 进程编程3 - UNIX高级环境编程第9章读书笔记
- 进程编程2 – Unix环境高级编程8章读书笔记
- 进程编程3 - UNIX高级环境编程第9章读书笔记
- 进程编程3 - UNIX高级环境编程第9章读书笔记
- 进程编程1 – Unix环境高级编程7章读书笔记
- 进程编程2 – Unix环境高级编程8章读书笔记
- 进程编程3 - UNIX高级环境编程第9章读书笔记
- 进程编程3 - UNIX高级环境编程第9章读书笔记
- 进程编程1 – Unix环境高级编程7章读书笔记
- 进程编程1 – Unix环境高级编程7章读书笔记
- 进程编程3 - UNIX高级环境编程第9章读书笔记
- 进程编程1 – Unix环境高级编程7章读书笔记
- 进程编程1 – Unix环境高级编程7章读书笔记
- 进程编程3 - UNIX高级环境编程第9章读书笔记
- 进程编程1 – Unix环境高级编程7章读书笔记