Linux下C语言编程之进程学习

进程的定义

定义

应用程序关于某数据集合上的一次运行活动

操作系统进行资源分配和调度的基本单位

进程是程序的一次执行过程

进程是动态的,程序是静态的

同一程序同时运行于若干个数据集合上,该程序将对应与若干个不同的进程

每个进程拥有独立的地址空间

地址空间包括代码段、数据段和堆栈段

        代码段,存储程序的代码

        数据段,存储程序的全局变量和动态分配的内存

        堆栈段,存储函数运行时的局部变量

进程之间的地址空间是隔离的

        一个进程崩溃不会影响另一个进程

        一个进程崩溃不会影响到操作系统

进程的属性

进程控制块

操作系统使用一个结构体记录进程的各类属性,该结构体被称为进程控制块

进程标识

进程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

结果不太清晰,用图来列举一下:

Linux下C语言编程之进程学习_第1张图片

父进程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

 

你可能感兴趣的:(Linux)