Linux进程-----父进程与子进程(详细篇)

进程概念

一个程序文件 (program) ,只是一堆待执行的代码和部分待处理的数据,他们只有被 加载到内存中,然后让 CPU 逐条执行其代码,根据代码做出相应的动作,才形成一个真正 “活的”、动态的进程 (process) ,因此进程是一个动态变化的过程,是一出有始有终的 戏,而程序文件只是这一系列动作的原始蓝本,是一个静态的剧本。

 5-1 更好地展示了程序和进程的关系。

Linux进程-----父进程与子进程(详细篇)_第1张图片

当一个程序文件被执行的时候,内核将会产生这么一个结构体,来承载所有该活 动实体日后运行时所需要的所有资源,随着进程的运行,各种资源被分配和释放,是一个动 态的过程

另外,每个进程都有自己“身份证号码”,即 PID 号,PID 是重要的系统资源,他是 用以区分各个进程基本依据。可以使用命令 ps 来查看进程的 PID

Linux进程-----父进程与子进程(详细篇)_第2张图片

进程的生老病

说进程是动态的活动的实体,指的是进程会有很多种运行状态,一会儿睡眠、一会儿暂 停、一会儿又继续执行。下图给出 Linux 进程从被创建 (生) 到被回收 (死) 的全部状态, 以及这些状态发生转换时的条件:

Linux进程-----父进程与子进程(详细篇)_第3张图片

相关重要 API

API(Application Programming Interface)是一组定义了不同软件组件之间交互规则的函数和接口。它提供了一种编程接口,使得开发者可以通过调用这些接口函数来使用底层操作系统或其他软件库提供的功能。

建一个新的进程

文件

#include <unistd.h>

pid_t fork(void);

回值

成功

0 或者大于 0 的正整数

- 1

该函数执成功之后,将会产生一个新的子进程,在新的子进程中其返 回值为 0 ,在原来的父进程中其返回值为大于 0 的正整数,该正整数 子进程的 PID

这个函数接口本身非常简单,简单到连参数都没有,但是这个函数有个非常与众不同的 地方:他会使得进程一分为二!就像细胞分裂一样:

Linux进程-----父进程与子进程(详细篇)_第4张图片

当一个进程调用 fork( )成功后,fork( )将分别返回到两个进程之中,换句话说,fork( ) 在父子两个进程中都会返回,而他们所得到的返回值也不一样,看下图:

Linux进程-----父进程与子进程(详细篇)_第5张图片

1 fork( )会使得进程本身被复制 (想想细胞分裂) ,因此被创建出来的子进程和父进 程几乎是一模一样的,说“几乎”意味着子进程并不是 100%为一份父进程的复印件他们 具体关系如下:

父子进程的以下属性在创建之初完全一样,子进程相当于搞了一份复制品:

A) 实际 UID GID,以及有效 UID GID

B) 所有环境变量。

C) 进程组 ID 和会话 ID

D) 前工作路径。除非用 chdir()加以修改

E) 打开的文件。

F) 信号响应函数。

G) 整个内存空间,包括栈、堆、数据段、代码段、标准 IO 缓冲区等等。

而以下属性,父子进程是不一样的:

A) 进程号 PID PID 份证号码,哪怕亲如父子,也要区分开。

B) 记录锁。父进程对某文件加了把锁,子进程不会继承这把锁

C) 挂起的信号。这些信号是所谓的“悬而未决”的信号,等待着进程的响应,子进程 也不会继承这些信号。

2 ,子进程会从 fork( )返回值后的下一条逻辑语句开始运行。这样就避免了不断调 fork( )而产生无限子孙的悖论。

3,父子进程是相互平等的:他们的执行次序是随机的,或者说他们是并发运行的,除非使用特殊机制来同步他们,否则你不能判断他们的运行究竟谁先谁后。

 

4,父子进程是相互独立的:由于子进程完整地复制了父进程的内存空间,因此从内存 空间的角度看他们是相互独立、互不影响的。

示列代码如下:

#include 
#include 
#include 
#include 
#include 

int main()
{
	printf("hello\n");
	
	pid_t pid = fork();
	
	if(pid == 0)
	{
		
		//sleep(10);
		printf("[我是son]world,[自己p的pid]%d,[父进程的pid]%d\n",getpid(),getppid());//getpid(),getppid()
		//给出一个退出值        子进程      父进程
		exit(99);   //退出码 
		
		//正常exit(1),被信号杀死exit(2),异常退出exit(3);
	}
	if(pid > 0)
	{
		//wait(),等待子进程
		
		int status;
		
		wait(&status);
		printf("[我嫩爹]world,[自己的pid]%d,[父进程的pid]%d\n",getpid(),getppid());//
		
		printf("[退出状态值]%d\n",WEXITSTATUS(status));//获取退出值
		
	}
	
	
	return 0;
}

上述代码中,因为父进程和进程具备并发性 掉用wait函数使父进程先结束子进程再运行。

exit()函数的使用规范

退出本进

文件

#include td.h>

#include h>

void _exit(int status);

void exit(int status);

参数

status

子进程的退出值

回值

返回

1,如果子进程正常退出,则 status 一般为 0

2,如果子进程异常退出,则 statuc 一般为非 0

3exit( )退出时,会自动冲洗 (flush) 标准 IO 总残留的数据到内核,如果进程注册 了“退出处理函数”还会自动执行这些函数。而_exit( )会直接退出。

5-3 函数 exit()_exit()的接口规范

子进程

接下一个脱口而出的疑问是:好不容易生了个孩子,但是干的事情跟父进程是一样的, 那我们要这个孩子有何用呢答案是:上述代码确实没有什么实际意义,事实上我们一般会 让孩子去执行一个预先准备好的 ELF 文件或者脚本,用以覆盖从父进程复制过来的代码, 来介绍这个加载 ELF 文件或者脚本的接口函数:

在进程中加载新的程序文件或者脚本,覆盖原有代码,重新运行

文件

#include td.h>

int execl(const char *path, const char *arg, ...);

int execv(const char *path, char *const argv[ ]);

int execle(const char *path, const char *arg, ..., char * const envp[ ]);

int execlp(const char *file, const char *arg, ...);

int execvp(const char *file, char *const argv[ ]);

int execvpe(const char *file, char *const argv[ ],char *const envp[ ]);

参数

path

即将加载执行的 ELF 文件或脚本的路径

file

即将加载执行的 ELF 文件或脚本的名字

arg

以列表方式罗列的 ELF 文件或脚本的参数

argv

以数组方式组织的 ELF 文件或脚本的参数

envp

用户自定义的环境变量数

回值

成功

返回

- 1

1,函数名带字母 l 意味着其参数以列表 (list) 的方式提供。

2,函数名带字母 v 意味着其参数以矢量 (vector ) 数组的方式提供。

3,函数名带字母 p 意味着会利用环境变量 PATH 来找寻指定的执行文件。

 4,函数名带字母 e 意味着用户提供自定义的环境变量。

函数族 exec( )的接口规范

使用exec函数注意事项

1,被加载的文件的参数列表必须以自身名字为开始,以 NULL 为结尾。比如要加载执 行当前目录下的一个叫做 a.out 的文件,需要一个参数abcd,那么正确的调用应该是:

execl(“ ./a.out”, “a.out”, “abcd”, NULL);

或者:

const char *argv[3] = {“a.out”, “abcd”, NULL};

execv(“ ./a.out, argv);

2exec 函数簇成功执行后,原有的程序代码都将被指定的文件或脚本覆盖,因此这些 函数一旦成功后面的代码是无法执行的,他们也是无法返回的。

示列代码如下:

#include 
#include 
#include 
#include 
#include 

int main()
{
	printf("hello\n");
	
	pid_t pid = fork();
	
	if(pid == 0)
	{
		execl("./son","./son","1.txt","2.txt","3.txt",NULL);//函数名带l 参数以列表的方式提供
		
		 //定义参数数组
		// char *arg[] = {"./a.out","1.txt","2.txt","3.txt",NULL};
		// execv("./a.out",arg);//函数名带v,将参数列表以数组的方式传入
		
		//execl("/bin/ls","ls",NULL);//execl也可以直接使用shell命令替换子进程的代码,但是需要指定shell命令的路径
		
		//execlp("ls","ls",NULL);//函数名带p,自动寻找环境变量,不需要指定路径

		printf("[我是son]world,[自己的pid]%d,[父进程的pid]%d\n",getpid(),getppid());    
                                   //getpid(),getppid()
		//给出一个退出值
		exit(99);//exit可以自己指定退出值
		
	}
	if(pid > 0)
	{
		//wait(),等待子进程
		
		int status;
		wait(&status);
		
		printf("[我嫩爹]world,[自己的pid]%d,[父进程的pid]%d\n",getpid(),getppid());//
		
		printf("[退出状态值]%d\n",WEXITSTATUS(status));
                                         //status是一个4个字节的状态值,其中最后一个字节才是退出值,用宏WEXITSTATUS()可以直接打印退出值
		
	}
	
	
	return 0;
}

上述代码注释部分解释了exec的多种用法!

上述程序中使用wait函数如下

等待子进

文件

#include ait.h>

pid_t wait(int *stat_loc);

pid_t waitpid(pid_t pid, int *stat_loc, int options);

参数

pid

-1 :等待组 ID 的绝对值为 pid 的进程组中的任一子进程

-1:等待任一子进程

0:等待调用者所在进程组中的任一子进程

 0 :等待进程组 ID pid 的子进程

stat_loc

子进程退出状态

option

WCONTINUED:报告任一从暂停态出来且从未报告过的子进程的状态

WNOHANG:非阻塞等

WUNTRACED:报告任一当前处于暂停态且从未报告过的子进程的状态

回值

wait( )

功:退出的子进程 PID

败:- 1

waitpid( )

功:状态发生改变的子进程 PID (如果 WNOHANG 被设置,且由 pid  定的进程存在但状态尚未发生改变,则返回0) 。

败:- 1

如果不需要获取子进程的退出状态,stat_loc 可以设置为 NULL

 5-4 函数 wait()和 waitpid()的接口规范

注意,所谓的退出状态不是退出值,退出状态包括了退出值。如果使用以上两个函数成 功获取了子进程的退出状态,则可以使用以下宏来进一步解析:

WIFEXITED(status)

如果子进程正常退出,则该宏为真。

WEXITSTATUS(status)

如果子进程正常退出,则该宏将获取子进程的退出值。

WIFSIGNALED(status)

如果子进程被信号杀死,则该宏为真。

WTERMSIG(status)

如果子进程被信号杀死,则该宏将获取导致他死亡的信号值。

 WCOREDUMP(status)

如果子进程被信号杀死且生成核心转储文件 (core dump) , 则该宏为真。

WIFSTOPPED(status)

如果进程的被信号暂停,且 option WUNTRACED 已经被 置时,则该宏为真。

WSTOPSIG(status)

如果 WIFSTOPPED(status)为真,则该宏将获取导致子进程暂 停的信号值。

WIFCONTINUED(status)

如果子进程被信号 SIGCONT 重新置为就绪态,该宏为真。

5-5 处理子进程退出状态值的宏

①正常退出指的是调用 exit( )/_exit( ),或者在主函数中调用 return,或者在最后一个 线程调用 pthread_exit( )

②由于没有 POSXI.1-2001 标准中定义,这个选项在某些 Unix 系统中无效,比如 AIX 或者 sunOS 中。

至此,我们已经知道如何创建多进程,以及掌握了他们的基本操作方法了,有一点是必 须再提醒一次的:进程他们是相互独立的,最重要体现在他们互不干扰的内存空间上,他们的数据是不共享的,但如果多个进程需要协同合作,就必然会有数据共享的需求,就像人与 人之间需要说话一样,进程亟需通过某样东西来互相传递信息和数据,这就是所谓的 IPC (Inter-Process Comunication) 机制,IPC 有很多种,他们是怎么使用的,有哪些特点,在 什么场合适用,请关注下一篇文章咱们慢慢道来。

你可能感兴趣的:(linux,运维,服务器)