实验目的
加深对进程调度概念的理解,体验进程调度机制的功能,了解Linux系统中进程调度策略的使用方法。 练习进程调度算法的编程和调试技术。
实验说明
1.在linux系统中调度策略分为3种
- SCHED_OTHER:默认的分时调度策略,值为0
- SCHED_FIFO:先进先出调度,值为1
- SCHED_RR:轮转法调度,值为2
理解关键点:
- 不同调度策略之间本身的优先级不同,FIFO调度策略优先级最高,其次是轮转法调度法,最后是默认分时调度策略(FIFO的所有程序相比于分时程序优先级更高)
- 分时调度策略的优先级指的是动态优先级。动态优先级=nice值+进程运行时间值来决定的。对于FIFO、RR来说只是决定nice值的方式不一样
2.设置进程调度策略的系统调用语法
int sched_setscheduler(pid_t pid,int policy,const struct sched_param *sp);
- pid:进程号
- policy:三种调度策略之一
- sp:调度参数结构指针,里面有调度优先数
理解关键点:
- 设置进程的调度策略时,输入的参数肯定有:进程标识pid、调度策略policy。同时考虑到确定进程调度策略后要马上根据进程优先级来确定进程实际的执行次序,所以在设置进程调度策略时需要传递进程优先级
- sched_setscheduler函数在sched.h头文件当中
- 执行成功返回0
3.获取进程调度策略的系统调用语法
int sched_getscheduler(pid_t pid);
- pid 进程号
- 返回值: 进程当前的调度策略对应的数值
4.获取进程动态优先数的系统调用语法
int getpriority(int which,int who);
理解关键点:
1、which代表设置的对象是什么类型的(进程、进程组、用户)
2、which的值可以是:
- 进程 PRIO_PROCESS
- 进程组 PRIO_PGRP
- 用户 PRIO_USER
3、who的值是:设置对象类型下的标号(进程号或组号或用户号)
4、返回值:所有匹配进程中的最高优先级(本次实验是查找进程pid,所以结果只有一个)
5.设置进程动态优先数的系统调用语法
int setpriority(int which,int who,int prio);
理解关键点:
1、which代表设置的对象,which的值可以是
- 进程 PRIO_PROCESS
- 进程组 PRIO_PGRP
- 用户 PRIO_USER
2、who表示对应查找对象集合中的具体实例
3、prio设置的是要设置的进程优先级
4、返回值:所有匹配进程中的最高优先级
实例程序
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <sys/time.h>
#include <sys/resource.h>
int main(int argc, char *argv[])
{
int i,j,status;
int pid[3]; //存放进程号
struct sched_param p[3]; //设置调度策略时使用的数据结构
for(i=0; i<3;i++)
{
//循环创建3 个子进程
if((pid[i]=fork()) >0)
{
//取进程优先数放在调度策略数据结构中
p[i].sched_priority = (argv[i+1] != NULL) ? atoi(argv[i+1]):10;
//父进程设置子进程的调度策略.如果命令行第4,5,6 参数指定了3个策略值则按指定的数设置,否则都为默认策略
sched_setscheduler(pid[i],(argv[i+4] != NULL) ? atoi(argv[i+4]) : SCHED_OTHER,&p[i]);
//父进程设置子进程的优先数,如果命令行第1,2,3 参数指定了3个优先数则按指定的数设置,否则都为10
setpriority(PRIO_PROCESS,pid[i],(argv[i+1] != NULL) ? atoi(argv[i+1]):10);
}
//各子进程循环报告其优先数和调度策略
else
{
sleep(1);
//每隔1 妙报告一次进程号和优先级
for(i=0; i<10; i++)
{
printf("Child PID = %d priority = %d\n",getpid(),getpriority(PRIO_PROCESS,0));
sleep(1);
}
exit( EXIT_SUCCESS);
}
}
//父进程报告子进程调度策略后先行退出
printf("My child %d policy is %d\n",pid[0],sched_getscheduler(pid[0]));
printf("My child %d policy is %d\n",pid[1],sched_getscheduler(pid[1]));
printf("My child %d policy is %d\n",pid[2],sched_getscheduler(pid[2]));
return EXIT_SUCCESS;
}
运行结果
结果解释
1、父进程先运行结束:父进程并未用wait等待子进程。同时对子进程使用sleep函数来休眠,所以父进程一定先运行结束并汇报出子进程的调度策略
2、优先数小的进程先执行:按照正常逻辑推理应该是优先数小的进程先执行,但是对于现在的多核CPU来说不同优先级的三个子进程仍然几乎可以说是在并行的(不是并发),所以实际效果是三进程顺序不太固定。但是可以看到优先级为18的进程不可能是第一个被调度的,说明优先级仍然在影响对CPU资源的占用
3、进程调度策略的修改:由于没有开sudo管理员权限,所以不允许对进程的调度策略进行修改
本次实验代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sched.h>
#include <sys/time.h>
#include <sys/resource.h>
static int priority1=0;
static int priority2=0;
//加信号,只对子进程
void add(int sig)
{
priority2++;
}
//减信号,只对子进程
void minus(int sig)
{
priority2--;
}
int main(int argc, char *argv[])
{
//绑定信号
signal(SIGINT,add);
signal(SIGTSTP,minus);
//定义数组存储父子进程的pid值
int pid[2];
//父进程运行
if((pid[1]=fork())>0)
{
pid[0] = getpid();
struct sched_param p[2];
//得到argv参数
int priority1=atoi(argv[1]);
int priority2=atoi(argv[2]);
//父进程初始化父子进程优先级以及调度策略
for(int i=0; i<2;i++){
//取进程优先数放在调度策略数据结构中
p[i].sched_priority = (argv[i+1] != NULL) ? atoi(argv[i+1]):10;
//父进程设置自己和子进程的调度策略
sched_setscheduler(pid[i],(argv[i+3] != NULL) ? atoi(argv[i+3]):SCHED_OTHER,&p[i]);
//父进程设置自己和子进程的优先数
setpriority(PRIO_PROCESS,pid[i],(argv[i+1] != NULL) ? atoi(argv[i+1]):10);
}
//父进程循环调整优先级
int parentPid=getpid();
while(1)
{
//输出进程号
printf("parent pid = %d\n",parentPid);
//输出优先级
printf("parent process priority = %d\n",getpriority(PRIO_PROCESS,parentPid));
//输出调度策略
printf("parent process scheduler = %d\n",sched_getscheduler(parentPid));
//设置进程的优先级
setpriority(PRIO_PROCESS,parentPid,priority1);
sleep(5);
}
}
else
{
int childPid=getpid();
while(1)
{
//输出进程号
printf("child pid = %d\n",childPid);
//输出当前运行进程的优先级(也就是子进程的优先级)
printf("child process priority = %d\n",getpriority(PRIO_PROCESS,0));
//输出调度策略
printf("child process scheduler = %d\n",sched_getscheduler(childPid));
//设置进程的优先级
setpriority(PRIO_PROCESS,childPid,priority2);
sleep(5);
}
}
}
运行结果
结果分析
1、一开始设定父进程优先级为6,子进程为4。打入一个中断后,父子进程中便会是子进程先运行。(中断会让所有进程都去执行信号处理函数,然后再统一开始顺序执行进程)
2、后续利用自定义add方法能够让子进程的优先级变为8,此时大部分情况下,会先调度父进程后调度子进程(如何错乱了利用中断信号可以恢复正确的顺序)
3、有时候进程调度的顺序不是完全按照设定优先级进行的,为了防止部分进程恶意抢占所有资源,并且其他的进程随着时间也会增加静态优先级,从而获得更高的优先级
makefile文件
# DEPEND 代替 依赖文件
# CC 代替 gcc
# CFLAGS 代替 编译命令
# PARA 代替 参数
# OBJS 代替 目标文件
DEPEND=expr_3.c
OBJS=expr_3
CC=gcc
CFLAGS=-o
PARA=6 4 0 0
expr_3:$(DEPEND)
$(CC) $(DEPEND) -o $(OBJS)
run:$(OBJS)
./$(OBJS) $(PARA)
clean:
rm *.o $(OBJS) -rf
总结
本文到这里就结束啦~~
本篇文章重点在于利用linux系统的完成操作系统的实验,巩固课堂知识
本篇文章的撰写+实验代码调试运行+知识点细致化学习,共花了本人3h左右的时间
如果仍有不够希望大家多多包涵~~如果觉得对你有帮助,辛苦友友点个赞哦~
知识来源:山东大学操作系统实验三、山东大学《操作系统原理实用实验教程》张鸿烈老师编著