多进程实现

一、进程三种状态

1.就绪状态(Ready)

当进程已分配到除CPU以外的所有必须的资源,只要获得处理机便可以立即执行,这时的进程状态就被称为就绪状态。

2. 执行状态(Running)

当进程已获得CPU,其程序正在CPU上执行,此时的进程状态被称为执行态。

3. 阻塞状态(Blocked)

又称为等待、挂起状态。
正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件有很多种,例如,等待I/O完成、申请缓存区不能满足、等待条件(信号)等。

4. 状态转换

在这里插入图片描述

处于阻塞态的进程在事件发生后不会直接进入到运行态,而是先进入到就绪态。

二、进程相关命令

1. ps查看进程

一般使用 ps -aux(进程状态) 或 ps -ef(父子进程关系)
在这里插入图片描述
在这里插入图片描述
使用ps命令时查看的进程状态标记符
R:正在执行
S:阻塞状态
T:暂停状态
Z:僵尸进程(不存在但暂时无法消除)
D:不可中断的进程
<:高于优先级的进程
N:低于优先级的进程
L:有内存分页分配并锁在内存中
s 进程的领导者(在它之下有子进程);
l 多进程的(使用 CLONE_THREAD, 类似 NPTL pthreads);
+ 位于后台的进程组

2. kill消灭进程

一般使用 kill -9 pid 消灭指定进程 (类似于Ctrl+c)
(给进程ID为pid的进程发送一个编号为9的信号)
kill命令本质是给一个指定的进程发送一个信号。
信号分为很多种,大多数情况下,进程接收到信号后会结束运行。进程中可以更改对信号的处理方式,但是无法更改对9、19信号的处理方式,防止特殊情况发生无法终止进程。

三、相关函数

1.相关头文件

#include <sys/types.h>
#include <unistd.h> 
#include <stdlib.h>
#include <sys/wait.h>

2.获取进程ID

grtpid()
原型:pid_t getpid(void);
返回值:成功,当前进程的ID
getppid()
原型:pid_t getppid(void);
返回值:成功,当前进程的父进程ID

#include <sys/types.h>
#include <unistd.h> 
#include <stdlib.h>
#include <sys/wait.h>
#include <stdio.h>

int main(void)
{
	pid_t pid,ppid;
	pid = getpid();
	ppid = getppid();
	printf("pid = %d\r\nppid = %d\r\n",pid,ppid);
}

3.运行进程

system()
在一个进程中运行另一个进程,运行完后回到原进程
原型:int system(const char * string); 参数:const char * string
待执行的命令(以字符串的形式),比如 “ls -l” 功能:system()会调用 fork()产生子进程,由子进程来调用/bin/sh-c
string 来执行参数 string 字符串所代表的命令,此命令执行完后随即返回 原调用的进程。在调用 system()期间
SIGCHLD 信号会被暂时搁 置,SIGINT 和 SIGQUIT 信号则会被忽略

#include <unistd.h> 
#include <stdlib.h>
#include <sys/wait.h>
#include <stdio.h>

int main(void)
{
	pid_t pid,ppid;
	pid = getpid();
	ppid = getppid();
	printf("MAIN:pid = %d , ppid = %d\r\n",pid,ppid);
	system("pwd");
}

在这里插入图片描述

4.替换进程

exec函数族 execl(),execlp(),execv(),execve(),execvp()
用新程序替换原进程的程序(进程ID不变,仅仅是执行代码发生了变化)

execl()
原型:int execl(const char *
path,const char * arg,…); 参数:const char * path 待执行程序的路径
const char * arg,… 待执行程序的argv[0]~argv[n],且再加上参数NULL(或 (char *)0) 返回值:成功,不返回(即execl()函数执行成功,原位于execl()之后的代码将不会被执行)
失败,返回-1 功能:用参数指定的进程替换当前进程

int main(void)
{
	pid_t pid,ppid;
	pid = getpid();
	ppid = getppid();
	printf("MAIN:pid = %d , ppid = %d\r\n",pid,ppid);
	execl("/bin/pwd","pwd",NULL);
    printf("hello");//未执行
}

在这里插入图片描述
在exec函数族中l和v不能同时出现

  • 若出现了l,则需要将Shell中的各参数分别作为函数参数
  • 若出现了v,则需要将Shell中的各参数整合在一起作为函数参数

Eg:
char *str[]={”ls”,”-al”,”/etc/passwd”,(char * )0};
execv(“/bin/ls”,str);

  • 若出现了p,则参数const char * path 中可以省略PATH变量中包含的路径

Eg:
execl(“/bin/ls”,”ls”,”-al”,”/etc/passwd”,(char * )0);
等价
execlp(“ls”,”ls”,”-al”,”/etc/passwd”,(char *)0);

4.创建进程

原型:pid_t fork(void);
返回值:创建成功,会在父进程中返回子进程的PID,会在子进程中返回0
创建失败,会在父进程中返回-1
功能:创建一个子进程
备注:当fork()函数成功执行时,位于fork()函数之后的将分别被父进程和新创建的子进程各执行一遍

int main(void)
{
	pid_t pid,ppid;
	int i=0;
	fork();
	i++;
	printf("i=%d\r\n",i);
	printf("pid = %d , ppid = %d\r\n",getpid(),getppid());
}

在这里插入图片描述

两次显示变量i的值都是1,说明fork()执行后,父子进程拥有独立的进程空间,只是fork()执行时,子进程先拷贝了父进程的用户空间(因为在子进程执行时可以直接使用变量i而不需要定义)(相当于父子进程中各有一个变量i,彼此在CPU物理内存空间上独立)

5.结束进程

进程结束的方式:
a) 该进程的main函数自然返回
(比如:执行return 0;)
b) 该进程调用exit()
c) 该进程收到信号,导致进程(非正常结束)

函数名: void exit(int status);
功能: 使进程自然结束
函数参数:int status 用于在进程结束时返回其父进程的数据status & 0377
返回值: 该函数不会返回
备注: return 0;使函数结束,但由于结束函数不一定是main函数,所以调用return 0;后不一定使进程结束;而在任一子函数中调用exit(0)都会使当前进程结束运行。在一个进程中调用exit(0)会使当前进程结束运行。 exit(0)近似于在main()中调用return 0;

6.进程的等待

函数名: *pid_t wait(int status);
功能:

  1. 等待(期间,等待状态)一个子进程的状态改变(运行结束)
  2. 让系统回收本次结束的子进程的空间

函数参数:int *status
与本次结束的子进程中调用exit(int status)搭配
用于返回exit()参数中的值
返回值:
成功,本次结束的子进程的PID
失败,-1
备注:调用该函数时,会使当前进程进入到等待状态,直到一个子进程结束运行/状态改变。若没有子进程,则该函数不生效。

int main(void)
{
	pid_t ret;
	ret = fork();
	if(ret == -1)
	{
		printf("fork failed\n");
		return -1;
	}
	else if(ret == 0)//child only
	{
		printf("I am child\n");
		sleep(5);
		printf("ret = %d , PID = %d , PPID = %d\n",ret,getpid(),getppid());
		sleep(5);
	}
	else//father only
	{
		printf("I am father\n");
		printf("ret = %d , PID = %d , PPID = %d\n",ret,getpid(),getppid());
		wait(NULL);  //父进程等待子进程运行结束,让系统回收子进程的空间避免出现僵尸进程。
		printf("one of my child is dead\n");
	}
	exit(0);//child and father
}

函数名:*pid_t waitpid(pid_t pid, int status, int options);
功能 :

  1. 等待(期间,等待状态)一个子进程的状态改变(运行结束)
  2. 让系统回收本次结束的子进程的空间

函数参数:
pid_t pid
<-1,等待进程组ID为 |pid|的任一一个子进程结束
=-1,等待任一一个子进程结束
=0 , 等待同组的一个子进程结束
>0 , 等待进程ID为pid的子进程结束
int *status 与本次结束的子进程中调用exit(int status)搭配
用于返回exit()参数中的值
int options 一般填0
返回值 成功,本次结束的子进程的PID
失败,-1

四、实践

1.实现父子进程有效执行不同的程序代码

通过fork()函数在父子进程中不同返回值实现

int main(void)
{
	pid_t pid,ppid;
	int i=0;
	pid_t ret;
	ret = fork();
	if(ret < 0)
	{
		printf("create child process failed\r\n");
		return -1;
	}
	if(ret == 0)
	{
		//Child
		printf("I am child,pid = %d,ppid = %d\r\n",getpid(),getppid());
	}
	else
	{
		//Parent
		printf("I am parent,pid = %d,ppid = %d\r\n",getpid(),getppid());
	}
}

在这里插入图片描述

2.设计一个程序,实现一个进程和其两个子进程,交替显示数据1~15(每隔1s显示一个数据)

比如:
C1:1
C2:2
P :3
C1:4
C2:5
P :6
…….

int main(int argc,char *argv[])
{
	int i;
	pid_t ret1,ret2;
	ret1 = fork();
	if(ret1 == 0)//child1 only
	{
		for(i=1;i<=15;i+=3)
		{
			sleep(1);
			printf("C1:%d\n",i);
			sleep(2);
		}
	}
	else	     //parent only
	{
		ret2 = fork();
		if(ret2 == 0)	//child2 only
		{
			for(i=2;i<=15;i+=3)
			{
				sleep(2);
				printf("C2:%d\n",i);
				sleep(1);
			}
		}
		else            //parent only
		{
			for(i=3;i<=15;i+=3)
			{
				sleep(3);
				printf("P :%d\n",i);
			}
			waitpid(ret1,NULL,0);//wait(NULL);
			waitpid(ret2,NULL,0);//wait(NULL);
		}
	}
	exit(0);
}


版权声明:本文为qq_29011025原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。