APUE第7章 进程环境

1、概述

i、当程序执行时,main函数如何被调用;ii、命令行参数如何传递给新程序;iii、典型C程序的存储空间布局;iv、如何分配另外的存储空间(利用malloc、calloc、realloc及free函数);v、进程如何使用环境变量;vi、进程的7种不同终止方式;vii、longjmp和setjmp函数及它们与栈的交互作用;viii、查看进程资源限制。

2、main函数

C程序是从main函数开始。

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

3、进程终止方式讲解

8种方式使进程终止,其中5中为正常终止。

i、从main返回;ii、调用exit;iii、调用_exit或_Exit;iv、最后一个线程从其启动例程返回;v、从最后一个线程调用pthread_exit;vi、调用abort;vii、接到一个信号;viii、最后一个线程对取消请求做出响应。

a、退出函数

3个函数用于正常终止一个程序:_exit和_Exit立即进入内核,exit则先执行一些清理处理(如,对数据缓冲进行冲洗),然后返回内核。隐式返回的终止状态码是0;

b、函数atexit

一个进程可以登记多到32个函数(也被称为终止处理程序,通过atexit函数来登记这些函数),这些函数将由exit自动调用。

#include<stdlib.h>
int atexit(void(*fun)(void));
		//返回值:若成功,返回0;若出错,返回非0

下图是对执行程序和终止程序调用函数的最清晰说明。

内核使程序执行的唯一方法是调用一个exec函数。进程自愿终止的唯一方法是显式或隐式(通过调用exit)调用_exit或_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");
}

4、命令行参数及环境表及环境变量

在程序中命令参数的调用,即利用argv[1]、argv[2]...但agrv[argc]是一个空指针。

每个程序都会收到一张环境表,其是一个字符指针数组,其中每个指针包含一个以null结束的C字符串的地址。如下图所示:

环境由如name=value字符串组成。大多数预定义名完全由大写字母组成(惯例而已)。

#include<stdlib.h>
char* getenv(const char *name);
		//返回值:指向与name关联的value的指针;若未找到,返回NULL
int putenv(char* str);//将指定环境变量放置到环境表中
		//函数返回值:若成功,返回0;若出错,返回非0
int setenv(const char* name, const char* value, int rewrite);
int unsetenv(const char* name);
		//两个函数返回值:若成功,返回0;若出错,返回-1

i、函数getenv,可以用其取环境变量值,该函数返回一个指针,指向name=value字符串中的value。一般应使用getenv从环境中取一个指定环境变量的值,而不是直接访问environ。

ii、putenv取形式为name=value的字符串,将其放到环境表中。如果name已经存在,则先删除其原先的定义。

iii、setenv将name设置为value。如果在环境中name已经存在,那么(a)若rewrite非0,则首先删除其现有的定义;(b)若rewrite为0,则不删除其现有定义(name不设置为新的value,而且也不出错)。

vi、unsetenv删除name的定义。即使不存在这种定义也不算出错。

5、C程序的存储空间布局(太TM重要啦!)

i、正文段。由cpu执行的机器指令部分。通常正文段是可共享的,即使频繁执行的程序在存储器中也只需有一个副本,另外,正文段常常是只读的,以防止程序由于意外而修改其指令。

ii、初始化数据段。即数据段(data段),其包含了程序中需明确地赋初值的变量。如,int maxcount=99;此变量及初始值存放在data段中

iii、未初始化数据段。即bss段。这一块的内容,在程序开始执行之前,内核将此段中的数据初始化为0或空指针。如,long sum[1000];此变量存放在非初始化数据段中。

iv、栈。自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次函数调用时,其返回地址以及调用者的环境信息(如某些机器寄存器的值)都存放在栈中。然后,最近被调用的函数在栈上为其自动和临时变量分配存储空间。最后,每当有一次函数调用时,就用一个新的栈帧。

v、堆。通常在堆中进行动态存储分配。堆位于未初始化数据段和栈之间。

一个执行程序当然还包括其他段,只是上述所列举的是最基本。此外还有,符号表的段,调度信息的段,动态共享库链接表的段等,但并不会装载到进程执行的程序映像中。

简单以图片呈现存储空间安排的内容。

利用size命令可以报告正文段、数据段、bss段的长度。

6、存储空间分配的3个函数

#include<stdlib.h>
void* malloc(size_t size);
void* calloc(size_t nobj, size_t size);
void* realloc(void* ptr, size_t newsize);
		//3个函数返回值:若成功,返回非空指针;若出错,返回NULL
void free(void* ptr);

使用时注意点:

i、三个函数返回的都是void*,但返回时,一律统一成显式强制类型转换,以避免不必要的错误。

ii、realloc的最后一个参数是存储区的新长度,不是新、旧存储区长度之差。若ptr是一个空指针,则realloc的功能与malloc相同,用于分配一个指定长度为newsize的存储区。

iii、大多数实现所分配的存储空间比所要求的要稍大一些,额外的空间用来记录管理信息——分配块的长度、指向下一个分配块的指针等。

7、setjmp和longjmp函数及其与栈的交互作用

首先,需要明确的是,setjmp和longjmp的优点是可以执行跨函数转跳功能,而goto只能在函数体内进行转跳。

i、考虑以下的代码及其对应的开僻的栈帧。

#include "apue.h"

#define	TOK_ADD	   5

void	do_line(char *);
void	cmd_add(void);
int		get_token(void);

int
main(void)
{
	char	line[MAXLINE];

	while (fgets(line, MAXLINE, stdin) != NULL)//stdin标准输入流函数,以换行符为终止符,最后一个字符自动添加为null字节。
		do_line(line);
	exit(0);
}

char	*tok_ptr;		/* global pointer for get_token() */

void
do_line(char *ptr)		/* process one line of input */
{
	int		cmd;

	tok_ptr = ptr;
	while ((cmd = get_token()) > 0) {//get_token的功能:从该输入行中取下一个标记
		switch (cmd) {	/* one case for each command */
		case TOK_ADD:
				cmd_add();
				break;
		}
	}
}

void
cmd_add(void)
{
	int		token;

	token = get_token();
	/* rest of processing for this command */
}

int
get_token(void)
{
	/* fetch next token from line pointed to by tok_ptr */
}

ii、采用setjmp和longjmp函数进行转跳后的改写代码及其栈帧。

#include<setjmp.h>
int setjmp(jmp_buf env);//jmp_buf是一种特殊类型,而env通常定义为全局变量
		//返回值:若直接调用,返回0;若从longjmp返回,则为非0
void longjmp(jmp_buf env, int val);

在希望返回到的位置调用setjmp,直接调用该函数,其返回值为0。setjmp参数env的类型是一个特殊类型jmp_buf。该数据类型是某种形式的数组,其中存放在调用longjmp时能用来恢复栈状态的所有信息。同时因为需在另一个函数中引用env变量,所以通常将env变量定义为全局变量。

以两个参数调用longjmp函数,第一个就是在调用setjmp时所用的env;第二个参数是非0值的val,它将成为从setjmp处返回的值。使用第二个参数的原因是对于一个setjmp可以有多个longjmp。可以通过测试返回值可判断是哪个造成返回的longjmp的函数。

#include "apue.h"
#include <setjmp.h>

#define	TOK_ADD	   5

jmp_buf	jmpbuffer;//jmpbuffer变量一般设置为全局变量

int
main(void)
{
	char	line[MAXLINE];

	if (setjmp(jmpbuffer) != 0)//通过setjmp,在此处设置一个标志
		printf("error");
	while (fgets(line, MAXLINE, stdin) != NULL)
		do_line(line);
	exit(0);
}

 . . .

void
cmd_add(void)
{
	int		token;

	token = get_token();
	if (token < 0)		/* an error has occurred */
		longjmp(jmpbuffer, 1);//如若出现问题,则可跨函数转跳到setjmp所设置的标志处,并返回第二个参数值1。
	/* rest of processing for this command */
}

iii、自动变量、寄存器变量和易失变量

自动变量:存放在栈中,可放在存储器中,也可放在寄存器中。(有时会被回滚)

寄存器变量:由register修饰。(会被回滚)

易失变量:由volatile关键字进行修饰,此时变量一定会被放在存储器中,而非寄存器中。(自动变量,不想使其值回滚,就声明为volatile属性)

以下代码利用setjmp和longjmp函数对回滚现象导致变量值的更改进行举例说明。

#include "apue.h"
#include <setjmp.h>

static void	f1(int, int, int, int);
static void	f2(void);

static jmp_buf	jmpbuffer;//定义jmpbuffer,方便回跳
static int		globval;//全局变量

int
main(void)
{
	int				autoval;//自动变量
	register int	regival;//寄存器变量
	volatile int	volaval;//volatile修饰的易失变量,使得变量存放在存储器而非寄存器中
	static int		statval;//静态变量

	globval = 1; autoval = 2; regival = 3; volaval = 4; statval = 5;

	if (setjmp(jmpbuffer) != 0) {//放置jmpbuffer,等待longjmp回来
		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);//由于会在f2中进行longjmp转跳,此函数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);
}

8、查看进程资源限制

#include<sys/resource.h>
int getrlimit(int resource, struct rlimit *rlptr);
int setrlimit(int resource, const struct rlimit* rlptr);
		//两个函数返回值:若成功,返回0;若出错,返回非0
struct rlimit {
	rlim_t rlim_cur;	/* soft limit:current limit *///软限制
	rlim_t rlim_max;	/* hard limit:maximum value for rlim_cur *///硬限制
};

两个函数,每次调用指定一个资源以及上述结构的指针。

在更改资源限制时,须遵循下列3条规则:

i、任何一个进程都可将一个软限制值更改为小于或等于其硬限制值;

ii、任何一个进程都可降低其硬限制值,但它必须大于或等于其软限制值。这种降低,对普通用户而言是不可逆的。

iii、只有超级用户进程可以提高硬限制值。

常用查看限制资源命令为ulimit -a

如需更改core文件的最大可利用块,命令为ulimit -c 2048

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值