进程的概念以及进程的创建

1. 进程的基本概念

1.1 基本概念

进程Process是指计算机中已运行的程序,是系统进行资源分配和调度的基本单位,是操作系统结构的基础
在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;
在当代面向线程设计的计算机结构中,进程是线程的容器。
进程是程序真正运行的实例,若干进程可能与同一个程序相关,且每个进程皆可以同步或异步的方式独立运行。

狭义定义:进程是正在运行的程序的实例

广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

进程的概念主要有两点:

第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。

1.2 进程与程序的主要区别:

  • 程序是永存的;进程是暂时的,是程序在数据集上的一次执行,有创建有撤销,存在是暂时的;
  • 程序是静态的观念,进程是动态的观念;
  • 进程具有并发性,而程序没有;
  • 进程是竞争计算机资源的基本单位,程序不是。
  • 进程和程序不是一一对应的:
    一个程序可对应多个进程即多个进程可执行同一程序;
    一个进程可以执行一个或几个程序

1.3 并发与并行的区别

并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。就好像两个人各拿一把铁锨在挖坑,一小时后,每人一个大坑。所以无论从微观还是从宏观来看,二者都是一起执行的。
image
并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。这就好像两个人用同一把铁锨,轮流挖坑,一小时后,两个人各挖一个小一点的坑,要想挖两个大一点得坑,一定会用两个小时。
image

并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在,并发能够在单处理器系统中存在是因为并发是并行的假象,并行要求程序能够同时执行多个操作,而并发只是要求程序假装同时执行多个操作(每个小时间片执行一个操作,多个操作快速切换执行)。

1.4 描述进程—–PCB

  • 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
  • 课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct
  • 在Linux中描述进程的结构体叫做task_struct。
  • task_struct是Linux内核的⼀一种数据结构,它会被装载到RAM(内存)⾥里并且包含着进程的信息。

1.5 task_ struct内容分类

在进程执行时,任意给定一个时间,进程都可以唯一的被表征为以下元素。

标示符: 描述本进程的唯一标示符,⽤用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据
I/O状态信息: 包括显⽰示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息

2. 进程的特征

进程是由多程序的并发执行而引出的,它和程序是两个截然不同的概念。进程的基本特征是对比单个程序的顺序执行提出的,也是对进程管理提出的基本要求。

  1. 动态性:进程是程序的一次执行,它有着创建、活动、暂停、终止等过程,具有一定的生命周期,是动态地产生、变化和消亡的。动态性是进程最基本的特征。

  2. 并发性:指多个进程实体,同存于内存中,能在一段时间内同时运行,并发性是进程的重要特征,同时也是操作系统的重要特征。引入进程的目的就是为了使程序能与其他进程的程序并发执行,以提高资源利用率。

  3. 独立性:指进程实体是一个能独立运行、独立获得资源和独立接受调度的基本单位。凡未建立PCB的程序都不能作为一个独立的单位参与运行。

  4. 异步性:由于进程的相互制约,使进程具有执行的间断性,即进程按各自独立的、 不可预知的速度向前推进。异步性会导致执行结果的不可再现性,为此,在操作系统中必须配置相应的进程同步机制。

  5. 结构性:每个进程都配置一个PCB对其进行描述。从结构上看,进程实体是由程序段、数据段和进程控制段三部分组成的。

3. 进程的状态和转换

3.1 五种状态

进程在其生命周期内,由于系统中各进程之间的相互制约关系及系统的运行环境的变化,使得进程的状态也在不断地发生变化(一个进程会经历若干种不同状态)。通常进程有以下五种状态,前三种是进程的基本状态。

  1. 运行状态:进程正在处理机上运行。在单处理机环境下,每一时刻最多只有一个进程处于运行状态。

  2. 就绪状态:进程已处于准备运行的状态,即进程获得了除处理机之外的一切所需资源,一旦得到处理机即可运行。

  3. 阻塞状态:又称等待状态:进程正在等待某一事件而暂停运行,如等待某资源为可用(不包括处理机)或等待输入/输出完成。即使处理机空闲,该进程也不能运行。

  4. 创建状态:进程正在被创建,尚未转到就绪状态。创建进程通常需要多个步骤:首先申请一个空白的PCB,并向PCB中填写一些控制和管理进程的信息;然后由系统为该进程分 配运行时所必需的资源;最后把该进程转入到就绪状态。

  5. 结束状态:进程正从系统中消失,这可能是进程正常结束或其他原因中断退出运行。当进程需要结束运行时,系统首先必须置该进程为结束状态,然后再进一步处理资源释放和 回收等工作。

注意区别就绪状态和等待状态:就绪状态是指进程仅缺少处理机资源,只要获得处理机资源就立即执行;而等待状态是指进程需要其他资源(除了处理机)或等待某一事件。之所以把处理机和其他资源划分开,是因为在分时系统的时间片轮转机制中,每个进程分到的时间片是若干毫秒。也就是说,进程得到处理机的时间很短且非常频繁,进程在运行过程中实际上是频繁地转换到就绪状态的;而其他资源(如外设)的使用和分配或者某一事件的发生(如I/O操作的完成)对应的时间相对来说很长,进程转换到等待状态的次数也相对较少。这样来看,就绪状态和等待状态是进程生命周期中两个完全不同的状态,很显然需要加以区分。
image

3.2 状态转换

就绪状态 -> 运行状态:处于就绪状态的进程被调度后,获得处理机资源(分派处理机时间片),于是进程由就绪状态转换为运行状态。

运行状态 -> 就绪状态:处于运行状态的进程在时间片用完后,不得不让出处理机,从而进程由运行状态转换为就绪状态。此外,在可剥夺的操作系统中,当有更高优先级的进程就 、 绪时,调度程度将正执行的进程转换为就绪状态,让更高优先级的进程执行。

运行状态 -> 阻塞状态:当进程请求某一资源(如外设)的使用和分配或等待某一事件的发生(如I/O操作的完成)时,它就从运行状态转换为阻塞状态。进程以系统调用的形式请求操作系统提供服务,这是一种特殊的、由运行用户态程序调用操作系统内核过程的形式。

阻塞状态 -> 就绪状态:当进程等待的事件到来时 ,如I/O操作结束或中断结束时,中断处理程序必须把相应进程的状态由阻塞状态转换为就绪状态。

4. 进程号

每个进程都由一个进程号来标识,其类型为 pid_t(整型),进程号的范围:0~32767。进程号总是唯一的,但进程号可以重用。当一个进程终止后,其进程号就可以再次使用。
进程号(PID): 标识进程的一个非负整型数。
父进程号(PPID) : 任何进程( 除 init 进程)都是由另一个进程创建,该进程称为被创建进程的父进程,对应的进程号称为父进程号(PPID)。如,A 进程创建了 B 进程,A 的进程号就是 B 进程的父进程号
进程组号(PGID): 进程组是一个或多个进程的集合。他们之间相互关联,进程组可以接收同一终端的各种信号,关联的进程有一个进程组号(PGID)

getpid函数

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);

功能:
    获取本进程号(PID)
参数:
    无
返回值:
    本进程号

getppid函数

#include <sys/types.h>
#include <unistd.h>
pid_t getppid(void);

功能:
    获取调用此函数的进程的父进程号(PPID)
参数:
    无
返回值:
    调用此函数的进程的父进程号(PPID)

getpgid函数

#include <sys/types.h>
#include <unistd.h>
pid_t getpgid(pid_t pid);

功能:
    获取进程组号(PGID)
参数:
    pid:进程号
返回值:
    参数为 0 时返回当前进程组号,否则返回参数指定的进程的进程组号

案例

#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main(int argc,char *argv[])
{
	printf("进程号的PID:%d\n", getpid());
	printf("父进程的PPID:%d\n", getppid());
	printf("进程组号的PGID:%d\n", getpgid(0));
	while(1);//阻塞 方便终端 验证
	return 0;
}

运行结果:

进程号的PID:1067538
父进程的PPID:403911
进程组号的PGID:1067538

5. 进程的创建

5.1 创建进程fork()

系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成进程树结构模型.

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);

功能:
    用于从一个已存在的进程中创建一个新进程,新进程称为子进程,原进程称为父进程。
参数:
    无
返回值:
    成功:子进程中返回 0,父进程中返回子进程 ID。pid_t,为整型。
    失败:返回-1。
失败的两个主要原因是:
    1)当前的进程数已经达到了系统规定的上限,这时 errno 的值被设置为 EAGAIN。
    2)系统内存不足,这时 errno 的值被设置为 ENOMEM

例子1,父子进程交替执行

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
void test01()
{
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork");
        exit(1);
    } else if (pid == 0) {
        int i = 0;
        while (1) {
            printf("%d this is child process \n",i++);
            sleep(1);
        }
    } else {
        int j = 0;
        while (1)
        {
            printf("%d this is parent process \n",j++);
            sleep(1);
        }
    }
}
int main()
{
    test01();
    return 0;
}

运行结果:

0 this is parent process
0 this is child process
1 this is parent process
1 this is child process
2 this is parent process
2 this is child process
3 this is parent process
3 this is child process
4 this is parent process
4 this is child process

例子2,fork创建的子进程 会复制 父进程资源

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
void test02()
{
    int num = 10;
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork");
        exit(1);
    } else if (pid == 0) {
        num = 1000;
        printf("The child process : num = %d\n",num);
    } else {
        sleep(5); //休眠 保证子进程给num赋值
        printf("The parent process : num  = %d\n",num);
        getchar();//阻塞一下
    }
}
int main()
{
    test02();
    return 0;
}

运行结果:

The child process : num = 1000
The parent process : num = 10

以上程序可见 子进程复制了父进程的空间 子进程和父进程都拥有各自独立的空间,所以子进程之修改了子进程的num
总结: 创建 子进程 那么 子进程 复制父进程的资源 父子进程拥有独立的空间

5.2 创建 子进程的过程

使用fork函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间。 地址空间:  包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号等。 子进程所独有的只有它的进程号,计时器等。因此,使用fork函数的代价是很大的

5.3 fork创建的父子进程 谁先运行?

在fork之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。
注意:读共享 写独立
那么 子进程 复制父进程的资源 父子进程拥有独立的空间**

5.2 创建 子进程的过程

使用fork函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间。 地址空间:  包括进程上下文、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号等。 子进程所独有的只有它的进程号,计时器等。因此,使用fork函数的代价是很大的

5.3 fork创建的父子进程 谁先运行?

在fork之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。
注意:读共享 写独立

  • 3
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值