程序员成长之旅 ——进程概念

进程和程序的区别

程序

完成特定任务一系列指令集合

单道程序: CPU只能一次执行一个程序
多程序设计:CPU执行多个程序,每个程序分为几段错开执行

进程

每个进程都有自己的状态和独立的地址空间

从用户角度看:进程是程序的一次动态执行过程
分时系统:时间片轮转
从操作系统:进程是操作系统分时的基本单位(最小单位)

区别

程序:数据+代码
进程:数据+代码+堆栈+PCB
PCB:进程控制块(Process.Control.Block)Linux下的PCB是task_struct(每个进程有一个PCB表示)---- 用链表存储

并发:多个进程在一个cpu下采用进程切换的方式,在一段时间之内让多个进程得以推进。
并行:多个进程在多个CPU下分别.同时进行。

  1. 进程是动态的,程序是静态的;
  2. 进程的生命周期是短暂的,而程序相对永久;
  3. 进程有重要的数据结构PCB;
  4. 一个进程只能对应一个程序,而一个程序可以对应多个进程。

进程的进程调度算法

  1. 时间片轮转调度算法(RR): 给每个进程固定的执行时间,根据进程到达的先后顺序让进程在单位时间片内执行,执行完成后便调度下一个进程执行,时间片轮转调度不考虑进程等待时间和执行时间,属于抢占式调度。优点是兼顾长短作业;缺点是平均等待时间较长,上下文切换较费时。适用于分时系统。

  2. 先来先服务调度算法(FCFS):根据进程到达的先后顺序执行进程,不考虑等待时间和执行时间,会产生饥饿现象。属于非抢占式调度,优点是公平,实现简单;缺点是不利于短作业。

  3. 优先级调度算法 (HPF):在进程等待队列中选择优先级最高的来执行。

  4. 多级反馈队列调度算法:将时间片轮转与优先级调度相结合,把进程按优先级分成不同的队列,先按优先级调度,优先级相同的,按时间片轮转。优点是兼顾长短作业,有较好的响应时间,可行性强,适用于各种作业环境。

  5. 高响应比优先调度算法:根据“响应比=(进程执行时间+进程等待时间)/ 进程执行时间”这个公式得到的响应比来进行调度。高响应比优先算法在等待时间相同的情况下,作业执行的时间越短,响应比越高,满足段任务优先,同时响应比会随着等待时间增加而变大,优先级会提高,能够避免饥饿现象。优点是兼顾长短作业,缺点是计算响应比开销大,适用于批处理系统。

在这里插入图片描述
在这里插入图片描述

调研task_struct结构体, 理解结构体中的各个字段的含义

  • 在Linux中描述进程的结构体叫task_struct
  • task_struct是Linux内核中的一种数据结构,它会被装载到RAM(内存)中并包含进程的信息。

task_struct都可能包含哪些成员的信息?

  1. 进程状态,记录进程在等待,运行或死锁
  2. 调度信息,由那个函数调度,怎样调度等
  3. 进程的通讯状态
  4. 因为要插入进程树,必须有联系父子兄弟的指针,当然是task_struct型
  5. 时间信息,比如计算好执行的时间 以便CPU分配
  6. 标号,决定进程归属
  7. 可以读写打开的一些文件信息
  8. 进程上下文和内核上下文
  9. 处理上下文
  10. 内存信息

因为每一个pcb都是这样的,只有这些结构,才能满足一个进程的所有要求。

进程状态

  1. volatile long state;
  2. int exit_state

state成员的可能取值如下:

#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
/* in tsk->exit_state */
#define EXIT_ZOMBIE 16
#define EXIT_DEAD 32
/* in tsk->state again */
#define TASK_DEAD 64
#define TASK_WAKEKILL 128
#define TASK_WAKING 256

系统中的每个进程都必然处于以上所列进程状态中的一种。

对上述信息进行简要描述:

      TASK_RUNNING : 表示进程要么正在执行,要么正要准备执行。

TASK_INTERRUPTIBLE : 表示进程被阻塞(睡眠),直到某个条件变为真。条件一旦达成,进程的状态就被设置为TASK_RUNNING。

TASK_UNINTERRUPTIBLE: 意义与TASK_INTERRUPTIBLE类似,除了不能通过接受一个信号来唤醒以外。

      __TASK_STOPPED: 表示进程被停止执行。

      __TASK_TRACED : 表示进程被debugger等进程监视。

        EXIT_ZOMBIE : 表示进程的执行被终止,但是其父进程还没有使用wait()等系统调用来获知它的终止信息。

          EXIT_DEAD :表示进程的最终状态。

EXIT_ZOMBIE和EXIT_DEAD也可以存放在exit_state成员中。

代码模拟实现孤儿和僵尸进程的场景

进程(id):PID
父进程(id):PPID

僵尸进程

什么是僵尸进程?
简而言之就是子进程先退出,父进程没有接收到子进程退出时的返回代码,进而形成僵尸进程。(Z状态)

僵尸进程的危害

  • 退出状态本身就是要用数据去维护的,也属于进程信息,所以保存在task_struct(PCB)中,那么z状态一直不退出,pcb就要一直维护
  • 当一个父进程创建了很多子进程,但却一直没有回收,这样会造成内存资源的浪费。(因为数据结构本身就要占用内存)
  • 如果父进程不去回收子进程,还会造成内存泄露。

怎样避免僵尸进程?

  • 父进程通过wait和waitpid等函数等待子进程的结束,这样会导致父进程挂起
  • 如果父进程不关心子进 程什么时候结束,那么可以用signal(SIGCHLD,SIG_IGN) 通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收, 并不再给父进程发送信号。
  • 倘若父进程非常忙,那么可以用singal函数为SIGCHLD安装handler(因为子进程结束后,父进程会收到该信号,可以在handler中调用wait回收)
  • fork两次,父进程fork一个子进程,然后继续工作,子进程fork一个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收还是需要自己做。

代码场景

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
	pid_t id = fork();
	if(id < 0)
	{
		perror("fork");
		return 1;
	}
	else if(id > 0 )
	{
		//father
		printf("father [%d] is sleeping...\n",getpid());
		sleep(30);
	}
	else
	{
		printf("child [%d] is begin Z...\n",getpid());
		sleep(5);
		exit(EXIT_SUCCESS);
	}
	return 0;
}

子进程五秒后进入僵尸状态
在这里插入图片描述
孤儿进程

什么是孤儿进程?
孤儿进程就是在其父进程执行完成或被终止后仍然继续运行的一类进程。这些孤儿进程将会被init进程(进程号为1号)所收养,并由init进程对它们完成状态收集工作。因为有init进程会循环的wait()已经退出的子进程,所以孤儿进程并不会产生什么危害。

代码

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
	pid_t id = fork();
	if(id < 0)
	{
		perror("fork");
		return 1;
	}
	else if(id == 0)
	{
		//child
		printf("I am child,pid : %d\n",getpid());
		sleep(10);
	}
	else
	{
		//parent
		printf("I am parent,pid : %d\n",getpid());
		sleep(3);
		exit(0);
	}
	return 0;
}

看到结果,父进程3秒后退出,子进程变成孤儿进程
在这里插入图片描述

setenv, export环境变量相关的函数和命令

setenv

相关的函数

作用:增加或者修改环境变量
注意:通过此函数并不能添加或修改 shell 进程的环境变量,或者说通过setenv函数设置的环境变量只在本进程,而且是本次执行中有效。如果在某一次运行程序时执行了setenv函数,进程终止后再次运行该程序,上次的设置是无效的,上次设置的环境变量是不能读到的。
头文件:#include<stdlib.h>
注:stdlib.h在Linux和Windows中略不同,比如setenv函数是用在linux中的,在Windows中没有setenv函数而用putenv来代替
函数声明:int setenv(const char *name,const char * value,int overwrite);
函数说明:setenv()用来改变或增加环境变量的内容。参数name为环境变量名称字符串。参数 value则为变量内容,参数overwrite用来决定是否要改变已存在的环境变量。如果没有此环境变量则无论overwrite为何值均添加此环境变量。若环境变量存在,当overwrite不为0时,原内容会被改为参数value所指的变量内容;当overwrite为0时,则参数value会被忽略。返回值 执行成功则返回0,有错误发生时返回-1。
相关函数:getenv,putenv,unsetenv

命令
Linux中的功能:查询或显示环境变量
语法:setenv [变量名称] [变量值]
setenv用于在C shell设置环境变量的值
用法:setenv ENVVAR value
ENVVAR 为所要设置的环境变量的名。value为所要设置的环境变量的值
例:setenv PATH "/bin:/usr/bin:usr/sbin:"设置环境path的搜索路径为/bin,/usr/bin以及/usr/sbin

export

Linux中的功能:设置或显示环境变量(比如我们要用一个命令,但这个命令的执行文件不在当前目录,这样我们每次用的时候必须制定执行文件的目录,麻烦,在代码中先执行export,这个相当于告诉程序,执行某某东西时,需要的文件或什么东西在这些目录里)
说明:在shell中执行程序时,shell会提供一组环境变量。export可新增,修改或删除环境变量,供后续执行的程序使用。export的效力仅及于该次登陆操作。
语法:export [-fnp] [变量名称] = [变量设置值]
参数说明:

  • -f  代表[变量名称]中为函数名称。
  • -n  删除指定的变量。变量实际上并未删除,只是不会输出到后续指令的执行环境中。
  • -p  列出所有的shell赋予程序的环境变量。
    延伸:export设置环境变量是暂时的,只在本次登录中有效,可修改如下文件来使命令长久有效。

注意:
1、执行脚本时是在一个子shell环境运行的,脚本执行完后该子shell自动退出;
2、一个shell中的系统环境变量才会被复制到子shell中(用export定义的变量);
3、一个shell中的系统环境变量只对该shell或者它的子shell有效,该shell结束时变量消失(并不能返回到父shell中)。
4、不用export定义的变量只对该shell有效,对子shell也是无效的。

发布了76 篇原创文章 · 获赞 16 · 访问量 4428

猜你喜欢

转载自blog.csdn.net/wuweiwuju___/article/details/102702800
今日推荐