Linux-进程描述(5)之进程环境
main函数和启动例程
当内核使用一个exec函数执行C程序时,在调用main函数之前先调用一个特殊的启动例程,可执行程序将此例程指定为程序的起始地址。启动例程从内核获取命令行参数和环境变量,然后为调用main函数做好准备。我们常用gcc main.c -o main
命令编译一个程序,其实也可以分三步做,第一步生成汇编代码,第二步生成目标文件,第三步生成可执行文件:
$ gcc -S main.c
$ gcc -c main.s
$ gcc main.o
-S
选项生成汇编代码, -c
选项生成目标文件,此外 -E
选项只做预处理而不编译,如果不加这些选项则 gcc
执行完整的编译步骤,直到最后链接生成可执行文件为止。gcc命令的选项图
这些选项都可以和 -o
搭配使用,给输出的文件重新命名而不使用 gcc
默认的文件名( xxx.c
、 xxx.s
、 xxx.o
和 a.out
),例如 gcc main.o -o main
将 main.o
链接成可执行文件 main
。
gcc
做链接,gcc
其实是调用ld
将目标文件crt1.o
和我们的hello.o
链接在一起。crt1.o
里面已经提供了_start
入口点,我们的汇编程序中再实现一个_start
就是多重定义了,链接器不知道该用哪个,只好报错。另外,crt1.o
提供的_start
需要调用main
函数,而我们的汇编程序中没有实现main
函数,所以报错。gcc
做链接就没错了,整个程序的入口点是crt1.o
中提供的_start
,它首先做一些初始化工作(以下称为启动例程,Startup Routin),然后调用C代码中提供的main
函数。所以,以前我们说main
函数是程序的入口点其实不准确,_start
才是真正的入口点,而main
函数是被_start
调用的。main
函数最标准的原型应该是int main(int argc, char *argv[])
,也就是说启动例程会传两个参数给main
函数,这两个参数的含义我们学了指针以后再解释。我们到目前为止都把main
函数的原型写成int main(void)
,这也是C标准允许的,如果你认真分析了上一节的习题,你就应该知道,多传了参数而不用是没有问题的,少传了参数却用了则会出问题。main
函数是被启动例程调用的,所以从 main
函数 return
时仍返回到启动例程中, main
函数的返回值被启动例程得到,如果将启动例程表示成等价的C代码(实际上启动例程一般是直接用汇编写的),则它调用 main
函数的形式是:
exit(main(argc, argv));
也就是说,启动例程得到 main
函数的返回值后,会立刻用它做参数调用 exit
函数。 exit
也是 libc
中的函数,它首先做一些清理工作,然后调用上一章讲过的 _exit
系统调用终止进程, main
函数的返回值最终被传给 _exit
系统调用,成为进程的退出状态。我们也可以在 main
函数中直接调用 exit
函数终止进程而不返回到启动例程,例如:
#include <stdlib.h> int main(void)
{
exit();
}
这样和 int main(void) { return 4; }
的效果是一样的。在Shell中运行这个程序并查看它的退出状态:
./a.out
echo $?
按照惯例,退出状态为0表示程序执行成功,退出状态非0表示出错。注意,退出状态只有8位,而且被Shell解释成无符号数,如果将上面的代码改为 exit(-1);
或 return -1;
,则运行结果为:
./a.out
echo $?
注意,如果声明一个函数的返回值类型是 int
,函数中每个分支控制流程必须写 return
语句指定返回值,如果缺了 return
则返回值不确定(想想这是为什么),编译器通常是会报警告的,但如果某个分支控制流程调用了 exit
或 _exit
而不写 return
,编译器是允许的,因为它都没有机会返回了,指不指定返回值也就无所谓了。使用 exit
函数需要包含头文件 stdlib.h
,而使用 _exit
函数需要包含头文件 unistd.h
。
进程终止
进程终止的方式有8种,前5种为正常终止,后三种为异常终止:
1 从main函数返回;
2 调用exit函数;
3 调用_exit或_Exit;
4 最后一个线程从启动例程返回;
5 最后一个线程调用pthread_exit;
6 调用abort函数;
7 接到一个信号并终止;
8 最后一个线程对取消请求做出响应。
(1) exit函数
#include <stdlib.h>
void exit( int status );
void _Exit( int status );
#include <unistd.h>
void _exit( int status );
这三个函数用于正常终止一个程序, _exit和_Exit立即进入内核,而exit则要先做一些清理工作(调用执行各终止处理程序,关闭所有标准I/O流),再进入内核。三个函数所带的整型参数称为终止状态或退出状态,如果(a)调用这些函数不带参数,(b) main函数中的return语句无返回值,(c) main函数没有声明返回类型为整型,则进程的终止状态是未定义的。 main函数返回一个整型值与用该值调用exit是等价的。
exit()退出程序过程
2.cleanup();关闭所有打开的流,这将导致写所有被缓冲的输出,删除用TMPFILE函数建立的所有临时文件。
3.最后调用_exit()函数终止进程。
_exit做3件事(man):
1,属于此过程的任何打开文件描述符都已关闭;
2,进程的任何子进程由进程1继承,初始化;
3,这个过程父进程发送SIGCHLD信号。
exit执行完清理工作后就调用_exit来终止进程。
程序示例
#include<stdlib.h>
#include<conio.h>
#include<stdio.h>
int main(int argc,char*argv[])
{
int status;
printf("Enter either 1 or 2\n");
status=getch();
/*Sets DOS error level*/
exit(status-'');
/*Note:this line is never reached*/
return ;
}
exit()和return的区别:
(2) atexit函数
一个进程可以登记若干个个函数,这些函数由exit自动调用,这些函数被称为终止处理函数, atexit函数可以登记这些函数。 exit调用终止处理函数的顺序和atexit登记的顺序相反,如果一个函数被多次登记,也会被多次调用。按照ISO C的规定,一个进程可以登记至少32个函数,这些函数将由exit自动调用。atexit()注册的函数类型应为不接受任何参数的void函数。
#include<stdio.h>
#include<stdlib.h>
void func1(void)
{
printf("in func1\n");
}
void func2(void)
{
printf("in func2\n");
}
void func3(void)
{
printf("in func3\n");
}
int main()
{
atexit(func3);
atexit(func2);
atexit(func1);
20 sleep(5);
printf("In main\n");
exit(0);
}
过程分析:atexit()函数先注册三个func()函数,然后等待5秒,再打印“int main”(如果main()函数输出部分后面没有“\n”,则main()函数要输出的内容会先放到标准输出缓冲区中,当main()中调用exit()函数的时候,会做一些自身清理工作,同时刷新缓冲区的内容),当执行到exit(0)时,exit()会自动调用这些已注册的函数,但是由于压栈的过程中先入后出的原则,所以先注册的函数最后执行。
一个进程可以登记多达32个函数,这些函数将由exit自动调用,通常这32个函数被称为终止处理程序,并调用atexit函数来登记这些函数,atexit的参数是一个函数地址,当调用此函数时无须传递任何参数,该函数也不能返回值,atexit函数称为终止处理程序注册程序,注册完成以后,当函数终止是exit()函数会主动的调用前面注册的各个函数,但是exit函数调用这些函数的顺序于这些函数登记的顺序是相反的,我认为这实质上是参数压栈造成的,参数由于压栈顺序而先入后出。同时如果一个函数被多次登记,那么该函数也将多次的执行。
exit函数运行时首先会执行由atexit()函数登记的函数,然后会做一些自身的清理工作,同时刷新所有输出流、关闭所有打开的流并且关闭通过标准I/O函数tmpfile()创建的临时文件。exit()函数用于在程序运行的过程中随时结束程序,exit的参数state是返回给操作系统,返回0表示程序正常结束,非0表示程序非正常结束。
环境表
每个程序都会收到一张环境表, 环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串,环境指针environ是一个全局变量,指向指针数组的地址。通常用getenv和putenv函数来访问特定的环境变量,而不是environ全局变量。如果要查看整个环境,则必须用environ全局变量。
C程序的存储空间布局
正文段:CUP执行的机器指令部分,是共享和只读的。
初始化数据段:又称作数据段,包含了程序中明确需要赋初值的变量。
非初始化数据段:在程序开始执行前,内核将此段中的数据初始化为0或空指针。
栈:自动变量以及每次函数调用时所需保存的数据都存放在此段中。
堆:用于动态存储分配。堆位于栈和非初始化数据段之间。
存储器分配
#include <stdlib.h>
void *malloc( size_t size );
void *calloc( size_t nobj, size_t size );
void *realloc( void *ptr, size_t newsize );
void free( void *ptr );
m alloc函数分配指定字节数的存储区,该存储区中的初始值不确定; calloc函数为指定数量且指定长度的对象分配存储空间,该空间中的每一位都初始化为0; realloc函数更改存储区的长度(增加或减少),新增区域内的初始值不确定,如果ptr为空, realloc和malloc的功能相同。以上函数的大多数实现所分配的存储空间都比所要求的要大一些,额外的空间用来存储管理信息。如果在一个超过已分配区的尾端进行写操作,就会重写下一个分配区的管理记录;同样,在一个已分配区的起始位置之前写入,会重写本分配区的管理记录。这种错误是灾难性的,但因为不会很快暴露出来,所以很难发现。
环境变量:环境字符串的形式如: name=value,它们的解释完全取决于各个应用程序,而与内核无关。
#include <stdlib.h>
char *getenv( const char *name );
int putenv( char *str );
int setenv( const char *name, const char *value, int rewrite );
int unsetenv( const char *name );
getenv函数返回指向name=value中的value的指针; putenv函数把字符串name=value放入环境表中,如果name已经存在,则先删除原来的定义。
setenv函数将name设置为value,如果name存在且rewrite非0,则删除其现有定义,若rewrite为0,则不删除其现有定义; unsetenv函数删除name的定义,即使不存在也不会出错。
setjmp和longjmp
#include <setjmp.h>
int setjmp( jmp_buf env );
void longjmp( jmp_buf env, int val );
setjmp和longjmp函数用于处理发生在深层次函数调用中的出错情况longjmp函数可以在栈上跳过若干个调用帧,返回到当前函数调用路径上的某个函数中。在希望返回到的位置调用setjmp,数据类型jmp_buf是某种形式的数组,存放在调用longjmp时能用来恢复栈状态的所有信息。因为需要在另一个函数中引用env变量,所以将env定义为全局变量。当检查到一个错误时,调用longjmp函数,第一个参数env就是在调用setjmp时所用的env,第二个参数val非0,它将成为从setjmp处返回的值。使用第二个参数的原因是一个setjmp可以对应多个longjmp,这样就可以根据返回值来判断造成返回的longjmp函数在那个函数中,从而确定出错的位置。
getrlimit和setrlimit函数
#include <sys/resource.h>
int getrlimit( int resource, struct rlimit *rlptr );
int setrlimit( int resource, const struct rlimit *rlptr );
getrlimit和setrlimit函数用于获取或设置进程的资源限制。资源限制通常是由进程0建立的,由每个后续进程继承。更改资源限制时,注意以下三条规则:
1 进程的软限制值只能用于或等于硬限制值;
2 任意进程都可以降低其硬限制值,但它必须用于或等于其软限制值,这种操作对普通用户是不可逆的;
3 只有超级用户进程可以提高硬限制值。
资源限制影响到调用进程并由其子进程继承,这意味着为了影响一个用户的所有进程,需要将资源限制构造在shell中。
Linux-进程描述(5)之进程环境的更多相关文章
- Linux分析第六周——进程的描述和进程的创建
Linux分析第六周--进程的描述和进程的创建 李雪琦+原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/US ...
- Linux下进程描述(1)—进程控制块
进程概念介绍 进程是操作系统对运行程序的一种抽象. • 一个正在执行的程序: • 一个正在计算机上执行的程序实例: • 能分配给处理器并由处理器执行的实体: • 一个具有普以下特征的活动单元:一组指令 ...
- Linux进程-命令行参数和环境列表
命令行参数 在C中,main函数有很多的变种,比如 main(), int main(), int main(int argc, char *argv[]), int main(int argc, c ...
- Linux进程描述符task_struct结构体详解--Linux进程的管理与调度(一)【转】
Linux内核通过一个被称为进程描述符的task_struct结构体来管理进程,这个结构体包含了一个进程所需的所有信息.它定义在include/linux/sched.h文件中. 谈到task_str ...
- Linux内核分析 笔记六 进程的描述和进程的创建 ——by王玥
一.知识点总结 (一)进程的描述 1.操作系统内核里有三大功能: 进程管理 内存管理 文件系统 2.进程描述符:task_struct 2.进程描述符——struct task_struct 1. p ...
- Linux下进程描述(1)—进程控制块【转】
转自:http://www.cnblogs.com/33debug/p/6705391.html 进程概念介绍 进程是操作系统对运行程序的一种抽象. • 一个正在执行的程序: • 一个正在计算机上执行 ...
- linux进程学习-进程描述符的存储
当进程被新建时,内核会给进程分配一个8K的空间作为进程的内核堆栈.同时我们知道task_struct结构体也会被创建,但有意思的是,内核不会给task_struct单独分别空间,而是直接将其扔到8k的 ...
- linux进程学习-进程描述符,控制块
从数据结构的角度,进程用task_struct结构来描述,称为“进程描述符 (Process Descriptor)”或者“进程控制块(Process Control Block, PCB)”,其包含 ...
- Linux内核创建一个新进程
张雨梅 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-10000 创建新进程 如果同一个程序被多 ...
- 第六周——分析Linux内核创建一个新进程的过程
"万子恵 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 &q ...
随机推荐
- 有关ospf抓包
有关ospf抓包 1.相关的路由器为这样子的: 路由器都运行了ospf: 抓包的链路为GE0/0/2 , 2.抓包图: 从图上我们可以看到,protocol info 这一栏里面出现了: hello ...
- C风格字符串和C++ string 对象赋值操作的性能比较
<<C++ Primer>> 第四版 Exercise Section 4.3.1 部分Exercise 4.2.9 习题如下: 在自己本机执行如下程序,记录程序执行时间: # ...
- 配置adb环境变量
作为Android开发人员,会经常使用adb命令来进行push.pull.install.看数据库等.记录一下adb配置流程,以备后用. 在没有配置adb之前,我们打开Windows命令处理程序(cm ...
- 【收集】sql查询统计,周,月,年
昨天 select * from tb where datediff(day, 时间字段 ,getdate()) = 1 今天 select * from tb where datediff(day, ...
- PHP随机生成随机个数的字母组合示例
在很多系统环境下大家都会用到字母组合各种编码,下面推荐大家非常实用的PHP代码. $num由几个字母组合. $s字母包含大小写,可以自己调配大写还小写. <?php function makec ...
- 给 Virtualbox 中 Ubuntu 系统设置静态 IP ,让 DNS 配置信息不会在重启后被清除
虚拟机网络选择 桥接网卡 模式. 主要涉及两个步骤: 1. 修改 /etc/network/interfaces 文件: 2. 修改 dns : 第一步,修改 interfaces 文件: sudo ...
- 求两个字符串的最长公共子串(LCS)
http://tianyunpu2008.blog.163.com/blog/static/6559379920089162236915/
- FastDFS分布文件系统相关资料索引
FastDFS是为互联网应用量身定做的一套分布式文件存储系统,非常适合用来存储用户图片.视频.文档等文件.对于互联网应用,和其他分布式文件系统相比,优势非常明显.具体情况大家可以看相关的介绍文档,包括 ...
- CSS.01 -- 选择器及相关的属性文本、文字、字体、颜色、
与html相比,Css支持更丰富的文档外观,Css可以为任何元素的文本和背景设置颜色:允许在任何元素外围设置边框:允许改变文本的大小,装饰(如下划线),间隔,甚至可以确定是否显示文本. 什么是CSS? ...
- JS和Flash(AS)相互调用
<!DOCTYPE html> <html> <head> <title>swf</title> <meta charset=&quo ...