Ubuntu Linux编程快速入门(1-1)-系统API-获得进程基本信息


如无说明,假定本文所有文件名为main。
这一节的核心是一道经典的面试题。先放简单变体:

#include<unistd.h>
#include<stdio.h>
int main(){
	for(int i=0;i<2;++i){
		printf("-\n");
		fork();
	}
	return 0;
}

执行命令和结果:

> ./main
-
-
> -

读者应该已经注意到,本来应该属于用户输入的提示文本,被夹在程序输出中间了。之后我们会更明确地看到一点,通常而言,shell在它启动的进程结束后,会立即输出提示文本并接收用户输入,但其他由该进程启动的进程仍在运行。
这里首先介绍一个可以创建进程的函数,方便简单理解之后的内容。

fork进程

(笔者实在是不知道这东西怎么翻译。。。)

基本使用

/*
@brief 用于创建一个几乎一样的进程(指令段和多数数据)。
@return 创建者得到新建进程的进程pid,被创建者得到0。
返回值通常写的是pid_t,一般就是int。
*/
pid_t fork(void)

从本文最开始的代码中可以看出它的用法。

进程0
i=0, 输出, fork
进程1
i=0
进程0
i=1, 输出, fork
进程1
i=1, 输出, fork
进程2
i=1
进程3
i=1

从这里能看出为什么它总共输出了三个进程。
我们用两个函数来验证一下。

获取进程pid

基本用法

/*
@brief 用于获取进程pid
@return 当前进程的pid
*/
pid_t getpid(void)

获取父进程pid

基本用法

/*
@brief 获取父进程的pid
@return 父进程的pid
*/
pid_t getppid(void)

进程信息样例代码

#include<unistd.h>
#include<stdio.h>
int main(){
	for(int i=0;i<2;++i){
		printf("pid:%d,ppid:%d\n",getpid(),getppid());
		fork();
	}
	return 0;
}

执行指令和结果:

> ./main
pid:64686,ppid:57387
pid:64686,ppid:57387
> pid:64687,ppid:1325

这里也能看出,fork并不是直接创建进程的。至少很多情况下是这样。

#include<unistd.h>
#include<stdio.h>
int main(){
	printf("pid:%d,fork():%d\n",getpid(),fork());
	return 0;
}

执行指令和结果:

> ./main
pid:64772,fork():64773
> pid:64773,fork():0

这里我们看到,想要直接获取fork所得进程的pid,应直接使用fork的返回值

一些有趣的东西

接下来,我们看看开篇提到的面试题吧:

#include<unistd.h>
#include<stdio.h>
int main(){
	for(int i=0;i<2;++i){
		printf("-");
		fork();
	}
	return 0;
}

会输出几个-字符?
相比本节开始的简化版,只减少了用于换行的\n转义符。那么应该还是三个。
真的吗?

> ./main
--> ------

实测一下,发现是八个。我们换个更能说明问题的写法。

#include<cstdio>
#include<pthread.h>
#include<unistd.h>
int main(){
	for(int i=0;i<2;i++){
		printf("(%d)",getpid());
		fork();
	}
	return 0;
}

执行指令和结果:

> ./main
(64899)(64899)> (64899)(64899)(64899)(64900)(64899)(64900)

看上去更诡异了,很多pid是一样的……
为什么呢?想一想,进程数据中还有哪些很关键的东西?显然,这里涉及的是输出缓冲区。没有\n而且缓冲区不满,一般是不会输出的。永远不要忘了它,否则指不定什么时候就被它坑了。

进程0
i=0, fork
缓冲区: -
进程0
i=1, fork
缓冲区: --
进程1
i=0
缓冲区: -
进程1
i=1, fork
缓冲区: --
进程2
i=1
缓冲区: --
进程3
i=1
缓冲区: --

观察到总共四个进程,每个进程缓冲区内最后有两个字符,所以共计八个。同理,我们来看看pid输出情况。假定pid就是0开始计(实际情形中,除非你把系统内核改了,否则基本不涉及pid真的是0的情形)。

进程0
i=0, fork
缓冲区: 0
进程0
i=1, fork
缓冲区: 00
进程1
i=0
缓冲区: 0
进程1
i=1, fork
缓冲区: 01
进程2
i=1
缓冲区: 00
进程3
i=1
缓冲区: 01

由于笔者不太会用mermaid,这里先把括号略去了……读者们明白就好。
这里我们也可以大致猜测,进程2实际上在进程1之前进行。这也告诉我们一点,不要试图依赖fork的执行顺序。此外,你的输出可能误导你对程序运行状况的判断,不要过度依赖输出。一定要用输出来调试的话,尽可能在每次输出后立即用\n强制输出和清空缓冲区内容。如果你是竞赛生,你的老师可能会持有相反的观点,但这实际上不是本质问题。本质问题是,不要混淆竞赛风格和工程风格,二者的代码写出来天差地别。
此外补充一点,fork后调用exec系列函数可能代价比较大,建议这时使用vfork。但如果fork后不进行进程体替换则不能使用vfork。如果fork后仍要执行exec最多就是性能问题,但vfork后不执行exec系列函数则可能引发致命错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值