版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
一、进程的概念
首先需要了解下,什么是进程。
Linux 下的进程,可能我们比较陌生,但是我们一直在玩 Windows 系统。应用程序文件、任务管理器,这些东西应该是很溜的。比如:
通过上面两张图片我们可以得知:
程序,是被存储在磁盘上,包含机器指令和数据的文件。
进程,是被装载到内存中,被处理器操作的代码和数据。
一个程序可被同时运行多个进程。
进程在操作系统中执行特定的任务。
更详细一点,参看:百度百科 -- 进程
1、进程的概念主要有两点:
第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据集区域(data region)和堆栈(stack region)。文本区域存储处理执行的代码;数据区域存储变量和基础讷航执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行它),它才能成为一个活动的实体,我们称其为进程。
2、程序和进程的区别:
而程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态的概念。程序可以作为一种软件资料长期存在,而进程是一定生命周期的。程序时永久的,进程是暂时的。进程更能真是地描述并发,而程序不能。进程具有创建其他进程的功能,而程序没有。同一个程序同时运行于若干个数据集合上,它将属于若干个不同的进程,也就是说同一个程序可以对应多个进程。在传统的操作系统中,程序并不能独立运行,作为资源分配和独立运行的基本单元都是进程。
3、进程的特征
动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行。
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的,不可预知的速度向前推进。
结构特征:进程有程序、数据、和进程控制块三部分组成。
多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。
4、进程的分类
UNIX/Linux 系统中的基础能一般被分为以下三类:
(1)交互式进程
由 shell 启动,既可在前台运行,也可在后台运行,通过终端接收用户的输入,并为用户提供输出。
如:vi、ps等
(2)批处理进程
与终端没有联系,以进程列的方式,在无需人工干预的条件下完成一组批量任务。
如:各种 shell 脚本程序
(3)守护进程
有名精灵进程,是系统的后台服务进程,独立于控制中毒那,周期性地执行某种任务或等待某些事件,在系统引导时启动,在系统关闭是终止,生命周期很长。
如:crond、lpd等
二、进程环境
进程简单介绍了下,回到我们今天要讲的主题,进程环境。
首先从 main 函数讲起。参看:C语言再学习 -- 函数
C 程序总是从 main 函数开始执行。main 函数的原型是:
int main (int argc, char * argv[]);
其中,argc 是命令行参数的数目,argv 是指向参数的各个指针所构成的数组。
举个例子:
-
/* 将所有命令行参数回显到标准输出 */
-
#include <stdio.h>
-
-
int main (int argc, char *argv[])
-
{
-
int i;
-
for (i =
0; i < argc; i++)
-
printf (
"argv[%d]: %s\n", i, argv[i]);
-
return
0;
-
}
-
输出结果:
-
# ./a.out hello world
-
argv[
0]: ./a.out
-
argv[
1]: hello
-
argv[
2]: world
三、进程终止
有 8 种方式使进程终止,其中 5 种为正常终止,它们是:
(1)从 main 返回
(2)调用 exit
(3)调用 _exit 或 _Exit
(4)最后一个线程从其启动例程返回
(5)从最后一个线程调用 pthread_exit
异常终止有 3 种方式,它们是:
(6)调用 abort
(7)接到一个信号
(8)最后一个线程对取消请求做出响应
退出函数、函数atexit 讲解,
参看:
C语言再学习 -- 关键字return和exit ()函数
四、函数 setjmp 和 longjmp (了解)
在 C 中,goto 语句是不能跨越函数的,而执行这种类型跳转功能的是函数 setjmp 和 longjmp。
这两个函数对于处理发生在很深层嵌套函数调用中的出错情况是非常有用的。
-
#include <setjmp.h>
-
int setjmp(jmp_buf env);
-
返回:若直接调用则为
0,若从 longjmp 返回则为非
0
-
void longjmp(jmp_buf env, int val);
1、参数解析
setjmp 参数 env 的类型是一个特殊类型 jmp_buf。这一数据类型是某种形式的数组,其中存放在调用 longjmp 时能用来恢复栈状态的所有信息。因为需要在另一个函数中引用 env 变量,所以通常将 env 变量定义为全局变量。
longjmp 函数的第一个参数就是调用 setjmp 时所用的 env;第二个参数是具有非 0 值的 val,它将成为从 setjmp 处返回的值。
使用第二个参数的原因是对于一个 setjmp 可以有多个 longjmp。通过返回值可以判断造成返回的 longjmp 是哪个位置造成的。
2、函数解析
参看:全面了解setjmp与longjmp参看:C 语言中 setjmp 和 longjmp
(1)setjmp 是 C 标准库中提供的一个函数,它的作用是保存程序当前运行的一些状态。
setjmp 函数用于保存程序的运行时的堆栈环境,接下来的其它地方,你可以通过调用 longjmp 函数来恢复先前被保存的程序堆栈环境。当 setjmp 和 longjmp 组合一起使用时,它们能提供一种在程序中实现“非本地局部跳转”("non-local goto")的机制。并且这种机制常常被用于来实现,把程序的控制流传递到错误处理模块之中;或者程序中不采用正常的返回(return)语句,或函数的 正常调用等方法,而使程序能被恢复到先前的一个调用例程(也即函数)中。
对 setjmp 函数的调用时,会保存程序当前的堆栈环境到 env 参数中;接下来调用 longjmp 时,会根据这个曾经保存的变量来恢复先前的环境,并且当前的程序控制流,会因此而返回到先前调用 setjmp 时的程序执行点。此时,在接下来的控制流的例程中,所能访问的所有的变量(除寄存器类型的 变量以外),包含了 longjmp 函数调用时,所拥有的变量。setjmp 和 longjmp 并不能很好地支持 C++ 中面向对象的语义。因此在 C++ 程序中,请使用 C++ 提的异常处理机制。
(2)longjmp 也是 C 标准库中提供的一个函数,它的作用是用于恢复程序执行的堆栈环境。
longjmp 函数用于恢复先前程序中调用的 setjmp 函数时所保存的堆栈环境。setjmp 和 longjmp 组合一起使用时,它们能提供一 种在程序中实现“非本地局部跳转”("non-local goto")的机制。并且这种机制常常被用于来实现,把程序的控制流传递到错误处理模块,或者不采用正常的返回(return)语句,或函数的正常调用等 方法,使程序能被恢复到先前的一个调用例程(也即函数)中。
对 setjmp 函数的调用时,会保存程序当前的堆栈环境到 env 参数中;接下来调用 longjmp 时,会根据这个曾经保存的变量来恢复先前的环 境,并且因此当前的程序控制流,会返回到先前调用 setjmp 时的执行点。此时,value 参数值会被 setjmp 函数所返回,程序继续得以执行。并且, 在接下来的控制流的例程中,它所能够访问到的所有的变量(除寄存器类型的变量以外),包含了 longjmp 函数调用时,所拥有的变量;而寄存器类型的变量 将不可预料。setjmp 函数返回的值必须是非零值,如果 longjmp 传送的 value 参数值为 0,那么实际上被 setjmp 返回的值是1。在调用 setjmp 的函数返回之前,调用longjmp,否则结果不可预料。
对 setjmp 函数的调用时,会保存程序当前的堆栈环境到 env 参数中;接下来调用 longjmp 时,会根据这个曾经保存的变量来恢复先前的环 境,并且因此当前的程序控制流,会返回到先前调用 setjmp 时的执行点。此时,value 参数值会被 setjmp 函数所返回,程序继续得以执行。并且, 在接下来的控制流的例程中,它所能够访问到的所有的变量(除寄存器类型的变量以外),包含了 longjmp 函数调用时,所拥有的变量;而寄存器类型的变量 将不可预料。setjmp 函数返回的值必须是非零值,如果 longjmp 传送的 value 参数值为 0,那么实际上被 setjmp 返回的值是1。在调用 setjmp 的函数返回之前,调用longjmp,否则结果不可预料。
在使用 longjmp 时,请遵守以下规则或限制:
不要假象寄存器类型的变量将总会保持不变。在调用 longjmp 之后,通过 setjmp 所返回的控制流中,例程中寄存器类型的变量将不会被恢复。
不要使用 longjmp 函数,来实现把控制流,从一个中断处理例程中传出,除非被捕获的异常是一个浮点数异常。在后一种情况下,如果程序通过调用 _fpreset 函数,来首先初始化浮点数包后,它是可以通过 longjmp 来实现从中断处理例程中返回。
在C++程序中,小心对 setjmp 和 longjmp 的使用,应为 setjmp 和 longjmp 并不能很好地支持 C++ 中面向对象的语义。因此在 C++ 程序中,使用 C++ 提供的异常处理机制将会更加安全。
不要假象寄存器类型的变量将总会保持不变。在调用 longjmp 之后,通过 setjmp 所返回的控制流中,例程中寄存器类型的变量将不会被恢复。
不要使用 longjmp 函数,来实现把控制流,从一个中断处理例程中传出,除非被捕获的异常是一个浮点数异常。在后一种情况下,如果程序通过调用 _fpreset 函数,来首先初始化浮点数包后,它是可以通过 longjmp 来实现从中断处理例程中返回。
在C++程序中,小心对 setjmp 和 longjmp 的使用,应为 setjmp 和 longjmp 并不能很好地支持 C++ 中面向对象的语义。因此在 C++ 程序中,使用 C++ 提供的异常处理机制将会更加安全。
3、示例说明
很遗憾,上面说了讲了一大桶,我一句没看明白。还是用示例说明吧。
-
//示例一
-
#include <stdio.h>
-
#include <setjmp.h>
-
-
jmp_buf buf;
-
void foo (void)
-
{
-
printf (
"在 foo 函数中\n");
-
longjmp (buf,
1);
//跳转到 setjmp 位置,后面不再打印
-
printf (
"跳过此处,不会打印\n");
-
}
-
-
int main (void)
-
{
-
if (setjmp (buf))
//第一次直接调用 setjmp 返回值为 0
-
printf (
"回到 main 函数\n");
-
else
-
printf (
"第一次通过\n");
-
-
sleep (
1);
-
foo ();
-
-
return
0;
-
}
-
输出结果:
-
第一次通过
//第一次直接调用 setjmp 返回值为 0
-
在 foo 函数中
-
回到 main 函数
//从longjmp返回 则 setjmp 返回值为非 0
-
在 foo 函数中
-
回到 main 函数
-
在 foo 函数中
-
回到 main 函数
-
在 foo 函数中
-
回到 main 函数
-
.....
-
//示例二
-
#include <stdio.h>
-
#include <setjmp.h>
-
-
void foo (void)
-
{
-
printf (
"1111111111\n");
-
ok:
-
printf (
"2222222222\n");
-
}
-
int main (void)
-
{
-
goto ok;
-
printf(
"main函数开始\n");
-
// ok:
-
printf(
"main函数结束\n");
-
-
foo ();
-
return
0;
-
}
-
输出结果:
-
test.c:
12:
2: 错误: 标号‘ok’使用前未定义
4、示例总结
示例二:主要证明了 goto 语句是不能跨函数的。
示例一:首先说明 setjmp 和 longjmp 这对组合是可以实现跨函数跳转的。
上面有提到 “非本地局部跳转”,非局部指的是这不是由普通的 C 语言 goto 语句在一个函数内实施的跳转,而是在栈上跳过若干调用帧,返回到当前函数调用路径上的某个函数中。
示例运行过程:
一开始,我们直接调用 setjmp 返回值为 0,当执行到函数 foo 时,调用了 longjmp 函数,从 longjmp 处返回,且返回值为非 0,形成循环。
5、深入讲解
我们已经了解在 longjmp 后栈帧的基本机构。
那么问题来了,如果在 main 函数中,自动变量、寄存器变量、静态变量、易失变量状态如何呢?
当 longjmp 返回到 main 函数时,这些变量的值是否能恢复到以前调用 setjmp 是的值(即回滚到原先值)呢
遗憾的是,对此问题的回答是“看老子心情”。大多实现并不会滚这些自动变量和寄存器变量的值,而所有标准则成它们的值是不确定的。
变量这部分,如果忘了的话,参看:C语言再学习 -- 存储类、链接
(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, 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);
-
}
-
-
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);
-
}
-
输出结果:
-
# gcc test.c
-
in f1():
-
globval =
95, autoval =
96, regival =
97, volaval =
98, statval =
99
-
after longjmp:
-
globval =
95, autoval =
96, regival =
97, volaval =
98, statval =
99
-
-
//编译器优化后:
-
# gcc -O test.c
-
in f1():
-
globval =
95, autoval =
96, regival =
97, volaval =
98, statval =
99
-
after longjmp:
-
globval =
95, autoval =
2, regival =
3, volaval =
98, statval =
99
(2)示例总结
全局变量、静态变量、和易失变量不受优化影响,在 longjmp 之后,它们的值是最近所呈现的值。
不进行优化时,所有这 5 个变量都存放在存储器中(即忽略了对 regival 变量的register 存储类说明),而进行优化后,autoval(局部变量) 和 regival (寄存器变量)都存放在寄存器中(即使autoval 并未说明是 register),volatile 变量则仍存放在存储器中。
通过这一示例我们可以理解到,如果要编写一个使用非局部跳转的可移植程序,则必须使用 volatile 属性。
(但是从一个系统移植到另一个系统,其他任何事情都可能改变了。)
五、函数 getrlimit 和 setrlimit
每个进程都有一组资源限制,其中一些可以用 getrlimit 和 setrlimit 函数查询和更改。
-
#include <sys/time.h>
-
#include <sys/resource.h>
-
int getrlimit(int resource, struct rlimit * rlptr);
-
int setrlimit(int resource, const struct rlimit * rlptr);
-
两个函数返回:若成功则为
0,若出错则为非
0
1、函数解析
对两个函数的每一次调用都指定一个资源以及一个指向下列结构的指针。
-
The getrlimit() and setrlimit() system calls get and set resource limits respectively. Each resource has an associated soft and
-
hard limit, as defined by the rlimit structure:
-
-
struct rlimit {
-
rlim_t rlim_cur;
/* Soft limit */
-
rlim_t rlim_max;
/* Hard limit (ceiling for rlim_cur) */
-
};
在更改资源限制时,须遵循下列三条规则:(1) 任何一个进程都可将一个软限制更改为小于或等于其硬限制。
(2) 任何一个进程都可降低其硬限制值,但它必须大于或等于其软限制值。这种降低,对
普通用户而言是不可逆反的。
(3) 只有超级用户可以提高硬限制。
一个无限量的限制由常数RLIM_INFINITY指定。
这两个函数的resource参数取下列值之一。注意并非所有资源限制都受到 SVR4和4.3+BSD的支持。
-
RLIMIT_AS
//进程的最大虚内存空间,字节为单位。
-
RLIMIT_CORE
//内核转存文件的最大长度。
-
RLIMIT_CPU
//最大允许的CPU使用时间,秒为单位。当进程达到软限制,内核将给其发送SIGXCPU信号,这一信号的默认行为是终止进程的执行。然而,可以捕捉信号,处理句柄可将控制返回给主程序。如果进程继续耗费CPU时间,核心会以每秒一次的频率给其发送SIGXCPU信号,直到达到硬限制,那时将给进程发送 SIGKILL信号终止其执行。
-
RLIMIT_DATA
//进程数据段的最大值。
-
RLIMIT_FSIZE
//进程可建立的文件的最大长度。如果进程试图超出这一限制时,核心会给其发送SIGXFSZ信号,默认情况下将终止进程的执行。
-
RLIMIT_LOCKS
//进程可建立的锁和租赁的最大值。
-
RLIMIT_MEMLOCK
//进程可锁定在内存中的最大数据量,字节为单位。
-
RLIMIT_MSGQUEUE
//进程可为POSIX消息队列分配的最大字节数。
-
RLIMIT_NICE
//进程可通过setpriority() 或 nice()调用设置的最大完美值。
-
RLIMIT_NOFILE
//指定比进程可打开的最大文件描述词大一的值,超出此值,将会产生EMFILE错误。
-
RLIMIT_NPROC
//用户可拥有的最大进程数。
-
RLIMIT_RTPRIO
//进程可通过sched_setscheduler 和 sched_setparam设置的最大实时优先级。
-
RLIMIT_SIGPENDING
//用户可拥有的最大挂起信号数。
-
RLIMIT_STACK
//最大的进程堆栈,以字节为单位。
2、示例说明
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <sys/resource.h>
-
-
int main (void)
-
{
-
struct rlimit r;
-
if (getrlimit (RLIMIT_NOFILE, &r))
-
perror (
"fail to getrlimit"),
exit (
1);
-
-
printf (
"RLIMIT_NOFILE cur:%ld\n", r.rlim_cur);
-
printf (
"RLIMIT_NOFILE max:%ld\n", r.rlim_max);
-
-
r.rlim_cur =
100;
-
r.rlim_max =
200;
-
-
if (setrlimit (RLIMIT_NOFILE, &r))
-
perror (
"fail to setrlimit"),
exit (
1);
-
-
printf (
"RLIMIT_NOFILE cur:%ld\n", r.rlim_cur);
-
printf (
"RLIMIT_NOFILE max:%ld\n", r.rlim_max);
-
-
return
0;
-
}
-
输出结果:
-
RLIMIT_NOFILE cur:
1024
-
RLIMIT_NOFILE max:
4096
-
RLIMIT_NOFILE cur:
100
-
RLIMIT_NOFILE max:
200
3、Linux 下的 ulimit 命令
资源限制影响到调用进程并由其子进程继承。这就意味着,为了影响一个用户的所有后续进程,需将资源限制的设置构造在 shell 之中。
bash shell 具有内置 ulimit 命令。
(1)选项
-
-a:显示目前资源限制的设定;
-
-c :设定core文件的最大值,单位为区块;
-
-d <数据节区大小>:程序数据节区的最大值,单位为KB;
-
-f <文件大小>:shell所能建立的最大文件,单位为区块;
-
-H:设定资源的硬性限制,也就是管理员所设下的限制;
-
-m <内存大小>:指定可使用内存的上限,单位为KB;
-
-n <文件数目>:指定同一时间最多可开启的文件数;
-
-p <缓冲区大小>:指定管道缓冲区的大小,单位
512字节;
-
-s <堆叠大小>:指定堆叠的上限,单位为KB;
-
-S:设定资源的弹性限制;
-
-t :指定CPU使用时间的上限,单位为秒;
-
-u <程序数目>:用户最多可开启的程序数目;
-
-v <虚拟内存大小>:指定可使用的虚拟内存上限,单位为KB。
(2)示例
-
# ulimit -a
-
core file size (blocks, -c) 0
-
data seg size (kbytes, -d) unlimited
-
scheduling priority (-e) 0
-
file size (blocks, -f) unlimited
-
pending signals (-i) 7892
-
max locked memory (kbytes, -l) 64
-
max memory size (kbytes, -m) unlimited
-
open files (-n) 1024
-
pipe size (512 bytes, -p) 8
-
POSIX message queues (bytes, -q) 819200
-
real-time priority (-r) 0
-
stack size (kbytes, -s) 8192
-
cpu time (seconds, -t) unlimited
-
max user processes (-u) 7892
-
virtual memory (kbytes, -v) unlimited
-
file locks (-x) unlimited
-
六、未讲部分
环境表 (之前已讲)
C 程序的存储空间布局 (下面单独讲)
共享库 (之前已讲)
存储空间分配 (下面单独讲)
替代的存储空间分配程序 (下面单独讲)
环境变量 (之前已讲)
自动变量的潜在问题 (没讲)