Process Scheduling 进程调度
进程调度器(或者简单地说是调度程序)是内核的组件,它选择接下来要运行的进程。在决定哪些进程可以运行,什么时候运行时,调度器负责 支持最大限度地利用处理器,同时提供多个进程并行和无缝执行的错觉。
多任务操作系统有两种变体:协作式cooperative和先发制人式preemptive。
进程在调度程序抢占之前允许运行的时间长度,称为进程的时间间隔timeslice。
I/O - Versus Processor - Bound Processes
持续消耗所有可用时间的进程被认为是processor - bound。
最简单的例子是无限循环:
//100% processor-bound
while(1)
;
其他较不极端的例子包括科学计算、数学计算和图像处理。
与执行相比,花费更多时间阻塞等待某些资源的进程被认为是I/O - bound。
许多GUI应用程序,它们花费大量时间等待用户输入。
processor-bound的应用程序需要最大的时间点,允许它们最大限度地缓存hit rate(通过时间局部性),并尽可能快地完成他们的作业。
I/O-bound进程不一定需要大量时间,因为在发出I/O请求和阻塞某些内核资源之前,它们通常只运行很短的时间。
应用程序可以在阻塞和调度更多I/O请求之后重新启动的更快,那么就可以更好的是利用系统的硬件。此外,如果应用程序正在等待用户输入, 时间安排得越快,用户对无缝执行的感知就越强。
Preemptive Scheduling 抢先调度
在传统的UNIX进程调度中,所有可运行的进程都被分配了一个时间片。当一个进程耗尽它的timeslice时,内核将暂停它,并开始运行一个不同的进程。
系统上有更高优先级的进程-低优先级进程只能等待高优先级进程耗尽它们的时间或阻塞。
这种行为制定了Unix调度的一个重要但默契的规则:所有进程都必须向前推进。
The Completely Fair Scheduler
完全公平调度器(CFS)与传统的Unix进程调度程序有很大的不同。
在大多数Unix系统中,包括CFS推出之前的Linux,进程调度中有两个基本的进程变量:优先级和时间。
CFS引入了一种完全不同的算法,称为公平调度fair shecduling,它消除了时间片作为分配给处理器的访问权限的单元。
CFS为每个进程分配处理器时间的一部分,而不是时间间隔:
- CFS首先给N个进程分配1/N的处理器时间。
- CFS然后通过用每个进程的nice value加权每个进程的占用处理器时间的比重。nice value为零的进程的权重为1,因此它们的比例不变。具有较小的nice value(高优先级)的进程接收较大的权重,增加了占用处理器的时间比重,而具有较大的nice value(较低优先级)的进程则减少了占用处理器的时间比重。
- 为了确定每个进程运行的实际时间长度,CFS需要将比重划分成一个固定时间。这个时间被称为target latency,表示系统调用的latency。
CFS引入了第二个关键变量,最小粒度the minimum granularity。
The minimum granularity是任何进程运行的时间长度的最小单元。
Yielding the Processor
虽然linux是一个先发制人的多任务操作系统,但它也提供了一个系统调用,允许进程显式地产生执行并指示调度程序选择一个新进程执行。
#include <sched.h>
int sched_yield(void);
对schedyfield()的调用导致当前正在运行的进程暂停,之后进程调度器选择要运行的新进程,
if (sched_yield ())
perror ("sched_yield");
Legitimate Uses
实际上,在适当的先发制人多任务处理系统(如Linux)上,sched_yield()很少有合法的使用。内核完全有能力自己处理这个调度,并且性能更好。
那么为什么POSIX要由这么以分系统调用呢?
答案在于应用程序必须等待外部事件,这些事件可能是由用户、硬件组件或其他进程引起的。
/* the consumer... */
do {
while (producer_not_ready ()){
sched_yield ();
}
process_data ();
} while (!time_to_quit ());
值得庆幸的是,UNIX程序员不倾向于编写这样的代码。UNIX程序通常事件驱动,并倾向于在消费者之间使用某种可阻止的机制(如管道)。 和代替SCHED_LEVENT()的生产者。
Process Priorities
nice value 的范围是[-20, 19] 默认值是0。
nice value 的值越小, 优先级越高,时间片越大。
nice value 的值越大, 优先级越小,时间片越小。
Linux提供了几个系统调用,用于检索和设置进程的nice value。
最简单的是nice():
#include <unistd.h>
int nice(int inc);
对nice()的成功调用将由inc递增进程的nice value,并返回新更新的值。
也就是如果inc为正,那么就是在降低优先级,r如果inc为负,则是在增加优先级。
错位的时候返回-1。因为-1也是nice的一个成功调用,所以要检测错误的话就得像下面这么用。
int ret;
errno = 0;
ret = nice(10);
if(ret == -1 && errno != 0)
perror("nice")
else
printf("nice value is now %d\n", ret);
传值0给nice()是一个很简单的获取当前进程nice value的方法。
printf("nice value is currently %d\n", nice(0));
int ret, val;
/*get current nice value*/
val = nice (0)
/*we want a nice value of 10*/
val = 10 - val;
errno = 0;
rer = nice(val);
if(ret == -1 && errno != 0)
perror("nice")
else
printf("nice value is now %d\n", ret)
getpriority() and setpriority()
最好好的解决方案是使用getpriority()和setpriority()系统调用,它允许更多的控制,但在操作上更复杂:
#include <sys/time.h>
#include <sys/resource.h>
int getpriority(int which, int who);
int setpriority(int which, int who, int prio);
参数which有如下参数选项:
- PRIO_PROCESS
- PRIO_PGRP
- PRIO_USER
参数who有就是对相应上面which参数的进程ID, 进程组ID, 用户ID。
who是0的时候,就表示处理的是当前的进程ID,进程组ID,或用户ID
参数prio通nice()的参数。错误的处理方式也同nice。
Processor Affinity
一旦进程被调度在一个CPU上,进程调度程序应该在将来将它调度在同一个CPU上。这是有益的,因为将进程从一个处理器迁移到另一个处理器是有代价的。
有时,用户或应用程序希望实施进程到处理器的绑定。这通常是因为该过程是强缓存敏感的,并且希望保留在同一处理器上。
将进程连接到特定处理器并让内核强制执行关系称为设置hard affinity。
sched_getaffinity() and sched_setaffinity()
Linux提供了两个系统调用,用于检索和设置进程的hard affinity:
#include _GNU_SOURCE
#include <sched.h>
typedef struct cpu_set_t;
size_t CPU_SETSIZE;
void CPU_SET (unsigned long cpu, cpu_set_t *set);
void CPU_CLR (unsigned long cpu, cpu_set_t *set);
int CPU_ISSET (unsigned long cpu, cpu_set_t *set);
void CPU_ZERO (cpu_set_t *set);
int sched_setaffinity (pid_t pid, size_t setsize,
const cpu_set_t *set);
int sched_getaffinity (pid_t pid, size_t setsize,
cpu_set_t *set);
如果pid为0时,调用将检索当前进程的affinity。
cpu_set_t set;
int ret , i;
CPU_ZERO(&set);
ret = sched_getaffinity(0, sizeof(cpu_set_t), &set);
if(ret == -1)
perror("sched_getaffinity");
for(int i = 0; i < CPU_SETSIZE; ++i){
int cpu;
cpu = CPU_ISSET(i, &set);
printf("cpu=%i is %s\n, i , cpu ? "set" : "unset");
}
我们使用CPU_ISSET检查系统中的给定处理器 i 是否绑定到此进程。
测试结果我们只关注CPU#0,#1, #2, #3,因为它们是这个系统上唯一的物理处理器。也许我们希望确保我们的进程只在CPU#0上运行,而不是在其他三个处理器。
int processBindCpu() {
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(0, &set);
CPU_CLR(1, &set);
CPU_CLR(2, &set);
CPU_CLR(3, &set);
int ret = sched_setaffinity(0, sizeof(cpu_set_t), &set);
if(ret == -1)
perror("sched_setaffinity");
for(int i = 0; i < CPU_SETSIZE; ++i){
int cpu = CPU_ISSET(i, &set);
printf("cpu=%i is %s\n", i, cpu?"set":"unset");
}
return 0;
}
测试结果
Real-Time Systems
Setting the Linux scheduling policy
#include <sched.h>
struct sched_param {
/* ... */
int sched_priority;
/* ... */
};
int sched_getscheduler (pid_t pid);
int sched_setscheduler (pid_t pid,int policy,
const struct sched_param *sp);
int getProcessScheduler() {
int policy;
policy = sched_getscheduler(0);
switch (policy){
case SCHED_OTHER:
printf("Policy is normal\n");
break;
case SCHED_RR:
printf("Policy is round_robin\n");
break;
case SCHED_FIFO:
printf("Policy is first-in, first-out\n");
break;
case -1:
perror("sched_getscheduler");
break;
default:
fprintf(stderr, "Unknown policy!\n");
}
return 0;
}
测试结果
//怎么设置进程的调度方式
struct sched_param sp = { .sched_priority = 1 };
int ret;
ret = sched_setscheduler (0, SCHED_RR, &sp);
if (ret == −1) {
perror ("sched_setscheduler");
return 1; }
Setting Scheduling Parameters
#include <sched.h>
struct sched_param {
/* ... */
int sched_priority;
/* ... */
};
int sched_getparam (pid_t pid, struct sched_param *sp);
int sched_setparam (pid_t pid, const struct sched_param *sp);
struct sched_param sp;
int ret;
ret = sched_getparam (0, &sp);
if (ret == −1) {
perror ("sched_getparam");
return 1; }
printf ("Our priority is %d\n", sp.sched_priority);
struct sched_param sp;
int ret;
sp.sched_priority = 1;
ret = sched_setparam (0, &sp);
if (ret == −1) {
perror ("sched_setparam");
return 1; }
Determining the range of valid priority
每个进程都具有静态优先级,与nice value无关。对于正常的应用程序,这个优先级总是0。对于实时过程,它的范围从1到99。
linux调度程序总是选择要运行的最高优先级进程(即具有最大数值静态优先级值的进程)。
#include <sched.h>
int sched_get_priority_min (int policy);
int sched_get_priority_max (int policy);
int min, max;
min = sched_get_priority_min (SCHED_RR);
if (min == −1) {
perror ("sched_get_priority_min");
return 1;
}
max = sched_get_priority_max (SCHED_RR);
if (max == −1) {
perror ("sched_get_priority_max");
return 1;
}
printf ("SCHED_RR priority range is %d - %d\n", min, max);
/*
* set_highest_priority - set the associated pid's scheduling
* priority to the highest value allowed by its current
* scheduling policy. If pid is zero, sets the current
* process's priority.
*
* Returns zero on success.
*/
int set_highest_priority (pid_t pid) {
struct sched_param sp;
int policy, max, ret;
policy = sched_getscheduler (pid);
if (policy == −1)
return −1;
max = sched_get_priority_max (policy);
if (max == −1)
return −1;
memset (&sp, 0, sizeof (struct sched_param));
sp.sched_priority = max;
ret = sched_setparam (pid, &sp);
return ret;
}
#include <sched.h>
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
int sched_rr_get_interval (pid_t pid, struct timespec *tp);
struct timespec tp;
int ret;
/* get the current task's timeslice length */
ret = sched_rr_get_interval (0, &tp);
if (ret == −1) {
perror ("sched_rr_get_interval");
return 1;
}
/* convert the seconds and nanoseconds to milliseconds */
printf ("Our time quantum is %.2lf milliseconds\n", (tp.tv_sec * 1000.0f) + (tp.tv_nsec / 1000000.0f));
Resoucre Limits
#include <sys/time.h>
#include <sys/resource.h>
struct rlimit {
rlim_t rlim_cur; /* soft limit */
rlim_t rlim_max; /* hard limit */
};
int getrlimit (int resource, struct rlimit *rlim);
int setrlimit (int resource, const struct rlimit *rlim);
resource参数有如下:
- RLIMIT_AS
限制进程地址空间的最大大小(以字节为单位)。试图增加超过此限制的地址空间的大小(通过调用mmap()和brk()将失败并返回ENOMEM。如果 进程的堆栈,根据需要自动增长,扩展超过这个限制,内核向进程发送SIGSEGV信号。这个极限通常是RLIM_INFINITY。 - RLIMIT_CORE
以字节为单位确定核心文件的最大大小。如果不是零,则大于此限制的核心文件将被截断为最大大小。如果为0,则永远不会创建核心文件。 - RLIMIT_CPU
指示进程可消耗的最大CPU时间(以秒为单位)。如果进程运行的时间超过此限制,内核将向它发送SIGXCPU信号,进程可能会捕获和处理该信号。
但是,Linux允许进程继续执行,并以一秒钟的间隔继续发送SIGXCPU信号。一旦进程达到hard limit,它将被发送一个SIGKILL并终止。 - RLIMIT_DATA
控制进程数据段和堆的最大大小,以字节为单位。 试图通过 brk () 放大超过此限制的数据段将失败并返回 ENOMEM - RLIMIT_FSIZE
指定进程可创建的最大文件大小(以字节为单位)。如果进程扩展超过此大小的文件,内核将向进程发送SIGXFSZ信号。默认情况下,此信号终止过程。但是,一个进程可以选择捕获和处理这个信号,在这种情况下,违规的系统调用失败并返回EFBIG。 - RLIMIT_LOCKS
控制进程可能持有的文件锁的最大数量。一旦达到此限制,进一步尝试获取其他文件锁会失败并返回ENOLCK。但是,Linux内核2.4.25删除了此功能。在当前内核中,此限制是可设置的,但没有任何效果。 - RLIMIT_MSGQUEUE
指定用户可为POSIX消息队列分配的最大字节数。如果新创建的消息队列将超过此限制,mq_open()将失败并返回ENOMEM;它是在内核2.6.8中添加的,并且是Linux特有的。 - RLIMIT_NICE
- RLIMIT_NOFILE
- RLIMIT_NPROC
- RLIMIT_RTTIME
Specifies a limit (in microseconds) on CPU time that a real-time process may consume without issuing a blocking system call. Once the process makes a blocking system call, the CPU time is reset to zero. - RLIMIT_RTPRIO
- RLIMIT_SIGPENDING
指定可为此用户排队的最大信号数(标准信号和实时信号) - RLIMIT_STACK
表示进程的栈的最大大小,单位为字节。超过此限制会导致SIGSEGV的传递。
struct rlimit rlim;
int ret;
/* get the limit on core sizes */
ret = getrlimit (RLIMIT_CORE, &rlim);
if (ret == −1) {
perror ("getrlimit");
return 1;
}
printf ("RLIMIT_CORE limits: soft=%ld hard=%ld\n",
rlim.rlim_cur, rlim.rlim_max);
struct rlimit rlim;
int ret;
rlim.rlim_cur = 32 * 1024 * 1024; /* 32 MB */
rlim.rlim_max = RLIM_INFINITY; /* leave it alone */
ret = setrlimit (RLIMIT_CORE, &rlim);
if (ret == −1) {
perror ("setrlimit");
return 1; }
网友评论