操作系统的常见进程调度算法
- 时间片轮转调度算法(RR):给每个进程固定的时间片,根据进程到达的先后顺序让进程在单位时间片内执行,执行完成后便调度下一个进程执行,时间片轮转调度不考虑进程等待时间和执行时间,属于抢占式调度.
优点:兼顾长短作业.
缺点:平均等待时间较长,上下文切换较费时.适用于分时系统
模拟代码如下
#pragma once
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<ctype.h>
#define MaxNum 100
typedef struct pcb //定义进程控制块
{
char Name[MaxNum]; //进程名
int arrivetime; //到达时间
int runtime; //运行时间
int wholetime; //固定运行时间
int FinishTime; //完成时间
double WeightTime; //周转时间
double WeightWholeTime; //带权周转时间
char state; //运行后的状态
struct pcb *next;
}PCB;
//全局变量
int N; //实际进程数
double SumWT; //周转时间之和
double SumWWT; //带权周转时间之和
double AverageWT; //平均周转时间
double AverageWWT; //平均带权周转时间
typedef struct //定义队列,封装头结点,指针分别指向队头和队尾
{
PCB *front, *rear;
}queue;
queue *init() //进程队列置空
{
queue *head;
head = (queue*)malloc(sizeof(queue));
head->front = NULL;
head->rear = NULL;
return head;
}
int empty(queue *head) //检验队列是否为空
{
return (head->front ? 0 : 1);
}
queue *append(queue *head, char c[MaxNum], int a, int r, char s) //进程队列入队,往后插入
{
PCB *p;
p = (PCB *)malloc(sizeof(PCB));
strcpy_s(p->Name, c);
p->arrivetime = a;
p->runtime = r;
p->wholetime = r;
p->state = s;
//p->FinishTime=0;
//p->WeightTime=0;
//p->WeightWholeTime=0;
p->next = NULL;
if (empty(head))
head->front = head->rear = p;
else
{
head->rear->next = p;
head->rear = p;
}
return head;
}
queue *creat(queue *head) //创建进程队列
{
char c[MaxNum];
char s = 'R';
int a, r, i;
printf("请输入共有几个进程:\n");
scanf_s("%d", &N);
for (i = 1; i <= N; i++)
{
printf("请输入第%d 个进程的进程名:\n", i);
getchar();
gets(c);
printf("请输入第%d 个进程的到达时间:\n", i);
scanf("%d", &a);
printf("请输入第%d 个进程的服务时间:\n", i);
scanf("%d", &r);
head = append(head, c, a, r, s);
}
return head;
}
void print(queue *head) //输入创建的进程队列
{
PCB *p;
p = head->front;
if (!p)
printf("时间片轮转调度队列为空!\n");
while (p)
{
printf("Name=%s arrivetime=%d runtime=%d state=%c", p->Name, p->arrivetime, p->runtime, p->state);
printf("\n");
p = p->next;
}
}
/*******************时间片轮转法调度算法的实现**************************/
void RR(queue *head, int q)
{
int t = head->front->arrivetime, lt = head->rear->arrivetime;
if (head->front->runtime<q)
t = t + head->front->runtime;
else
t = t + q;
/****进程队列为不空才可调度*****/
while (!empty(head))
{
PCB *p1, *p2;
printf("\n时刻 进程 运行后的状态\n");
/*第一种情况:当前运行的时间小于最后一个进程到达时间做一下操作*/
while (t<lt)
{
p1 = head->front;
printf("%2d %s", t, p1->Name);
p1->runtime = p1->runtime - q;
//1.运行时间小于0,删除队首
if (p1->runtime <= 0)
{
p1->state = 'C';
printf(" %c\n", p1->state);
p1->FinishTime = t;
p1->WeightTime = p1->FinishTime - p1->arrivetime;
p1->WeightWholeTime = p1->WeightTime / p1->wholetime;
SumWT += p1->WeightTime;
SumWWT += p1->WeightWholeTime;
printf("时刻%2d进程%s运行结束,进程%s周转时间=%5.2f,带权周转时间=%5.2f\n", t, p1->Name, p1->Name, p1->WeightTime, p1->WeightWholeTime);
head->front = p1->next;
free(p1);
}
//2.运行时间大于0,向后找位置插入
else
{
printf(" %c\n", p1->state);
p2 = p1->next;
while (p2->next && p2->arrivetime != t)
{
p2 = p2->next;
}
//此时无新进入队列的进程,有两种情况:1.不用找位置往后插入,队首不变,不做操作
//2.找位置往后插入
if (p2->arrivetime != t)
{
PCB *p3 = p1;
PCB *p4 = NULL;
while (p3->next && p3->arrivetime<t)
{
p4 = p3;
p3 = p3->next;
}
if (p3->arrivetime>t)
{
if (p4 != p1) //p1插在p4后,头为p1->next
{
head->front = p1->next;
p1->next = p4->next;
p4->next = p1;
}
else //不做操作
p4 = p3 = p2 = NULL;
}
else
p4 = p3 = p2 = NULL;
}
//此时有新进入队列的进程时:p1插在新进入队列的进程p2后,队首为p1->next
else
{
head->front = p1->next;
p1->next = p2->next;
p2->next = p1;
}
}
//时刻变化
if (head->front->runtime<q)
t = t + head->front->runtime;
else
t = t + q;
}
/*************第一种情况结束**************/
/******************第二种情况:当期运行的时间大于最后一个进程到达的时间做以下操作*********************/
while (t >= lt)
{
p1 = head->front;
printf("%2d %s", t, p1->Name);
p1->runtime = p1->runtime - q;
//1.运行时间小于0,删除队首
if (p1->runtime <= 0)
{
p1->state = 'C';
printf(" %c\n", p1->state);
p1->FinishTime = t;
p1->WeightTime = p1->FinishTime - p1->arrivetime;
p1->WeightWholeTime = p1->WeightTime / p1->wholetime;
SumWT += p1->WeightTime;
SumWWT += p1->WeightWholeTime;
printf("时刻%2d进程%s运行结束,进程%s周转时间=%5.2f,带权周转时间=%5.2f\n", t, p1->Name, p1->Name, p1->WeightTime, p1->WeightWholeTime);
//printf("时刻%2d进程%s运行结束",t,p1->pname);
head->front = p1->next;
free(p1);
}
//2.运行时间大于0,直接插在队尾
else
{
printf(" %c\n", p1->state);
//若原队列只有一个进程,不必往队尾插
if (!p1->next)
head->front = p1;
//若原队列有多个进程
else
{
head->front = p1->next;
head->rear->next = p1;
head->rear = p1;
p1->next = NULL;
}
}
//时刻变化,队列为空时不做时刻变化
if (empty(head))
return;
else
{
if (head->front->runtime<q)
t = t + head->front->runtime;
else
t = t + q;
}
}
/*******第二种情况结束*********/
}
}
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<ctype.h>
void test_rr()
{
queue *head;
int q;
head = init();
head = creat(head);
printf("\n您输入的时间片轮转进程队列为:\n");
print(head);
printf("\n请输入时间片轮转调度的时间片为:");
scanf("%d", &q);
//时间片轮转调度
RR(head, q);
AverageWT = SumWT / N;
AverageWWT = SumWWT / N;
printf("平均周转时间=%5.2f,平均带权周转时间=%5.2f", AverageWT, AverageWWT);
}
- 先来先服务调度算法(FCFS):根据进程到达的先后顺序执行进程,不考虑等待时间和执行时间,会产生饥饿现象.属于非抢占式调度.
优点:公平,实现简单.
缺点:不利于短作业.
模拟代码:
#pragma once
#include <iostream>
#define _n 20
using namespace std;
typedef struct
{
int id; //进程名
int atime; //进程到达时间
int runtime; //进程运行时间
}fcs;
void test_finffun()
{
int amount, i, j, diao, huan;
fcs f[_n];
cout << "请输入进程个数:" << endl;
cin >> amount;
for (i = 0; i<amount; i++)
{
cout << "请输入进程名,进程到达时间,进程运行时间:" << endl;
cin >> f[i].id;
cin >> f[i].atime;
cin >> f[i].runtime;
}
for (i = 0; i<amount; i++) //按进程到达时间的先后排序
{ //如果两个进程同时到达,按在屏幕先输入的先运行
for (j = 0; j<amount - i - 1; j++)
{
if (f[j].atime>f[j + 1].atime)
{
diao = f[j].atime;
f[j].atime = f[j + 1].atime;
f[j + 1].atime = diao;
huan = f[j].id;
f[j].id = f[j + 1].id;
f[j + 1].id = huan;
}
}
}
int count1 = 0;
float count2 = 0;
for (i = 0; i<amount; i++)
{
cout << "进程:" << f[i].id << "从" << f[i].atime << "开始" << "," << "在"
<< f[i].atime + f[i].runtime << "之前结束。" << endl;
f[i + 1].atime = f[i].atime + f[i].runtime;
count1 += (f[i].runtime);
count2 += (f[i].atime + f[i].runtime) / f[i].runtime;
}
cout << "平均周转时间:" << count1 / amount << " " << "平均带权周转时间:" << count2 / amount << endl;
}
- 优先级调度算法(HPF):在进程等待队列中选择优先级最高的来执行.
#pragma once
#define HEAP_SIZE 5
struct Process
{
int key;
int* pointer;//指向进程的入口代码
//更多的其他信息
}process;
void Min_heapify(int *array, int i)
{
int heap_size = array[0];
int l = 0;
int r = 0;
int least = 0;
//此处不使用递归节约时间;
while (i>0){
l = 2 * i;
r = 2 * i + 1;
if (l <= heap_size&&array[l]<array[i])
least = l;
else
least = i;
if (r <= heap_size&&array[r]<array[least])
least = r;
if (least != i){
int temp;
temp = array[i];
array[i] = array[least];
array[least] = temp;
}
i /= 2;
}
}
//=================Build_min_heap===============
/*
此函数是建立以数组array的最小堆;
*/
void Build_min_heap(int* array){
int heap_size = array[0];
for (int i = (heap_size / 2); i>0; i--)
Min_heapify(array, i);
}
//============= Heap_extract_min=============
/*
此函数是返回最小堆的最小的关键字
*/
int Heap_extract_min(int*array){
int min;
int heap_size = array[0];
if (heap_size<1)
printf("heap underflow\n");
min = array[1];
array[1] = array[heap_size];
array[0] -= 1;
Min_heapify(array, 1);
return min;
}
//=========== Heap_prior_increase===============
/*
此函数的作用是增加堆中某个元素的优先值,优先级高的关键字小;
*/
void Heap_prior_increase(int*array, int i, int key){
if (key>array[i] && key<0){
printf("the prior you want to increse cann't be relize\n");
return;
}
array[i] = key;
while (i>1 && array[i / 2]>array[i]){
int temp;
temp = array[i];
array[i] = array[i / 2];
array[i / 2] = temp;
i /= 2;
}
}
//=========== Min_heap_insert=====================
/*
此函数的作用是插入元素;
*/
void Min_heap_insert(int*array, int key){
int heap_size;
array[0] += 1;
heap_size = array[0];
array[heap_size] = -2;
Heap_prior_increase(array, heap_size, key);
}
void test_PSA(){
int test;
int heap_array[HEAP_SIZE + 1] = { 3, 2, 1, 4, -1, -1 };//此处的第一个元素是堆的大小;
Build_min_heap(heap_array);
Heap_prior_increase(heap_array, 3, 3);
printf("\nheap_array:");
for (int i = 0; i<6; i++)
printf("%d ", heap_array[i]);
Min_heap_insert(heap_array, 6);
printf("\nheap_array:");
for (int i = 0; i<6; i++)
printf("%d ", heap_array[i]);
Min_heap_insert(heap_array, 2);
printf("\nheap_array:");
for (int i = 0; i<6; i++)
printf("%d ", heap_array[i]);
test = Heap_extract_min(heap_array);
printf("\nHeap_extract_min=%d \n", test);
}
- 多级反馈队列调度算法:将时间片轮转和优先级调度相结合,吧进程按优先级分为不同的队列,先按优先级调度,优先级相同的,按照时间片轮转.优点:兼顾长短作,有较好的响应式案件,可行性强,适用于各种作业环境.
- 高响应比优先调度算法:根据”响应比=(进程执行时间+进程等待时间)/进程执行时间”这个公式得到的响应比来进程调度.高响应比优先算法在等待时间相同的情况下,作业执行的时间越短,响应比越高,满足短任务优先,同时响应比会随着等待时间增加而增大,优先级会提高,能够避免饥饿现象.优点兼顾长短作业.缺点:计算相应比开销大,适用于批处理系统.
使用代码模拟实现僵尸进程, 孤儿进程的场景.
孤儿进程:父进程先退出,子进程还没有退出这种场景就称为”孤儿进程”,而管理孤儿进程的任务就落在了init进程上.内核把孤儿进程的父进程变为init进程,init进程管理孤儿进程的善后工作.这样,当一个孤儿进程凄凉的结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作.因此孤儿进程并没有什么危害.
孤儿进程测试程序:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
pid_t pid;
pid=fork();
if(pid>0)
{
printf("fathrt pid=%d ppid=%d",getpid(),getppid());
printf("father is exit pid=%d ppid=%d",getpid(),getppid());//父进程先退出
}
else if(pid==0)
{
printf("child pid=%d ppid=%d",getpid(),getppid());
sleep(5);//子进程5s之后继续执行,保证父进程先退出
printf("child is exitpid=%d ppid=%d",getpid(),getppid());
}
else
{
perror("fork error");
}
return 0;
}
僵尸进程:父进程创建子进程之后,子进程将结果放入内核,父进程不去管理资源,子进程的结果将一直占用内核的一块存储.
危害:系统所能使用的进程号是限的,如果缠上大量的僵尸进程,将没有可用的进程号而导致系统不能产生新的进程,甚至导致系统崩溃.
有可能导致危害的场景:有一个进程,定期的产生一个子进程,这个子进程需要做的事情非常少,做完子进程的任务之后就退出,因此这个子进程的生命周期很短,但是,父进程值负责生成新的子进程,至于子进程退出之后的一切事务,一概不闻不问.这样,系统在运行一段时间之后某系统中就会存在很多僵尸进程,用”ps”命令查看,就会看到许多状态为”Z”的进程.僵尸进城并不是问题的根源,根源是产生大量僵尸进程的那个父进程.因此如何小米大量的僵尸进程只需要枪毙那个产生僵尸进程的父进程就好.通过kill发送SIGTERM.其产生的僵尸进城就变成了孤儿进程,孤儿进程如上文,被init进程接手,释放它们占用的系统进程表中的资源.
僵尸进程测试程序:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
pid_t pid;
pid=fork();
if(pid>0)
{
printf("fathrt pid=%d ppid=%d",getpid(),getppid());
sleep(5);
printf("father is exit pid=%d ppid=%d",getpid(),getppid());//父进程先等待
}
else if(pid==0)
{
printf("child pid=%d ppid=%d",getpid(),getppid());
sleep(1);//子进程退出之后直接exit,成为僵尸进程
printf("child is exitpid=%d ppid=%d",getpid(),getppid());
exit(0);
}
else
{
perror("fork error");
}
return 0;
}
setenv, export等环境变量相关的函数和命令.
getenv:从环境变量中取字符串,获取环境变量的值
putenv:此函数并不能添加或修改shell进程的环境变量,或者说通过setenv函数设置的环境变量只在本进程有效,且只在本次执行中有效,一旦某一次运行程序时执行了setenv函数,进程终止后再吃运行该程序,上次的设置是无效的,上次设置的环境变量是不可读到.
如果想要环境变量永久生效可以修改下面两个文件中的任何一个:
1. /etc/profile
2. .bash_profile
/etc/profile是全局有效的,而bash_profile只对当前用户起作用.
设置完毕,注销并重新登录,设置就生效了
注:export设置只对当前的bash登录session有效,这是存在内存里面的.