理解Linux內核搶佔模型(最透徹一篇)

本文原文地址:

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, &param);




  pthread_create(&t1,&attr,hi_prio,NULL);


  param.sched_priority = 30;
  pthread_attr_setschedparam(&attr, &param);


  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)

Linux閱碼場原創精華文章彙總

更多精彩,盡在"Linux閱碼場",掃描下方二維碼關注

轉發和在看是最大的支持~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章