國嵌視訊學習第十一天——核心程序控制
Linux程序控制
程序四要素
1.有一段程式供其執行。這段程式不一定是某個程序所專有,可以與其他程序共用
2.有程序專用的核心空間堆疊
3.在核心中有一個task_struct資料結構,即通常所說的“程序控制塊”(PCB)。有了這個資料結構,程序才能成為核心排程的一個基本單位接受核心的排程。
4.有獨立的使用者空間
程序描述
在linux中,執行緒、程序都使用struct task_struct來表示,它包含了大量描述程序/執行緒的資訊:
——pid_t pid:程序號,最大值10億。每個程序有唯一的程序號。核心執行緒、使用者執行緒都對應task_struct,所以他們也有pid
——volatile long state
1.TASK_RUNNING:程序正在被CPU執行,或者已經準備就緒,隨時可以執行。當一個程序剛被建立時,就處於TASK_RUNNING狀態(對應就緒和執行態)2.TASK_INTERRUPTIBLE:處於等待中的程序,待等待條件為真時被喚醒,也可以被訊號或者中斷喚醒
3.TASK_UNINTERRUPTIBLE:處於等待中的程序,待資源有效時喚醒,但不可以由其他程序通過訊號(signal)或中斷喚醒
4.TASK_STOPPED:程序中止執行。當接收到SIGSTOP和SIGSTP等訊號時,程序進入該狀態,接收到SIGCONT訊號後,程序重新回到TASK_RUNNING
5.TASK_KILLABLE:
6.TASK_TRACED:正處於被除錯狀態的程序
7.TASK_DEAD:程序退出時(呼叫do_exit),state欄位被設定為該狀態
——int exit_state程序退出時的狀態
1.EXIT_ZOMBIE(僵死程序):表示程序的執行被終止,但是父程序還沒有釋出waitpid()系統呼叫來收集有關死亡的程序的資訊
EXIT_DEAD(僵死撤銷狀態):表示程序的最終狀態。父程序已經使用wait4()或waitpid()系統呼叫來收集了資訊,因此程序將由系統刪除
——struct mm_struct *mm:程序使用者空間描述指標,核心執行緒該指標為空
——unsigned int policy:該程序的排程策略
——int prio:優先順序,相當於linux2.4中goodness()的計算結果,在0--(MAX_PRIO - 1)之間取值(MAX_PRIO定義為140),其中0--(MAX_RT_PRIO - 1)(MAX_RT_PRIO定義為100)屬於實時程序範圍,MAX_RT_PRIO - MX_PRIO - 1屬於非實時程序
數值越大,表示程序優先順序越小
——int static_prio:靜態優先順序,與linux2.4的nice意義相同。Nice值任沿用linux的傳統,在-20到19之間變動,數值越大,程序的優先順序越小。Nice是使用者可維護的,但僅影響非實時程序的優先順序。程序初始時間片的大小僅決定於程序的靜態優先順序,這一點不論是實時程序還是非實時程序都一樣。不過實時程序的static_prio不參與優先順序計算。Nice與static_prio的關係如下:
Static_prio = MAX_RT_PRIO + nice +20
核心定義了兩個巨集用來完成這一轉換:PRIO_TO_NICE()、NICE_TO_PRIO
——struct sched_rt_entity rt
Rt->time_slice
:時間片,程序的預設時間片與程序的靜態優先順序(在linux2.4中是nice值)相關,使用如下公式得出:
MIN_TIMESLICE +((MAX_TIMESLICE - MIN_TIMESLICE) * (MAX_PRIO -1 -(p)->static_prio)/(MAX_USER_PRIO - 1))
每當建立一個程序或者執行緒時,都會分配如上圖
在linux中用全域性變數current指標指向當前正在執行的程序(執行緒)的task_struct
Linux程序排程
排程
從就緒的程序中選出最適合的一個來執行
學習排程需要掌握的知識點:排程策略;排程時機;排程步驟
排程策略
——SCHED_NORMAL(SCHED_OTHER):普通的分時程序
——SCHED_FIFO:先入先出的實時程序
——SCHED_RR:時間片輪轉的實時程序
——SCHED_BATCH:批處理程序
——SCHED_IDLE:只在系統空閒時才能夠被排程執行的程序
排程策略是某一個程序的排程策略,而非整個系統所有的程序都用某一種策略!
排程類
排程類的引入增強了核心排程程式的可擴充套件性,這些類(排程程式模組)封裝了排程策略,並將排程策略模組化
——CFS排程類(在kernel/sched_fair.c中實現)用於以下排程策略:SCHED_NORMAL、SCHED_BATCH和SCHED_IDLE
——實時排程類(在kernel/sched_rt.c中實現)用於SCHED_RR和SCHED_FIFO策略(看pick_next_task核心函式可以知道,該類的優先順序比CFS排程類高)
排程時機
排程什麼時候發生?即:schedule()函式什麼時候被呼叫
排程的發生有兩種方式:
1.主動式:在核心中直接呼叫schedule()。當程序需要等待資源而暫時停止執行時,會把狀態置於掛起(睡眠),並主動請求排程,讓出CPU
例:current->state = TASK_INTERRUPTIBLE;
Schedule();//排程不會選上述程序,因為schedule只會選就緒態的程序
2.被動式(搶佔):使用者搶佔(linux2.4、linux2.6)、核心搶佔(linux2.6)
使用者搶佔:使用者搶佔發生在——從系統呼叫返回使用者空間
——從中斷處理程式返回使用者空間
核心即將返回到使用者空間的時候,如果need_resched標誌被設定,會導致schedule()被呼叫,此時就會發生使用者搶佔
核心搶佔:——在不支援核心搶佔的系統中,程序/執行緒一旦運行於核心空間,就可以一直執行,直到它主動放棄或時間片耗盡為止。這樣一些非常緊急的程序或執行緒將長時間得不到執行
——在支援核心搶佔的系統中,更高優先順序的程序/執行緒可以搶佔正在核心空間執行的低優先順序程序/執行緒
在支援核心搶佔的系統中,某些特例下是不允許核心搶佔的:
——核心正進行中斷處理。程序排程函式schedule()會對此作出判斷,如果是在中斷中呼叫,會打印出錯資訊。
——核心正在進行中斷上下文的Bottom Half(中斷的底半部)處理。硬體中斷返回前會執行軟中斷。此時任然處於中斷上下文中
——程序正持有spinlock自旋鎖、writelock/readlock讀寫鎖等,當持有這些鎖時,不應該被搶佔,否則由於搶佔將導致其他CPU長期不能獲得鎖而死等
——核心正在執行排程程式Schedule。搶佔的原因就是為了進行新的排程,沒有理由將排程程式搶佔掉再執行排程程式
為保證linux核心在以上情況下不會被搶佔,搶佔式核心使用了一個變數preempt_count,稱為核心搶佔計數。這一變數被設定在程序的thread_info結構中。每當核心要進入以上幾種狀態時,變數preempt_count就加1,指示核心不允許搶佔。每當核心從以上幾種狀態退出時,變數preempt_count就減1,同時進行可搶佔的判斷與排程
核心搶佔可能發生在:
——中斷處理程式完成,返回核心空間之前
——當核心程式碼再一次具有可搶佔性的時候,如解鎖及使能軟中斷等
排程標誌
TIF_NEED_RESCHED
作用:核心提供了一個need_resched標誌來標明是否需要重新執行一次排程
設定:當某個程序耗盡它的時間片時,會設定這個標誌;
當一個優先順序更高的程序進入可執行狀態的時候,也會設定這個標誌
排程步驟
Schedule函式工作流程如下:
1).清理當前執行中的程序
2)選擇下一個要執行的程序;(根據排程策略來選擇。pick_next_task核心函式分析:呼叫處於TASK_RUNNING狀態的程序)
3)設定新程序的執行環境
程序上下文切換
Linux系統呼叫
定義
一般情況下,使用者程序是不能訪問核心的。它既不能訪問核心中的資料,也不能呼叫核心中的函式。但系統呼叫是一個例外。Linux核心中設定了一組用於實現各種系統功能的函式,稱為系統呼叫。使用者可以通過系統呼叫命令在自己的應用程式中呼叫它們
區別
系統呼叫和普通的函式呼叫非常相似,區別僅僅在於,系統呼叫由作業系統核心實現,運行於核心態;而普通的函式呼叫由函式庫或使用者自己提供,運行於使用者態。
庫函式
Linux系統提供了一些C語言函式庫,這些庫函式對系統呼叫進行了一些包裝和擴充套件(fread、fwrite是庫函式,對系統呼叫做了一些封裝)
系統呼叫數
在linux2.6.29版核心中,共有系統呼叫360個,可在arch/arm/include/asm/unistd.h中找到它們
工作原理概述
void main()
{
create(“testfile”,0666)<0)
}
應用程式首先用適當的值填充暫存器,然後呼叫一個特殊的指令讓PC指標跳轉到核心某一固定的位置,核心根據應用程式所填充的固定值來找到相應的函式執行。
——適當的值
在檔案include/asm/unistd.h中為每個系統呼叫規定了唯一的編號,這個號稱為系統呼叫號。
#define__NR_restart_syscall (__NR_SYSCALL_BASE+0)
#define__NR_exit (__NR_SYSCALL_BASE+1)
#define__NR_fork (__NR_SYSCALL_BASE+ 2)
#define__NR_read (__NR_SYSCALL_BASE+ 3)
——特殊的指令
——在intelCPU中,這個指令由中斷0x80實現
——在ARM中,這個指令是SWI(已經重新命名為SVC指令)
——固定的位置
在ARM體系中,應用程式跳轉到的固定核心位置是ENTRY(vector_swi)。 (在<entry-common.S>程式中)
——相應的函式
核心根據應用程式傳遞來的系統呼叫號,從系統呼叫表sys_call_table找到相應的核心函式。
CALL(sys_restart_syscall)
CALL(sys_exit)
CALL(sys_fork_wrapper)
例:create系統呼叫實現原理
實現系統呼叫
向核心中新增新的系統呼叫,需要執行3個步驟:
1.新增新的核心函式
2.更新標頭檔案unistd.h
3.針對這個新函式更新系統呼叫表calls.S
例:
1.在kernel/sys.c中新增函式
asmlinkage int sys_add(int a, int b)
{
return a+b;
}
/*asmlinkage:使用棧傳遞引數*/
2.在arch/arm/include/asm/unistd.h中新增如下程式碼:
#define __NR_add (__NR_SYSCALL_BASE+ 361)
3.在arch/arm/kernel/calls.S中新增程式碼,指向新實現的系統呼叫函式:CALL(sys_add)
(在calls.S中新新增的順序必須和在unistd.h中新增的順序相一致)
4.重新編譯核心 make uImage ARCH=arm CROSS_COMPILE=arm-linux-
5.寫應用程式檢驗:
#include <stdio.h>
#include <linux/unistd.h>
main()
{
int result;
result = syscall(361, 1, 2); /*第一個引數是系統呼叫的編號,後兩個數是給核心函式sys_add傳入的引數*/
printf(“result = ”, result);
}
Proc檔案系統
定義
什麼是proc檔案系統?
例項:通過/proc/meminfo,查詢當前記憶體使用情況。
結論:proc檔案系統是一種在使用者態檢驗核心狀態的機制特點
——每個檔案都規定了嚴格許可權
——可以用文字編譯程式讀取(more、cat命令、vi程式等)
——不僅可以有檔案,還可以有子目錄
——可以自己編寫程式新增一個/proc目錄下的檔案
——檔案的內容都是動態建立的,並不存在於磁碟上
核心描述
核心當中一個proc檔案是如何來描述的呢?
struct proc_dir_entry{
………………
read_proc_t *read_proc; //後面講述
write_proc_t *write_proc;
………………
}
建立檔案
struct proc_dir_entry * create_proc_entry(const char* name, mode_t mode,struct proc_dir_entry * parent)
功能:建立proc檔案
引數:——name:要建立的檔名
——mode:要建立的檔案的屬性預設0755
——parent:這個檔案的父目錄
建立目錄
struct proc_dir_entry * proc_mkdir(const char * name, structproc_dir_entry * parent)
功能:建立proc目錄
引數:——name:要建立的目錄名
——parent:這個目錄的父目錄
刪除目錄/檔案
void remove_proc_entry(const char *name , struct proc_dir_entry *parent)
功能:刪除proc目錄或檔案
引數:——name:要刪除的檔案或目錄名
——parent:所在的父目錄
讀寫
為了能讓使用者讀寫新增的proc檔案,需要掛接上讀寫回調函式:read_proc、write_proc
讀操作
int read_func(char * buffer, char **stat, off_t off, int count, int*peof, void *data)
引數:——buffer:把要返回給使用者的資訊寫在buffer裡,最大不超過PAGE_SIZE
——stat:一般不使用
——off:偏移量
——count:使用者讀取的位元組數
——peof:讀到檔案尾時,需要把*peof置1
——data:一般不使用
寫操作
int write_func(struct file *file, const char *buffer, unsigned longcount, void *data)
引數:——file:該proc檔案對應的file結構,一般忽略
——buffer:待寫的資料所在的位置
——count:待寫資料的大小
——data:一般不使用
實現流程
實現一個proc檔案的流程:
(1)呼叫create_proc_entry建立一個struct proc_dir_entry
(2)對建立的struct proc_dir_entry進行賦值:read_proc,mode,owner,size,write_proc等等
例:proc.c
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#define procfs_name "proctest"
struct proc_dir_entry * Our_Proc_File;
int procfile_read(char *buffer,
char **buffer_location,
off_t offset, int buffer_length, int *eof, void *data)
{
int ret;
ret = sprintf(buffer, “Helloworld!\n”);
return ret;
}
int proc_init()
{
Our_Proc_File = create_proc_entry(procfs_name, 0644, NULL);
if (Our_Proc_File == NULL){
remove_proc_entry(procfs_name, NULL);
printk(KERN_ALERT “Error : Could not initialize /proc/%s \n”, procfs_name)
return –ENOMEM;
}
Our_Proc_File->read_proc = procfile_read;
Our_Proc_File->owner = THIS_MODULE;
Our_Proc_File->mode = S_IFREG | S_IRUGO;
Our_Proc_File->uid = 0;
Our_Proc_File->gid = 0;
Our_Proc_File->size = 37;
printk(“/proc/%s created \n”, procfs_name);
return 0;
}
void proc_exit()
{
remove_proc_entry(procfs_name, NULL);
printk(KERN_INFO “/proc/%s removed \n”, procfs_name);
}
module_init(proc_init);
module_exit(proc_exit) ;
proc1.c
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
static struct proc_dir_entry * mydir;
static struct proc_dir_entry *pfile;
static char msg[255];
static int myproc_read(char *page, char **start, off_t off, int count ,int *eof, void * data)
{
int len = strlen(msg);
if (off >= len)
return 0;
if(count > len- off)
count = len – off;
memcpy(page + off, msg + off, count);
return off + count;
}
static int myproc_write(struct file * file, const char __user *buffer,
unsigned long count, void *data)
{
unsigned long count2 = count;
if (count2 >= sizeof(msg))
count2 = sizeof(msg) -1;
if (copy_from_user(msg, buffer, count2))
return –EFAULT;
msg[count2] = ‘\0’;
return count;
}
static int __init myproc_init(void) /*__init:表示該程式碼段位於初始化程式碼段(初始化程式碼段:該段裡的函式只執行一次就會被回收,免得佔用記憶體)*/
{
mkdir = proc_mkdir(“mkdir”, NULL);
if (!mydir){
printk(KERN_ERR “Can’t create /proc/mydir\n”);
return -1;
}
pfile = create_proc_entry(“poll”, 0666, mydir);
if (!pfile){
printk(KERN_ERR “Can’t create /proc/mydir/pool \n”);
remove_proc_entry(“mydir”, NULL);
rerurn -1;
}
pfile->read_proc = myproc_read;
pfile->write_proc = myproc_write;
return 0;
}
static void __exit myproc_exit(void) /*__exit:*/
{
remoce_proc_entry(“pool”, mydir);
remove_proc_entry(“mydir”, NULL);
}
module_init(myproc_init);
module_exit(myproc_exit);
核心異常分析
定義
Oops可以看成是核心級的Segmentation
Fault。應用程式如果進行了非法記憶體訪問或執行了非法指令,會得到Segfault訊號,一般的行為是coredump,應用程式也可以自己截獲Segfault訊號,自行處理。如果核心自己犯了這樣的錯誤,則會打出Oops資訊
(核心中空指標、非法指標都會產生異常。非法指標,訪問地址c0000000(3G)以下的地址都會產生異常!)
分析步驟
1.錯誤原因提示
2.呼叫棧(對照反彙編程式碼)
3.暫存器