操作系统:进程(一)

进程的基本概念

一般的解释是:进程是程序的一个执行实例,是正在执行的程序。我们写的程序编译后是一段二进制的文件。启动的时候加载到系统里面执行,就是以进程的形式执行。也就是说,我们编译后的可执行程序是一个静态的概念,加载到系统中以进程的方式执行,是一个动态的概念。
从系统的角度看:进程是系统资源(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()函数来创建新的进程(子进程),也就是通过这个函数去执行从父进程到子进程的复制工作。
    这个函数比较特殊的地方是,调用一个,有两个返回结果。
    1. 父进程返回创建的子进程的PID
    2. 子进程返回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.但我们无法判断是哪个进程先打印,哪个进程后打印的。

僵尸进程(Zombie)

Kernal 并不会在进程终止后立即将其清理掉,已经终止的进程一般会保持在terminated状态,直到被父进程回收。
一个进程在调用exit()函数结束自己的生命的时候,kernal首先把子进程的exit status 发送给父进程,然后抛弃已经终止的进程,它并没有被真正的销毁,而是留下一个僵尸进程的数据结构,直到在被父进程回收资源之前,这个进程就称为僵尸进程。
如果父进程在子进程之前结束了,没有回收子进程的资源。子进程就会被init接管,由init来回收资源,这个过程就会比较久。
即使僵尸进程已经不再运行了,不再消耗cpu资源,但他们也消耗了系统的内存资源。所以我们在自己写程序的时候,要养成好的习惯,创建的子进程要尽量的去回收它的资源。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值