Linux(高级编程)1————进程概念

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/FangXiaXin/article/details/82874455

何为进程?
进程的典型定义:
1.进程是程序的一次执行。
2.进程是一个程序及其数据在处理机上顺序执行时所发生的活动。
3.进程是具有独立功能的程序在数据集合上运行的过程,他是系统进行资源分配和调度的一个独立单位。
进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。
进程 = 程序段+数据段+PCB(程序控制块)
那么进程有什么特征呢?
1.进程具有动态性。在这一点我们可以从进程状态可以看出,进程从产生–>执行->消亡的过程其实就是一个动态的过程。
2.进程具有并发性。是指多个进程同时存在内存中,且在一段时间内同时进行(是宏观上的,因为一个处理机同一时刻只能处理一个进程)。
3.进程具有独立性。在传统的OS中,独立性是指进程实体是一个独立运行、独立获得资源和独立接受调度的基本单位。
4.进程具有异步性。指进程是按异步方式运行。
上面对进程定义及特点说了一大堆,但我们似乎并没有意识到进程到底是个什么东西?那么我们来看一些东西实实在在感受一下进程面貌。
1.进程描述
在操作系统,进程是用一个结构体描述的(PCB),而在Linux操作系统中我们用的是task_struct,PCB的一种。
接下来我们来看一下task_struct中都有一些什么东西呢(核心成员)?

  • 1.进程描述符(身份标识):pid(进程ID)
    a.内部表示符:在所有的操作系统中,都为每一个进程赋予了一个惟一的数字标识符,它通常是一个进程的序号。设置内部标识符主要是为了方便系统使用。
    b.外部标识符:它由创建者提供,通常是由字母,数字组成,往往是由用户(进程)在访问该进程时使用。为了描述进程的家族关系,还应设置父进程标识和子进程标识。此外,还可设置用户标识,以指示拥有该进程的用户。

  • 2.一组内存指针:代码和代码依赖的数据(告诉进程对应的代码和代码依赖的数据)。

  • 3.辅助操作系统进行调度的属性
    a.优先级
    b.上下文信息(CPU中的各种寄存器:通用寄存器、指令寄存器、程序状态字PSW、用户栈指针):保存该进程上次在CPU上执行的现场。
    c.记账信息(进程切换时)指令执行的数量。
    d.进程状态

  • 4.IO相关信息(文件描述符表)。

  • 5.信号相关的信息(后面详细解释)

2.进程的组织
进程通过PCB描述后,通过双向链表组织起来。
3.如何创建一个进程?

  • fork()函数,用来创建子进程
    特点:
    1.子进程复制父进程以父进程为模板(写时拷贝)。
    2.把父进程的PCB复制过来并稍加修改(pid/ppid)。
    3.内存指针基本相同,上下文也相同(EIP)。
    4.父子进程执行同一份代码。
    5.EIP相同,所以fork之后,父子进程都要从fork之后开始执行。
    fork返回的信息:
    1.父进程返回子进程ID。
    2.子进程返回0。
    3.fork执行失败,返回-1。
  • 失败原因:

1.内存不够。
2.子进程数目达到上限。
fork之后,父子进程执行的先后顺序取决于操作系统的调度。

  • vfork()函数
    vfork是linux前期的进城创建函数,其缺点比较多。
    特点:
    1.父子进程共享进程地址空间。
    2.父子进程执行顺序固定,子进程先执行父进程后执行。

4.进程状态:
1.创建状态:进程在创建时需要申请一个空白PCB,向其中填写控制和管理进程的信息,完成资源分配。如果创建工作无法完成,比如资源无法满足,就无法被调度运行,把此时进程所处状态称为创建状态

2.就绪状态:进程已经准备好,已分配到所需资源,只要分配到CPU就能够立即运行

3.执行状态:进程处于就绪状态被调度后,进程进入执行状态

4.阻塞状态:正在执行的进程由于某些事件(I/O请求,申请缓存区失败)而暂时无法运行,进程受到阻塞。在满足请求时进入就绪状态等待系统调用

5.终止状态:进程结束,或出现错误,或被系统终止,进入终止状态。无法再执行

  • 进程状态转换:

在这里插入图片描述

  • Linux内核关于进程状态的描述:
    在这里插入图片描述
    僵尸进程:
    1.成因1:子进程执行完了,父进程没有做一些特殊处理。
    2.危害:PCB没有释放(内存泄露)。
    3.处理:kill掉某个进程,发送一个特定信号就能完成进程销毁。kill -9 pid杀死进程组。
    直接杀死僵尸进程是杀不掉的,但可以杀掉(kill 父进程)父进程和僵尸进程都销毁。
    4.成因2:僵尸进程是子进程向父进程汇报工作结果的一种机制。在父进程查看结果前,这样的结果不应该被释放掉,结果仍保存子进程的PCB中,父进程获取子进程结果的方式“进程等待” 。
    孤儿进程
    父进程结束,子进程没有结束成为孤儿进程。孤儿进程的父进程为1号进程init,1号进程会释放孤儿进程。
    —————————————————————————————————————————
    fork使用样例:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>

int main(void)
{
	pid_t pid = 0;
	int i = 1;
	if((pid = fork())<0)
	{
		printf("fork error! i = %d\n",i);
		exit(0);
	}
	if(pid == 0)
	{
		printf("child! i = %d\n",i);
	}
	else
	{
		i = 2;
		printf("father! i = %d\n",i);
	}
	return 0;
}

运行结果:
在这里插入图片描述
通过运行结果我们可以验证:父子进程有各自的地址空间。接下来再对比一下vfork();
vfork函数样例:

#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<stdio.h>
int main(void)
{
	pid_t pid = 0;
	int i = 1;
	if((pid = vfork())<0)
	{
		printf("vfork error! i = %d\n",i);
		exit(0);
	}
	if(pid >  0)
	{
		printf("father! i = %d\n",i);
	}
	else 
	{
		i = 2;
		printf("child! i = %d\n",i);
		exit(1);
	}
	return 0;
}

运行结果:
在这里插入图片描述
我们可以清楚的看到:1.子进程先运行,父进程后运行。2.子进程对i进行更改,父进程的值也发生了变化,说明父子进程共享进程地址空间。
创建一个僵尸进程实例:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main(void)
{
	pid_t pid;
	if((pid = fork())<0)
	{
		printf("fork error!\n");
		exit(0);
	}
	if(pid > 0)
	{
		while(1);//父进程死循环
	}
	else
	{
		printf("child ID = %d\n",getpid());//父进程没有wait子进程,子进程退出。
		exit(0);
	}
}

运行结果:
在这里插入图片描述
在这里插入图片描述
可以看到子进程退出,父进程没有为子进程收尸,导致子进程变为僵尸进程。
kill掉父进程再查看还有没有僵尸进程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们可以看到父进程杀死后,僵尸进程也随之释放。
孤儿进程实例:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main(void)
{
	pid_t pid;
	if((pid = fork())<0)
	{
		printf("fork error!\n");
		exit(0);
	}
	if(pid > 0)
	{
		exit(1);//父进程退出
	}
	else
	{
		printf("child ID = %d,father ID = %d\n",getpid(),getppid());
		while(1);//子进程死循环
	}
}

运行结果:
在这里插入图片描述
子进程在父进程退出后仍在运行,子进程成为孤儿进程,父进程变为1号进程。

猜你喜欢

转载自blog.csdn.net/FangXiaXin/article/details/82874455