专注于用户空间的系统级编程–即内核之上的所有内容。
进程调度
进程调度器是内核子系统,用于将有限的处理器资源分配给系统中的各个进程。
多任务操作系统分为协同式(cooperative)和抢占式(preemptive):
协同式多任务系统中,进程会一直运行到自己结束。这种自发结束的行为称为“让出(yielding)”。
操作系统不会强制要求让出。
抢占式多任务系统中,调度器决定某个进程何时停止运行,而由另一个进程运行,这种行为成为“抢占”。
进程在被抢占前所能够运行的时间成为该进程的“时间片(timeslice)”。
现代操作系统基本上都使用抢占式多任务机制。
完全公平调度器
当前Linux进程调度器是在Linux内核版本2.6.23发布的,称为“完全公平调度器(Completely Fair Scheduler,CFS)”。
采用“公平入队(fair queuing)”调度算法,对竞争进程采取公平访问资源的策略。
和传统的UNIX进程调度器有很大区别,消除了时间片作为处理器访问分配单元,而是给每个进程分配了处理器的时间比例。
让出处理器
#include <sched.h>
int sched_yield(void);
系统调用,支持进程主动让出处理器,并通知调度器选择新的进程来运行。
sched_yield()这个函数可以使用另一个级别等于或高于当前线程的线程先运行。
如果没有符合条件的线程,那么这个函数将会立刻返回然后继续执行当前线程的程序。
使用sched_yield()的两种情况:
- 用户空间线程的锁定。如果一个线程试图取得另一个线程所持有的锁,
则新的线程应该让出处理器知道该锁变为可用。
用户空间锁没有内核的支持,这是一个最间单、最有效率的做法。
但是现在Linux线程实现引入一个使用futexes的优化解决方案。 - 处理器密集型程序可用周期性调用sched_yield,
试图将该进程对系统的冲击减到最小。但是存在两个缺点:系统调度应该由调度器来承担,而不是用户进程。
而且减轻处理器密集应用带来的负担,是用户的责任,而不是某个应用。
进程优先级
CFS中进程的nice value会决定进程会运行多长时间,或者说是占用的百分比。
可以通过系统调用nice来设置、获取进程的nice value。
该值的范围是(-20,19],越低的值越高的优先级,实时进程应该是负数。
#include <unistd.h>
int nice(int inc);
nice()调用成功会在现有nice value上增加inc,并返回更新之后的值。
只有拥有CAP_SYS_NICE权限的进程(实际上即进程所有者为root)才可以使用负值inc,减少nice value。
从而提升该进程的优先级。因此,非root用户的进程只能降低优先级(增加nice value)。
因为nice()返回可以是-1,那么在判断是否系统调用失败的时候就要综合ret和errno。
调用前将errno值置为0,然后再检查errno:
int ret;
errno = 0;
ret = nice(10); /*increase our nice by 10*/
if(ret == -1 && errno != 0)
perror("nice");
else
printf("nice value is now %d\n", ret);
还有两个系统调用可以更灵活地设置,
getpriority可以获得进程组、用户的任何进程中优先级最高的。
setpriority将所指定的所有进程优先级设置为prio:
#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指定了进程ID、进程组ID或用户ID,当值为0时,调用分别在当前进程、当前进程组或当前用户上运行
处理器亲和力(Affinity)
Linux支持具有多个处理器的单一系统。在SMP上,系统要决定每个处理器上要运行那些程序,这里有两项挑战:
- 调度程序必须想办法充分利用所有的处理器
- 切换程序运行的处理器是需要代价的
进程会继承父进程的处理器亲和性,Linux提供两个系统调用用于获取和设定“硬亲和性”:
int ret;
cpu_set_t set;
CPU_ZERO(&set);
ret = sched_getaffinity(0, sizeof(cpu_set_t), &set);
if(ret == -1)
printf("调用失败!\n");
for(i = 0; i < 10; i++){
int cpu = CPU_ISSET(i, &set);
printf("cpu=%i is %s\n", i, cpu?"set":"unset");
}
CPU_ZERO(&set);
CPU_SET(0, &set); /*allow CPU #0*/
CPU_CLR(1, &set); /*disallow CPU #1*/
ret = sched_setaffinity(0, sizeof(cpu_set_t), &set);
if(ret == -1)
printf("调用失败!\n");
for(i = 0; i < 10; i++){
int cpu = CPU_ISSET(i, &set);
printf("cpu=%i is %s\n", i, cpu?"set":"unset");
}
实时系统
Linux除了正常的默认调度策略,还提供两种实时调度策略,头文件<sched.h>中宏定义:
- SCHED_FIFO,“先进先出”策略
- SCHED_RR,“轮询”策略(round-robin)
- SCHED_OTHER,标准调度策略,是非实时进程的默认调度策略,上述两种调度策略进程可以抢占它
获得和设置调度策略的系统调用:
#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);
sched_getscheduler()用法:
int policy;
/* Get our scheduling 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:
printf("sched_getscheduler failed.\n");
break;
default:
printf("Unknow policy\n");
sched_setscheduler()用法:
int ret;
struct sched_param sp;
sp.sched_priority = 1;
ret = sched_setscheduler(0, SCHED_RR, &sp);
if(ret == -1)
printf("sched_setscheduler failed.\n");
if(errno == EPERM)
printf("Process don't the ability.\n");
sched_getparam和sched_setparam接口可用于取得、设定一个已经设定好的调度策略参数:
int ret, i;
struct sched_param sp;
sp.sched_priority = 1;
ret = sched_setparam(0, &sp);
if(ret == -1)
printf("sched_setparam error.\n");
ret = sched_getparam(0, &sp);
if(ret == -1)
printf("sched_getparam error.\n");
Linux提供两个用于取得有效优先值的范围的系统调用:
int min, max;
struct sched_param sp;
min = sched_get_priority_min(SCHED_RR);
if(min == -1)
printf("sched_get_priority_min error.\n");
max = sched_get_priority_max(SCHED_RR);
if(max == -1)
printf("sched_get_priority_max error.\n");
printf("SCHED_RR priority range is %d-%d\n", min, max);
资源限制
Linux对进程加上了若干资源限制,这些限制是一个进程所能耗用的内核资源的上限。限制的类型如下:
- RLIMIT_AS:地址空间上限
- RLIMIT_CORE:core文件大小上限
- RLIMIT_CPU:可耗用CPU时间上限
- RLIMIT_DATA:数据段与堆的上限
- RLIMIT_FSIZE:所能创建文件的大小上限
- RLIMIT_LOCKS:文件锁数目上限
- RLIMIT_MEMLOCK:不具备CAP_SYS_IPC能力的进程最多将多少个字节锁进内存
- RLIMIT_MSGQUEUE:可以在消息队列中分配多少字节
- RLIMIT_NICE:最多可以将自己的友善值调多低
- RLIMIT_NOFILE:文件描述符数目的上限
- RLIMIT_NPROC:用户在系统上能运行进程数目上限
- RLIMIT_RSS:内存中页面的数目的上限
- RLIMIT_RTTIME:最多可以消耗CPU时间上限
- RLIMIT_RTPRIO:不具备CAP_SYS_NICE能力进程所能请求的实时优先级的上限
- RLIMIT_SIGPENDING:在队列中信号量的上限,Linux特有的限制
- RLIMIT_STACK:堆栈大小的上限
获取或设置这些限制的方法:
int ret;
struct rlimit rlim;
rlim.rlim_cur = 32*1024*1024;
rlim.rlim_max = RLIM_INFINITY;
ret = setrlimit(RLIMIT_CORE, &rlim);
ret = getrlimit(RLIMIT_CORE, &rlim);
if(ret == -1)
printf("getrlimit error.\n");
printf("RLIMIT_CORE limits: soft=%ld hard=%ld\n", rlim.rlim_cur, rlim.rlim_max);