定义
应用程序关于某数据集合上的一次运行活动
操作系统进行资源分配和调度的基本单位
进程是程序的一次执行过程
进程是动态的,程序是静态的
同一程序同时运行于若干个数据集合上,该程序将对应与若干个不同的进程
每个进程拥有独立的地址空间
地址空间包括代码段、数据段和堆栈段
代码段,存储程序的代码
数据段,存储程序的全局变量和动态分配的内存
堆栈段,存储函数运行时的局部变量
进程之间的地址空间是隔离的
一个进程崩溃不会影响另一个进程
一个进程崩溃不会影响到操作系统
进程控制块
操作系统使用一个结构体记录进程的各类属性,该结构体被称为进程控制块
进程标识
进程id,每个进程的id都是唯一的
父进程id
地址空间
代码段的起始地址和长度
数据段的起始地址和长度
堆栈段的起始地址和长度
打开文件列表
打开文件时,在打开文件列表中记录被打开文件的信息
关闭文件时,在打开文件列表中删除被关闭文件的信息
进程终止时,操作系统遍历打开文件列表,将尚未关闭的文件关闭.
并发特性
父进程和子进程并发运行
父进程创建子进程后,父子进程都处于运行状态中
两个进程的输出结果是交织在一起的
两者的代码段内容相同
父进程从fork()返回处执行,fork()返回为子进程的PID
子进程从fork()返回处执行,fork()返回0
举例说明:
#include
#include
#include
#include
void child()
{
int i = 0;
for(i = 0;i < 3;i++)
{
printf("child\n");
sleep(1);
}
}
void father()
{
int i;
for(i = 0 ;i < 3;i++)
{
printf("father\n");
sleep(1);
}
}
int main( int argc , char *argv[] )
{
pid_t pid;
pid = fork();
if(pid == 0)
child();
else
father();
return 0;
}
运行结果:
[wanghe@localhost ~]$ ./test.exe
father
child
father
child
father
child
由此可以看出进程之间是并发执行,下面开始详细讲解进程的相关函数。
1.进程ID
每个进程都有一个ID作为标识
getpid原型
原型
#include
#include
pid_t getpid(void);
pid_t getppid(void);
功能描述:
getpid获取当前进程ID
getppid获取父进程ID
pid_t是C语言中用户自定义类型
在sys/types.h中定义
typedef int pid_t;
2.fork()函数详解
linux下采用fork函数创建进程,fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
(1)在父进程中,fork返回新创建子进程的进程ID;
(2)在子进程中,fork返回0;
(3)如果出现错误,fork返回一个负值;
举例说明:
#include
#include
#include
#include
int main( int argc , char *argv[] )
{
pid_t pid;
pid = fork();
if(pid == 0) // 子进程
{
printf("child ID is : %d\n",getpid());
printf("child's FatherID is : %d\n",getppid());
}
else //父进程
{
sleep(1);
printf("child ID is %d\n",pid);
printf("Father ID is : %d\n",getpid());
printf("Father's FatherID is : %d\n",getppid());
}
return 0;
}
运行结果:
child ID is : 15577
child's FatherID is : 15576
child ID is 15577
Father ID is : 15576
Father's FatherID is : 8163
如果父进程不加sleep(1)函数,子进程的FatherID会因为父进程运行时间过短由进程ID为1的进程接管。
child ID is 15637
Father ID is : 15636
Father's FatherID is : 8163
child ID is : 15637
child's FatherID is : 1
fork出错可能有两种原因:
当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
系统内存不足,这时errno的值被设置为ENOMEM。
fork进阶
举例:
#include
#include
#include
#include
int main( int argc , char *argv[] )
{
int i = 0;
puts("Begin*********************");
for(i = 0;i < 2;i++)
{
pid_t id = fork();
if(id == 0)
printf("childprocess: %d-----currentID %d-----fatherID %d------- childID %d \n",i,getpid(),getppid(),id);
else
{
wait(NULL);
printf("fatherprocess: %d-----currentID %d-----fatherID %d------- childID %d \n",i,getpid(),getppid(),id);
}
}
return 0;
}
为了方便观察,采用wait函数使子进程执行完再执行父进程,结果如下:
Begin*********************
childprocess: 0-----currentID 7464-----fatherID 7463------- childID 0
childprocess: 1-----currentID 7465-----fatherID 7464------- childID 0
fatherprocess: 1-----currentID 7464-----fatherID 7463------- childID 7465
fatherprocess: 0-----currentID 7463-----fatherID 6664------- childID 7464
childprocess: 1-----currentID 7466-----fatherID 7463------- childID 0
fatherprocess: 1-----currentID 7463-----fatherID 6664------- childID 7466
总共有六个输出,但是根据进程ID显示结果有四个进程,原因是最初的进程ID是7463,他fork了两次,第一次fork的子进程后面也fork了一次,所以共有3个子进程。
假如再复杂一点,循环是三层,结果如下:
Begin*********************
childprocess: 0-----currentID 7603-----fatherID 7602------- childID 0
childprocess: 1-----currentID 7604-----fatherID 7603------- childID 0
childprocess: 2-----currentID 7605-----fatherID 7604------- childID 0
fatherprocess: 2-----currentID 7604-----fatherID 7603------- childID 7605
fatherprocess: 1-----currentID 7603-----fatherID 7602------- childID 7604
childprocess: 2-----currentID 7606-----fatherID 7603------- childID 0
fatherprocess: 2-----currentID 7603-----fatherID 7602------- childID 7606
fatherprocess: 0-----currentID 7602-----fatherID 6664------- childID 7603
childprocess: 1-----currentID 7607-----fatherID 7602------- childID 0
childprocess: 2-----currentID 7608-----fatherID 7607------- childID 0
fatherprocess: 2-----currentID 7607-----fatherID 7602------- childID 7608
fatherprocess: 1-----currentID 7602-----fatherID 6664------- childID 7607
childprocess: 2-----currentID 7609-----fatherID 7602------- childID 0
fatherprocess: 2-----currentID 7602-----fatherID 6664------- childID 7609
结果不太清晰,用图来列举一下:
父进程ID是7602,后面全是子进程,由于wait操作导致子进程结束后才是父进程,所以进程的关系如图所示。
由此我们可以总结出规律,循环n次创建的子进程数为1+2+4+……+2^(N-1)个,执行printf函数的次数为2*(1 + 2 + 4 + …… + 2^(N-1))次。
wait原型
原型
#include
#include
pid_t wait(int *status);
功能
等待子进程结束
参数
status
如果status不为NULL,子进程的退出码保存在status指向的变量中
退出码
进程可能由于不同的原因退出
主动调用exit正常退出
接受信号后退出
查询退出原因的宏
名称 | 功能 |
WIFEXITED(status) | 如果进程通过调用exit正常退出,则返回真 |
WEXITSTATUS(status) | 如果进程通过调用exit正常退出,返回进程的退出码 |
WIFSIGNALED(status) | 如果进程接受信号后退出,则返回真 |
WTERMSIG(status) | 如果进程接受信号后退出,返回导致进程退出的信号 |
举例:
#include
#include
#include
#include
void child()
{
puts("child");
exit(123);
}
int main( int argc , char *argv[] )
{
pid_t pid;
pid = fork();
if(pid == 0)
child();
int status;
wait(&status);
puts("father");
if(WIFEXITED(status))
{
printf("WIFEXITED = true\n");
printf("WEXITSTATUS = %d\n", WEXITSTATUS(status));
}
return 0;
}
结果:
child
father
WIFEXITED = true
WEXITSTATUS = 123
父进程会根据子进程结束后再执行,同时可以判断子进程是否出现问题。
exit原型
原型
#include
void exit(int status);
功能
正常退出当前进程
将status & 0xFF作为退出码返回给父进程
预定义常量
EXIT_SUCCESS,为0的数值,表示程序正常退出
EXIT_FAILURE,为非0的数值,表示程序执行过程发生了错误,异常退出
在linux shell中,可以通过特殊的环境变量$?获得程序的退出码,比如echo $?
atexit原型
原型
#include
int atexit(void (*function)(void));
功能
注册一个回调函数function,进程正常结束时,function会被调用
如果注册多个回调函数,进程结束时,以与注册相反的顺序调用回调函数
举例:
#include
#include
#include
#include
void f1()
{
puts("f1");
}
void f2()
{
puts("f2");
}
void f3()
{
puts("f3");
}
int main( int argc , char *argv[] )
{
atexit(f1);
atexit(f2);
atexit(f3);
puts("main function");
return 0;
}
结果:
main function
f3
f2
f1