0%

Linux系统编程-高级进程管理


专注于用户空间的系统级编程–即内核之上的所有内容。

进程调度

进程调度器是内核子系统,用于将有限的处理器资源分配给系统中的各个进程。

多任务操作系统分为协同式(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);