浅析Linux线程调度

在Linux中,线程是由进程来实现,线程就是轻量级进程( lightweight process ),因此在Linux中,线程的调度是按照进程的调度方式来进行调度的,也就是说线程是调度单元。Linux这样实现的线程的好处的之一是:线程调度直接使用进程调度就可以了,没必要再搞一个进程内的线程调度器。在Linux中,调度器是基于线程的调度策略(scheduling policy)和静态调度优先级(static scheduling priority)来决定那个线程来运行。

对于下面三种调度策略SCHED_OTHER, SCHED_IDLE, SCHED_BATCH,其调度优先级sched_priority是不起作用的,即可以看成其调度优先级为0;调度策略SCHED_FIFO和SCHED_RR是实时策略,他们的调度值范围是1到99,数值越大优先级越高,另外实时调度策略的线程总是比前面三种通常的调度策略优先级更高。通常,调度器会为每个可能的调度优先级(sched_priority value)维护一个可运行的线程列表,并且是以最高静态优先级列表头部的线程作为下次调度的线程。所有的调度都是抢占式的:如果一个具有更高静态优先级的线程转换为可以运行了,那么当前运行的线程会被强制进入其等待的队列中。下面介绍几种常见的调度策略:

SCHED_OTHER:该策略是是默认的Linux分时调度(time-sharing scheduling)策略,它是Linux线程默认的调度策略。SCHED_OTHER策略的静态优先级总是为0,对于该策略列表上的线程,调度器是基于动态优先级(dynamic priority)来调度的,动态优先级是跟nice中相关(nice值可以由接口nice, setpriority,sched_setattr来设置),该值会随着线程的运行时间而动态改变,以确保所有具有SCHED_OTHER策略的线程公平运行。在Linux上,nice值的范围是-20到+19,默认值为0;nice值越大则优先级越低,相比高nice值(低优先级)的进程,低nice值(高优先级)的进程可以获得更多的处理器时间。使用命令ps -el查看系统的进程列表,其中NI列就是进程对应的nice值;使用top命令,看到的NI列也是nice值。运行命令的时候可用nice –n xx cmd来调整cmd任务的nice值,xx的范围是-20~19之间。

SCHED_FIFO:先入先出调度策略(First in-first out scheduling)。该策略简单的说就是一旦线程占用cpu则一直运行,一直运行直到有更高优先级任务到达或自己放弃。

SCHED_RR:时间片轮转调度(Round-robin scheduling)。该策略是SCHED_FIFO基础上改进来的,他给每个线程增加了一个时间片限制,当时间片用完后,系统将把该线程置于队列末尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平。使用top命令,如果PR列的值为RT,则说明该进程采用的是实时策略,即调度策略是SCHED_FIFO或者为SCHED_RR,而对于非实时调度策略(比如SCHED_OTHER)的进程,该列的值是NI+20,以供Linux内核使用。我们可以通过命令:

ps -eo state,uid,pid,ppid,rtprio,time,comm

来查看进程对应的实时优先级(位于RTPRIO列下),如果有进程对应的列显示“-”,则说明它不是实时进程。注意任何实时策略进程的优先级都高于普通的进程,也就说实时优先级和nice优先级处于互不相交的两个范畴。

在Linux中,与调度相关的常见接口如下:

#include <sched.h>
int sched_get_priority_max(int policy);

该接口获取指定调度策略可以设置的最大优先级,类似的 sched_get_priority_min接口获取调度策略可以设置的最小优先级。在Linux中,对于SCHED_FIFO和SCHED_RR调度策略其优先级为1到99,其他调度策略优先级为0。注意在不同系统上,这个优先级范围可能不一样。

#include <pthread.h>
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);

该接口可以用来设置线程的调度策略,即设置线程属性attr。参数policy可以是CHED_FIFO, SCHED_RR和SCHED_OTHER。系统创建线程时,默认的线程调度策略是SCHED_OTHER。类似可以通过接口

int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy)

获取线程的调度策略。

#include <pthread.h>
int pthread_attr_setschedparam(pthread_attr_t *attr,
const struct sched_param *param);

该接口可以用来设置线程的调度优先级。结构sched_param定义如下:

struct sched_param {
   int sched_priority;     /* Scheduling priority */
};

类似的的接口,可以用来获取线程调度的优先级:

int pthread_attr_getschedparam(const pthread_attr_t *attr,
                                      struct sched_param *param);
#include <sched.h>
int sched_yield(void);

调用该接口可以使得当前线程主动交出CPU,并把该线程放到相应调度队列的末尾。如果当前线程是最高优先级队列中唯一的线程,则在调用sched_yield后,该线程继续保持运行。

#include <sched.h>
int sched_setaffinity(pid_t pid,
                      size_t cpusetsize,const cpu_set_t *mask);

该接口可以用来设置线程的CPU亲和性(CPU affinity),设置线程的亲和性可以使得线程绑定到一个或多个指定的CPU上运行。在多处理器系统上,设置CPU亲和性可以提高性能(主要原因是尽可能避免了cache失效和切换到其他CPU的消耗)。CPU亲和性掩码是由cpu_set_t结果来实现的,该结构体需要用预定义好的宏来操作;参数pid是指定线程的TID,可以通过gettid()来获取,即线程在内核中对应进程id,若pid为0,则设置的是调用线程的CPU亲和性,注意用getpid()获取的是主线程的id;参数cpusetsize的值通常是mask的大小,即sizeof(mask)。除了这个接口外,设置线程亲和性接口还有:

#include <pthread.h>
int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize,
                                  const cpu_set_t *cpuset);
int pthread_attr_setaffinity_np(pthread_attr_t *attr,
size_t cpusetsize, const cpu_set_t *cpuset);

通过fork创建的子进程继承父进程的CPU亲和性,通过 execve()后,亲和性仍然保持不变。我们可以下面命令来查看多核cpu的负载:

I)cat /proc/cpuinfo  查看所有cpu的信息;

II)top命令,然后再输入1,则显示多个cpu的使用信息;

III)top命令,然后按下f,进入top Current Fields设置页面,然后按下j,表示要求显示进程使用那个cpu,回车后,回到刚才界面,此时P 显示此进程使用哪个CPU。

下面是测试代码:

#include <stdio.h>
#include <pthread.h>
#include <sched.h>
#include <assert.h>

static int get_thread_policy(pthread_attr_t *attr)
{
    int policy;
    int rs = pthread_attr_getschedpolicy(attr,&policy);
    assert(rs==0);

    switch(policy)
    {
        case SCHED_FIFO:
            printf("policy=SCHED_FIFO\n");
            break;

        case SCHED_RR:
            printf("policy=SCHED_RR\n");
            break;

        case SCHED_OTHER:
            printf("policy=SCHED_OTHER\n");
            break;

        default:
            printf("policy=UNKNOWN\n");
            break;
    }
    return policy;
}

static void show_thread_priority(pthread_attr_t *attr,int policy)
{
    int priority = sched_get_priority_max(policy);
    assert(priority != -1);
    printf("max_priority=%d\n",priority);

    priority= sched_get_priority_min(policy);
    assert(priority != -1);
    printf("min_priority=%d\n",priority);
}

static int get_thread_priority(pthread_attr_t *attr)
{
    struct sched_param param;
    int rs = pthread_attr_getschedparam(attr,¶m);
    assert(rs == 0);

    printf("priority=%d\n",param.__sched_priority);
    return param.__sched_priority;
}

static void set_thread_policy(pthread_attr_t *attr,int policy)
{
    int rs = pthread_attr_setschedpolicy(attr,policy);
    assert(rs==0);
}

int main(void)
{
    pthread_attr_t attr;
    int rs;

    rs = pthread_attr_init(&attr);
    assert(rs==0);

    int policy = get_thread_policy(&attr);

    printf("Show current configuration of priority\n");
    get_thread_policy(&attr);
    show_thread_priority(&attr,policy);

    printf("show SCHED_FIFO of priority\n");
    show_thread_priority(&attr,SCHED_FIFO);

    printf("show SCHED_RR of priority\n");
    show_thread_priority(&attr,SCHED_RR);

    printf("show priority of current thread\n");
    get_thread_priority(&attr);

    printf("Set thread policy\n");

    printf("set SCHED_FIFO policy\n");
    set_thread_policy(&attr,SCHED_FIFO);
    get_thread_policy(&attr);
    get_thread_priority(&attr);

    printf("set SCHED_RR policy\n");
    set_thread_policy(&attr,SCHED_RR);
    get_thread_policy(&attr);

    printf("Restore current policy\n");
    set_thread_policy(&attr,policy);
    get_thread_priority(&attr);

    rs = pthread_attr_destroy(&attr);
    assert(rs==0);

    return 0;
}

编译和运行程序结果如下:

$gcc -Wall -lpthread hack_thread_sched.c -o hack_thread_sched
$./hack_thread_sched
policy=SCHED_OTHER
Show current configuration of priority
policy=SCHED_OTHER
max_priority=0
min_priority=0
show SCHED_FIFO of priority
max_priority=99
min_priority=1
show SCHED_RR of priority
max_priority=99
min_priority=1
show priority of current thread
priority=0
Set thread policy
set SCHED_FIFO policy
policy=SCHED_FIFO
priority=0
set SCHED_RR policy
policy=SCHED_RR
Restore current policy
priority=0

从输出结果,我们可以看到:

I)线程默认的调度策略为SCHED_OTHER,并且最大和最小调度优先级都是0。

II)调度策略SCHED_FIFO和SCHED_RR的优先级范围为1到99,并且初始设置时对应的调度优先级初始值为0。

在Linux中,调度程序是一个叫schedule()的函数,该函数调用的频率很高,由它来决定是否要执行进程的切换,如果要切换的话,切换到那个进程等。那么在Linux中,在什么情况下要执行这个调度程序呢?我们把这种情况叫作调度时机。Linux调度时机主要有:

I)进程状态转换的时刻:进程终止、进程睡眠(比如I/O阻塞就会导致这种情况),还比如进程调用sleep()或exit()等函数进行状态转换。

II)当前进程的时间片用完时。

III)设备驱动程序,当设备驱动程序执行长而重复的任务时,在每次反复循环中,驱动程序读检查是否需要调度,如果必要,则调用调度程序schedule()放弃CPU。

IV)进程从中断、异常及系统调用返回到用户态时。

参考资料

http://man7.org/linux/man-pages/man7/sched.7.html

http://man7.org/linux/man-pages/man2/sched_getaffinity.2.html

http://www.cnblogs.com/xiaotlili/p/3510224.html

http://www.708luo.com/?p=78 Linux

http://www.quora.com/What-is-the-difference-between-the-NI-and-PR-values-in-the-top-1-commands-output

http://blog.csdn.net/hanchaoman/article/details/6697636

http://www.ibm.com/developerworks/cn/linux/l-affinity.html

http://blog.csdn.net/chenggong2dm/article/details/6131052

《Linux内核设计与实现》(第3版)

《深入分析Linux内核源代码》(陈莉君著)

时间: 2024-08-05 16:20:42

浅析Linux线程调度的相关文章

浅析Linux驱动模型中的底层数据结构kobject和kset

1.kobject Linux内核用kobject来表示一个内核对象.它和Sysfs文件系统联系密切,在内核中注册到系统中的每个kobject对象在sysfs文件系统中对对应着一个文件目录.kobject数据结构通常的用法是嵌入到其对他的数据结构中(即容器,比如cdev结构),用于实现内核对该类数据结构对象的管理.这些数据结构(容器)通过kobject连接起来,形成了一个树状结构. 它在源码中的定义为: /*<include/linux/kobject.h>*/ struct kobject

浅析 Linux 下的 arm 交叉编译工具

什么是交叉编译工具 http://elinux.org/Toolchains 要解释交叉编译工具,我们首先要理解三个基本概念: 编译平台:是指该编译器是在哪个平台编译出来的 编译主机:编译器运行在哪个平台 目标平台:编译器为哪个平台产生代码 比如我们在 Ubuntu(Linux) 下编译 arm 开发板的程序,那么: 编译平台就是 X86,该编译器在 x86上运行 编译主机就是这台运行 Ubuntu Linux 的电脑 目标平台就是 arm 开发板 所以通常所指的交叉编译,就是在该平台编译生成在

浅析 Linux 初始化 init 系统

近年来,Linux 系统的 init 进程经历了两次重大的演进,传统的 sysvinit 已经逐渐淡出历史舞台,新的 UpStart 和 systemd 各有特点,越来越多的 Linux 发行版采纳了 systemd.本文简要介绍了这三种 init 系统的使用和原理,每个 Linux 系统管理员和系统软件开发者都应该了解它们,以便更好地管理系统和开发应用. 浅析 Linux 初始化 init 系统,第 1 部分: sysvinit 浅析 Linux 初始化 init 系统,第 2 部分: UpS

浅析 Linux 系统调用

浅析 Linux 系统调用 用户态.内核态以及中断 具有高执行级别的程序可以执行特权指令 intel X86 CPU 具有4种级别:0 ~ 3 Linux 只用了0和3(0表示内核态,3表示用户态) 特权级的表示:使用 CS 寄存器的低2位 内核态逻辑地址空间:0xc0000000以上 用户态逻辑地址空间:0x00000000 ~ 0xbfffffff 中断是从用户态到内核态的一种方式,即通过系统调用(系统调用是一种特殊的中断) 中断过程寄存器上下文的保存 保存到什么地方?堆栈 保存的内容: 用

浅析 Linux 中的时间编程和实现原理一—— Linux 应用层的时间编程【转】

本文转载自:http://www.cnblogs.com/qingchen1984/p/7007631.html 本篇文章主要介绍了"浅析 Linux 中的时间编程和实现原理一—— Linux 应用层的时间编程",主要涉及到浅析 Linux 中的时间编程和实现原理一—— Linux 应用层的时间编程方面的内容,对于浅析 Linux 中的时间编程和实现原理一—— Linux 应用层的时间编程感兴趣的同学可以参考一下. 简介: 本文试图完整地描述 Linux 系统中 C 语言编程中的时间问

浅析Linux内核同步机制

很早之前就接触过同步这个概念了,但是一直都很模糊,没有深入地学习了解过,近期有时间了,就花时间研习了一下<linux内核标准教程>和<深入linux设备驱动程序内核机制>这两本书的相关章节.趁刚看完,就把相关的内容总结一下.为了弄清楚什么事同步机制,必须要弄明白以下三个问题: 什么是互斥与同步? 为什么需要同步机制? Linux内核提供哪些方法用于实现互斥与同步的机制? 1.什么是互斥与同步?(通俗理解) 互斥与同步机制是计算机系统中,用于控制进程对某些特定资源的访问的机制. 同步

Linux线程调度引起的业务问题

一. 问题现象 1.业务组播出向报文偶尔有延迟: 2.单播出向报文平滑 二. 分析及定位 使用wireshark分析了组播出向报文的抓包,报文无丢包,但是IO 输出流量显示有burst和掉坑现象. 波形和抓包文件分析如下图: 后来在接收侧抓包,并分析日志,接收方没有出现丢包问题,但是有接收码流不足,导致收包不及时,业务不流畅. 通过在系统内核发包udp_sendmsg函数打点,发现有时候有300ms+没有报文发出,说明发包线程被阻塞. 下面是监控组播出向发包函数udp_sendmsg的打点记录日

浅析Linux系统下用户与权限管理

Linux作为一种多用户多任务操作系统,在日常的使用中不可避免地要划分出一个角色的概念来管理和使用计算机,这个角色与每一个计算机使用者关联,在Linux中称这种角色为用户.而在每一个用户使用计算机的过程中,又必然存在对有限计算机资源使用的限制性,那么操作系统就必须提供一种途径来保证每个用户独立.合理的使用计算机. 一.用户和用户组管理   (一)用户及用户组相关基本概念  用户:泛指计算机的使用者.用计算机可识别的用户ID(UID,user id)标识. 用户组:用户容器,用来将多个用户合并为一

浅析 Linux 初始化 init 系统,第 2 部分: UpStart

Upstart 简介 假如您使用的 Linux 发行版是 Ubuntu,很可能会发现在您的计算机上找不到/etc/inittab 文件了,这是因为 Ubuntu 使用了一种被称为 upstart 的新型 init 系统. 开发 Upstart 的缘由 大 约在 2006 年或者更早的时候, Ubuntu 开发人员试图将 Linux 安装在笔记本电脑上.在这期间技术人员发现经典的 sysvinit 存在一些问题:它不适合笔记本环境.这促使程序员 Scott James Remnant 着手开发 u