• 07.进程环境


    当执行程序时,其main函数是如何被调用的;命令行参数是如何传送给执行程序的;典型的存储器布局是什么样式;如何分配另外的存储空间;进程如何使用环境变量;各种不同的进程终止方式等。另外,还将说明longjmp和setjmp函数以及它们与栈的交互作用。

     

    1、main函数

    C程序总是从main函数开始执行。

    当内核执行一个C程序时(使用一个exec函数),在调用main前先调用一个特殊的启动例程。可执行程序文件将此启动例程指定为程序的起始地址---这是由连接编辑器设置的,而连接编辑器则由C编译器调用。启动例程从内核取得命令行参数和设置变量值,然后为按上述方式调用main函数做好安排。

     

    2、进程终止 

    有5种正常进程终止:

    ①从main函数返回;

    ②调用exit;

    ③调用_exit或_Exit;

    ④最后一个线程从其启动例程返回;

    ⑤最后一个线程调用pthread_exit;

     

    有3中异常终止:

    ①调用abort;

    ②接到一个信号终止;

    ③最后一个线程对取消请求(pthread_cancel)作出响应;

     

    有三个函数用于正常终止一个程序:_exit和_Exit立即进入内核,exit则先执行一些清理处理(包括调用执行各终止处理程序,关闭所有标准I/O流(非文件描述符)等),然后进入内核。

     

    由于历史原因,exit函数总是执行一个标准I/O库的清理关闭操作:为所有打开流调用fclose函数,这会造成所有缓冲的输出数据都被冲洗(写到文件上)。

     

    未定义的终止状态

    	#include <stdlib.h>
        void exit(int status);
    	
    	#include <unistd.h>
    	void _exit(int status);
    
    	#include <stdlib.h>
    	void _Exit(int status);

    三个exit函数都带一个参数,称之为终止状态,如果

    a、若调用这些函数时不带终止状态;

    b、main执行了一个无返回值的return语句;

    c、main没有声明返回类型为整型;

    则该进程的终止状态为未定义的。

     

    main函数返回一整型值与用该值调用exit是等价的,于是在main函数中

    exit(0);

    等价于

    return 0;

     

    3、aexit函数

    int atexit(void (*function)(void));

     

    一个进程可以调用atext登记多达32个函数,这些函数将由exit自动调用,我们称这些函数为终止处理函数。

    exit调用这些函数的顺序与它们登记时候的顺序相反。同一函数如若登记多次,则也会被调用多次。

     

    下图为一个C程序是如何启动的,以及它可以终止的各种方式:

    图片1

    注意,内核使程序执行的唯一方法是调用一个exec函数。进程自愿终止的唯一方法是显式地或隐式地(通过调用exit)调用_exit或_Exit。进程也可非自愿地由一个信号使其终止。

     

    4、环境表

    每个进程都会有一张环境表,它是一个字符指针数组,其中每个指针包含一个以null结束的C字符串的地址。全局变量environ则包含了该指针数组的地址:

    extern char **environ;

    如下图所示:

    图片1

    通常用getenv和putenv函数来访问特定的环境变量,而不是用environ变量。但是,如果要查看整个环境,则必须使用environ指针。

     

    5、C程序的存储空间布局

    a、正文段(代码段)。这是由CPU执行的机器指令部分。正文段是可共享的,即使是频繁执行的程序(如文本编辑器、C编译器和shell等)在存储器中也只需一个副本,另外,正文段常常是只读的,以防止程序由于意外而修改其自身的命令。

    b、初始化数据段。通常将此段称为数据段,它包含了程序中需明确地赋初值的变量。如,C程序中出现在任何函数之外的声明:

    int	maxcount = 99;

    使此变量带有其初值存放在初始化数据段中。

    c、非初始化数据段。通常将此段称为bss段,这一名称来源于汇编运算符,意思是"block started by symbol"(由符号开始的块),在程序开始执行之前,内核将此段中的数据初始化为0或空指针。出现在任何函数外的C声明

    long sum[1000];

    使此变量放在非初始化数据段中。

    d、栈。

    e、堆。

     

    程序分为下面的段:

    text,data(initialized),bss,stack,heap

    text和data需要存入可执行文件,bss的数据在程序载入时由内核清0,因此不需保存。所以有初值的全局变量和static变量在data区,未赋初值的在bss段,函数的局部变量和参数在stack中,动态分配的在heap中。

    未初始化数据段的内容并不存放在磁盘上的可执行文件中。其原因是,内核在程序开始运行前将它们都设置为0。需要存放在程序文件中的段只有正文段和初始化数据段。

     

    6、setjmp和longjmp函数

    两个函数属于非局部goto,非局部指的是,这不是由普通C语言goto语句在一个函数内实施的跳转,而是在栈上跳过若干调用帧,返回到当前函数调用路径上的某一个函数中。

    	#include <setjmp.h>
    	int setjmp(jmp_buf env);
    	void longjmp(jmp_buf env,int val);

    setjmp返回值:若直接调用则返回0,若从longjmp调用返回则返回longjmp的第二个参数值。

    longjmp函数使用第二个参数的原因是对于一个setjmp可以有多个longjmp,setjmp可以通过测试longjmp的第二个参数值可判断造成返回的longjmp是在哪个函数中。

    例子:

    	static jmp_buf buf;
    
    	void second()
    	{
    		printf("second.
    ");
    		longjmp(buf,1);
    	}
    
    	void first()
    	{
    		second();
    		printf("first.
    ");
    	}
    
    	int main()
    	{
    		if(!setjmp(buf)) {
    			first();
    		} else {
    			printf("main.
    ");
    		}
    
    		return 0;
    	}

    结果:

    	second.
    	main.

    注意到虽然first()子程序被调用,"first"不可能被打印。"main"被打印,因为条件语句if(!setjmp(buf))被执行第二次。

    使用setjmp和longjmp要注意以下几点:

    ①setjmp与longjmp结合使用时,它们必须有严格的先后执行顺序,也即先调用setjmp函数,之后再调用longjmp函数,以恢复到先前被保存的“程序执行点”。否则,如果在setjmp调用之前,执行longjmp函数,将导致程序的执行流变的不可预测,很容易导致程序崩溃而退出;

    ②longjmp必须在setjmp调用之后,而且longjmp必须在setjmp的作用域之内。具体来说,在一个函数中使用setjmp来初始化一个全局标号,然后只要该函数未曾返回,那么在其它任何地方都可以通过longjmp调用来跳转到setjmp的下一条语句执行。实际上setjmp函数将发生调用处的局部环境保存在了一个jmp_buf的结构当中,只要主调函数中对应的内存未曾释放 (函数返回时局部内存就失效了),那么在调用longjmp的时候就可以根据已保存的jmp_buf参数恢复到setjmp的地方执行。

     

    如果你有一个自动变量,而不想使其值回滚,则可定义其具有volatile属性,声明为全局或静态变量的值在执行longjmp时保持不变。

     

    自动变量的潜在危险

    如:

    	FILE *open_data(void)
    	{
    		FILE *fp;
    		char databuf[1024];
    		
    		if((fp = fopen(DATAFILE,"r")) == NULL)
    			return NULL;
    		if(setvbuf(fp,databuf,_IOLBF,1024) != 0)
    			return NULL;
    			
    		return fp;
    	}

    问题是:当open_data返回时,它在栈上所使用的空间将由下一个被调用函数的栈帧使用。但是,标准I/O库函数仍将使用其流缓冲区的存储空间。这就产生了冲突和混乱。为了校正这一问题,应在全局存储空间静态地(如static或extern)或者动态地(使用一种alloc函数)为数组databuf分配空间。

  • 相关阅读:
    学生信息管理系统
    设计模式,单例模式
    基础考试题(1)
    基础考试题(0)
    python面向对象进阶
    python面向对象
    Python反射
    14 定时器
    13 JS中的面向对象
    12 DOM操作的相关案例
  • 原文地址:https://www.cnblogs.com/sheshiji/p/3682845.html
Copyright © 2020-2023  润新知