美文网首页
Advanced Process Management 高级进程

Advanced Process Management 高级进程

作者: 无无吴 | 来源:发表于2019-08-16 11:04 被阅读0次

    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为每个进程分配处理器时间的一部分,而不是时间间隔:

    1. CFS首先给N个进程分配1/N的处理器时间。
    2. CFS然后通过用每个进程的nice value加权每个进程的占用处理器时间的比重。nice value为零的进程的权重为1,因此它们的比例不变。具有较小的nice value(高优先级)的进程接收较大的权重,增加了占用处理器的时间比重,而具有较大的nice value(较低优先级)的进程则减少了占用处理器的时间比重。
    3. 为了确定每个进程运行的实际时间长度,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有如下参数选项:

    1. PRIO_PROCESS
    2. PRIO_PGRP
    3. 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的传递。
    Default soft and hard resource limits
    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; }
    

    相关文章

      网友评论

          本文标题:Advanced Process Management 高级进程

          本文链接:https://www.haomeiwen.com/subject/vhaxsctx.html