进程的基本概念
一般的解释是:进程是程序的一个执行实例,是正在执行的程序。我们写的程序编译后是一段二进制的文件。启动的时候加载到系统里面执行,就是以进程的形式执行。也就是说,我们编译后的可执行程序是一个静态的概念,加载到系统中以进程的方式执行,是一个动态的概念。
从系统的角度看:进程是系统资源(cpu 时间,内存)分配的最小实体单位。
进程ID(PID)
每一个进程都有唯一的一个非负整数的ID,这个ID就是PID(process iD)
- Getpid() :返回当前进程的ID
- Getppid() : 返回当前进程的父进程的ID。
#include <unistd.h>
#include <sys/types.h>
pid_t getpid(void);
pid_t getppid(void);
父进程和子进程
这里涉及到父进程和子进程的概念。为什么会有父进程和子进程?进程的创建并不像结构体或者类一样,new一下就可以创建出来。进程之前存在着继承的关系。进程B继承于进程A,那么进程A就是进程B的父进程。进程B就是进程A的子进程。
因为进程是系统资源的分配实体,所以进程里面会有很多的系统信息,进程相关的分配的内存,寄存器,数据,PCB等很多的域。如果我们自己创建会是一个很繁琐的过程。在早期的操作系统设计的时候,就会考虑如何方便的创建进程。一个比较方便的办法就是把已经存在的进程的各个域完全拷贝一份,然后再修改不同的地方,就形成了一个新的进程,这样的话,创建的进程和被创建的进程之间就存在继承的关系,也就是父子进程。
在linux操作系统启动的时候,系统会先创建一个Init进程,这个是整个系统中的第一个进程,然后再由这个init进程去创建后面的系统进程和用户进程。所以从这个角度看,一个系统中的所有进程都有一个共同的祖先就是init进程。
父进程和子进程之间不只是复制一份的关系。父进程还需要负责子进程的资源的回收。也就是子进程结束后,有很多资源如果没有人回收的话,一个是会造成资源的浪费,另一个是时间久了,会导致整个系统没有可用的资源了。当前也会存在父进程比子进程先结束的情况,这种时候,init进程就会变成子进程的父进程。对资源进行回收,不过这个时间就会比较长。所以在编程的时候,大家还是最好自己创建的进程在使用完后就行回收,避免资源浪费。
进程退出
- exit(status)
这个函数没有返回值,会通过参数指定返回状态,这个返回状态会被父进程接收到,父进程就可以做一些处理。
#include<stdlib.h>
void exit(int status);
如何创建和初始化进程
上面在讲父进程和子进程的时候也提到了,如果先创建一个空的数据结构,再填充每个数据域,工作量是非常大的。所以操作系统采用的方式是通过父进程复制的方式创建新的进程。内核init进程是所有进程的祖先,pid=1, 所以的进程最初都是由init进程复制的方式而来的。
下面是创建新进程的时候用到的两个函数:
- fork()
通过fork()函数来创建新的进程(子进程),也就是通过这个函数去执行从父进程到子进程的复制工作。
这个函数比较特殊的地方是,调用一个,有两个返回结果。- 父进程返回创建的子进程的PID
- 子进程返回0
- exec(…)
通过上面的函数单纯的进行复制,并没有太大的意义,更多的时候我们还是希望新的进程可以执行新的任务,所以需要exec()这个函数.参数传进来一个program,更换当前的code和data,然后执行传进来的program命令。
#include <unistd.h>
#include <sys/types.h>
//Returns: 0 to child, Pid of child to parent, -1 on error
pid_t fork(void);
需要注意的是通过fork()创建的新的子进程,几乎,但不是完全的与父进程相同。父进程和子进程有不同的PID。
子进程得到一份父进程用户层虚拟机地址空间的完全拷贝。同时也得到父进程已打开的文件描述符的完全拷贝,这意味着子进程可以直接读写父进程中已经打开的任何文件。
下面是一个简单的例子:
1 #include "csapp.h"
2 #include <unistd.h>
3 int main()
4 {
5 pid_t pid;
6 int x = 1;
7
8 pid = fork();
9 if (pid == 0) { /* child */
10 printf("child : x=%d\n", ++x);
11 exit(0);
12 }
13
14 /* parent */
15 printf("parent: x=%d\n", --x);
16 exit(0);
17 }
第8行调用fork()函数时,进程就产生了分差,变成了一个父进程一个子进程同时在系统中运行,在父进程中,fork()函数返回子进程的pid, 在子进程中,fork()函数返回0,表示当前是子进程自己。由于x的定义是在fork()之前,所以在执行fork()的时候,x被复制了一份,一个属于子进程,一个属于父进程。
在第10行的打印,是子进程打印的,x为2;第15行为父进程打印的为0.也就是说从fork往后,父进程和子进程的数据就是独立的两份了,相互没有了关系。
再举例如下:
1 #include "csapp.h"
2
3 int main()
4 {
5 Fork();
6 Fork();
7 printf("hello!\n");
8 exit(0);
9 }
经过第5行的fork(),就会产生一对父子进程,两个进程继续向下走,到第6行,再次fork(),两个进程又分别创建一个子进程。如下图,到第七行打印的时候就有4个进程在打印。所以使用fork()创建进程是一种指数的增长。
进程的并行
在操作系统中进程是并行运行的,即使是父子进程,从创建出子进程的一刻开始,两个进程就开始并行运行了。所以基于上面的程序虽然会打印4次hello.但我们无法判断是哪个进程先打印,哪个进程后打印的。