本文原文地址:
https://devarea.com/understanding-linux-kernel-preemption/#.XrKLcfnx05k
作者:Liran B.H
譯者:宋寶華
當配置Linux內核的時候,我們可以選擇一些參數,這些參數能影響系統的行爲。你可以用不同的優先級、調度類和搶佔模型來工作。正確地選擇這些參數是非常重要的。
本文將論述不同的搶佔模型如何影響用戶和系統的行爲。
當你使用 make menuconfig配置內核的時候,你能看到這樣的菜單:
爲了深入理解這三個搶佔模型的區別,我們將寫一個案例:
2個線程,一個高優先級RT(50),一個低優先級RT(30)
高優先級的線程要睡眠3秒
低優先級的線程用CPU來做計算
3秒後高優先級線程喚醒。
如果低優先級的線程陷入系統調用,高優先級的線程睡眠到期,究竟會發生什麼?下面我們來一種模型一種模型地看。
No Forced Preemption
這種情況下,上下文切換髮生在系統調用返回用戶空間的點。案例如下:
2個線程,一個高優先級RT(50),一個低優先級RT(30)
高優先級的線程要睡眠3秒
低優先級的線程進入系統調用計算5秒
5秒後低優先級線程從內核系統調用返回
高優先級線程將醒來(但是比預期遲了2秒)。
內核代碼,簡單的字符設備:
#include <asm/uaccess.h>
#include <linux/fs.h>
#include <linux/gfp.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/kdev_t.h>
#include <linux/delay.h>
#include <linux/ioctl.h>
#include <linux/slab.h>
#include <linux/mempool.h>
#include <linux/mm.h>
#include <asm/io.h>
static dev_t my_dev;
static struct cdev *my_cdev;
// callback for read system call on the device
static ssize_t my_read(struct file *file, char __user *buf,size_t count,loff_t *ppos)
{
int len=5;
if(*ppos > 0)
{
return 0;
}
mdelay(5000); // busy-wait for 5 seconds
if (copy_to_user(buf , "hello" , len)) {
return -EFAULT;
} else {
*ppos +=len;
return len;
}
}
static struct file_operations my_fops =
{
.owner = THIS_MODULE,
.read = my_read,
};
static int hello_init (void)
{
my_dev = MKDEV(400,0);
register_chrdev_region(my_dev,1,"demo");
my_cdev=cdev_alloc();
if(!my_cdev)
{
printk (KERN_INFO "cdev alloc error.\n");
return -1;
}
my_cdev->ops = &my_fops;
my_cdev->owner = THIS_MODULE;
if(cdev_add(my_cdev,my_dev,1))
{
printk (KERN_INFO "cdev add error.\n");
return -1;
}
return 0;
}
static void
hello_cleanup (void)
{
cdev_del(my_cdev);
unregister_chrdev_region(my_dev, 1);
}
module_init (hello_init);
module_exit (hello_cleanup);
MODULE_LICENSE("GPL");
讀裏面delay了5秒, 注意mdelay是一個計算型的busy-loop。
用戶空間代碼如下:
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void *hi_prio(void *p)
{
printf("thread1 start time=%ld\n",time(NULL));
sleep(3);
printf("thread1 stop time=%ld\n",time(NULL));
return NULL;
}
void *low_prio(void *p)
{
char buf[20];
sleep(1);
int fd=open("/dev/demo",O_RDWR); // #mknod /dev/demo c 400 0
puts("thread2 start");
read(fd,buf,20);
puts("thread2 stop");
return NULL;
}
int main()
{
pthread_t t1,t2,t3;
pthread_attr_t attr;
struct sched_param param;
pthread_attr_init(&attr);
pthread_attr_setschedpolicy(&attr, SCHED_RR);
param.sched_priority = 50;
pthread_attr_setschedparam(&attr, ¶m);
pthread_create(&t1,&attr,hi_prio,NULL);
param.sched_priority = 30;
pthread_attr_setschedparam(&attr, ¶m);
pthread_create(&t2,&attr,low_prio,NULL);
sleep(10);
puts("end test");
return 0;
}
實驗步驟:
高優先級線程開始睡眠3秒
低優先級線程睡眠1秒然後做系統調用
高優先級線程6秒後醒來(stop和start的時間差)
# insmod demo.ko
# ./app
thread1 start time=182
thread2 start
thread1 stop time=188
thread2 stop
end test
Preemptible Kernel
這種情況內核裏面也可以搶佔,意味着上述程序裏面的高優先級線程3秒後可醒來。
這種情況下,系統會有更多的上下文切換,但是實時性更加好。對於要求軟實時的嵌入式系統而言,這個選項是最佳的。但是對於服務器而言,通常第一個選項更好——更少的上下文切換,更多的CPU時間用作有用功。
運行結果(stop、start時間差3秒):
# insmod ./demo.ko
#./app
thread1 start time=234
thread2 start
thread1 stop time=237
thread2 stop
end test
Voluntary Kernel Preemption
這種情況和第一種情況"no forced preemption"類似,但是內核開發者可以在進行復雜操作的時候,時不時檢查一下是否可以reschedule。他們可以調用might_resched()函數。
在下面的代碼中,我們添加了一些檢查點(check point)
// callback for read system call on the device
static ssize_t my_read(struct file *file, char __user *buf,size_t count,loff_t *ppos)
{
int len=5;
if(*ppos > 0)
{
return 0;
}
mdelay(4000); // busy-wait for 4 seconds
might_resched();
delay(3000); // busy wait for 3 seconds
if (copy_to_user(buf , "hello" , len)) {
return -EFAULT;
} else {
*ppos +=len;
return len;
}
}
如果我們把might_resched()註釋掉,它會delay 7秒。
添加cond_resched()調用將導致系統檢查是否有高優先級的任務被喚醒,這樣高優先級任務5秒可以醒來(其中1秒在systemcall之前,另外4秒在kernel)。
運行結果:
# insmod ./demo.ko
#./app
thread1 start time=320
thread2 start
thread1 stop time=325
thread2 stop
end test
Full Real Time Preemption
如果我們使能RT補丁,我們會得到一個硬實時的kernel。這意味着任何代碼可以搶佔任何人。比如一個更加緊急的任務可以搶佔中斷服務程序ISR。這個patch進行了如下改動:
把中斷服務程序轉化爲優先級是50的RT線程
把softIRQ轉化爲優先級是49的RT線程
把所有的spinlock變成mutex
高精度定時器
其他的細小改動
打補丁後會看到2個新增的菜單:
其中 “Preemptible Kernel (Basic RT)” 是爲了調試目的的,爲了全面使用RT補丁的功能,我們應該選擇最後一項 – Fully Preemptible Kernel。這樣我們會有更多的上下文切換,但是可以滿足RT的實時要求。
(END)
轉發和在看是最大的支持~