先简单介绍一下进程的概念
定义: 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
经典定义: 进程是一个执行中的程序的实例。系统中的每个程序都是运行在某个进程上下文(context)中的。
注:上下文可能让人看不懂,简单地理解,将文本文档里的‘e’字母翻译成ASCII码是101,,在这个环境(上下文)中101代表字母e;当用二进制101转换成int型后,101就是数值5。 在不同的环境中,二进制101所代表的意义不同,这个不同的环境就是上下文。
作用:有了进程的概念之后,程序在运行的过程中才会产生这种假象,好像自己是系统当前运行的唯一的程序,独占地使用CPU、独占地使用存储器。
进程所拥有的:1、独立的逻辑控制流。 (即 某时刻利用cpu来执行指令)
2、私有的地址空间。 (即存储器的一段地址空间)
内核为每个进程维持一个上下文,通过上下文切换的方式来从一个进程跳转到另一个进程,上下文切换也是进程并发执行的基础。
每个进程都有一个进程id(大于0的整数),getpid()可以返回进程id,getppid()返回父进程的pid。
#include
#include
pit_t getpid(void);
pid_t getppid(void);
先通过具体的程序来看一看,如何获取进程和父进程id。
#include
#include
void main(){
pid_t pid;
fprintf(stdout, "I am the first process, my pid is %d, my parent pid is %d\n", getpid(), getppid());
}
#include
#include
pid_t fork(void);
进程的退出
进程通过exit函数退出,status用来设置进程的退出状态。exit(0)代表正常0,exit(1)代表异常退出。
#include
void exit(int status);
在父进程中,当我们调用了fork函数之后,我们就创建了一个新的进程。这个函数在两种不同的进程上下文中返回不同的值。在父进程中,fork返回子进程的pid;在子进程中fork返回0。
我们要注意的是:
1、上面这句话第一次读的时候可能会觉得有些奇怪,子进程就不是进程吗?为什么它的fork函数会返回0,而不是返回子进程的子进程的pid?其实当子进程被创建的时候,系统会给子进程开辟一个新的地址空间,然后将父进程地址空间的所有东西都复制给子进程,也就是说会将父进程中的pid = Fork()这一段指令也复制给子进程。但是如果这个被复制来的pid=Fork()也被执行了,那么子进程就会产生新的子进程,然后新的子子进程复制了这句话又产生了新的子子子进程,这样岂不是子子孙孙无穷尽也了?所以在子进程中,这条指令是不会被执行的,而是会返回一个0。
2、子进程到底复制了哪些东西?a)所有的变量 b)从创建它的fork函数开始的指令(当然也有可能是指令也被全部复制,只是从fork的地址开始执行,这个有待继续考证)。被复制过来的用于创建它的fork函数如上所讲不会被执行,但是下面的其它的不是用来创建它的fork函数却会被执行。这个下面再讨论。
先看看具体实例:
#include
#include
#include
#include
void error_msg(char *msg){
fprintf(stderr, "%s: %d\n", msg, strerror(errno));
exit(0);
}
int Fork(){
pid_t pid;
if ( ( pid = fork() ) < 0 )
error_msg("fork failed");
return pid;
}
void main(){
pid_t pid;
fprintf(stdout, "I am the first process, my pid is %d, my parent pid is %d\n", getpid(), getppid());
pid = Fork();
if ( 0 == pid )
fprintf(stdout, "I am the child procress, my pid value is %d, but my real pid is %d, my parent pid is %d\n", pid, getpid(), getppid());
else
fprintf(stdout,"I am the parent process, my child pid is %d, my pid is %d, my parent pid is %d\n", pid, getpid(), getppid());
}
对于这段程序,采用了包裹函数的形式进行了错误处理,这样就可以直接使用pid = Fork()的形式进行子进程的调用,而不用额外写一大堆臃肿的错误处理代码。
简单运行如下:
结果分析:
1、第23行的fprintf函数只被父进程执行了,而在子进程中并没执行,但是24行的pid=Fork()后面的fprintf语句却被子进程执行了。证明这个指令的复制是以fork本身为分界线的。
2、不管父进程还是子进程,都有pid这个变量,它们的值在两个进程中是不一样的,在父进程中pid为子进程的pid52569,在子进程pid的值为0。 这个就让部分人头疼了,怎么fork上面有的代码被复制了,有的代码没有被复制?这个就得了解,程序被装载的时候,变量和指令存储的位置是不一样的,将变量全部复制,然后只执行部分指令,其实也很容易理解。
3、我明明执行了一次./a.out这个指令来运行这段程序,为什么会在运行的过程中会打印出来“jdh@jdh-virtual........../cssh$”这个东西,然后再继续运行程序,这个不是很奇怪么?而且子进程的父进程不就是最开始的进程吗?它的pid应该是52568,怎么变成了1352了呢?
这个我最开始也很奇怪,在查看了一些资料后知道,其实这是子进程被托管了的标志。子进程被创建后,父进程和子进程就同时作为两个独立的进程独立地运行。它们执行的先后顺序依赖于操作系统的调度。在本实例中,父进程在打印完了最后的话后就该结束了,所以它正常的退出了。退出之前,父进程将子进程托管其它的进程。大多数的时候子进程会被托管给init进程,而init的进程号是1。这时候疑问又来了,这个是1352又不是1,那么它到底被托管给了谁?其实是因为在ubuntu下面有一个init -user进程,一看这个名字就知道这个和init有关系了吧。至于为什么ubuntu要弄出个这个来,就超出了fork的范围了,有兴趣自己再深究。
我抓取下init -user 这个进程,看看他是不是1352,如图所示:
很庆幸,被我抓获到了,这下无影遁形了吧?
接下来,我们看看,假如父进程有多个fork,到底会产生多少个子进程。
#include
#include
#include
#include
void error_msg(char *msg){
fprintf(stderr, "%s: %d\n", msg, strerror(errno));
exit(0);
}
int Fork(){
pid_t pid;
if ( ( pid = fork() ) < 0 )
error_msg("fork failed");
return pid;
}
void main(){
pid_t pid;
fprintf(stdout, "I am the first process, my pid is %d, my parent pid is %d\n", getpid(), getppid());
pid = Fork();
pid = Fork();
pid = Fork();
fprintf(stdout," Hi Fork , my pid is %d, my parent pid is %d \n", getpid(), getppid());
}
结果分析:
我们不难看出调用了3个,产生了除了first进程外的7个其它进程,而且很不幸的是,它们都被托管了。
问题:为什么调用3个产生7个?你知道吗?
参考《深入理解计算机系统》