1. 程式人生 > >國嵌視訊學習第十一天——核心程序控制

國嵌視訊學習第十一天——核心程序控制

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:程序中止執行。當接收到SIGSTOPSIGSTP等訊號時,程序進入該狀態,接收到SIGCONT訊號後,程序重新回到TASK_RUNNING

5.TASK_KILLABLE

Linux2.6.25新引入的程序睡眠狀態,原理類似於TASK_UNINTERRUPTIBLE,但是可以被致命訊號(SIGKILL)喚醒。

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.4goodness()的計算結果,在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.4nice意義相同。Nice值任沿用linux的傳統,在-2019之間變動,數值越大,程序的優先順序越小。Nice是使用者可維護的,但僅影響非實時程序的優先順序。程序初始時間片的大小僅決定於程序的靜態優先順序,這一點不論是實時程序還是非實時程序都一樣。不過實時程序的static_prio不參與優先順序計算。Nicestatic_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_NORMALSCHED_BATCHSCHED_IDLE

——實時排程類(在kernel/sched_rt.c中實現)用於SCHED_RRSCHED_FIFO策略(看pick_next_task核心函式可以知道,該類的優先順序比CFS排程類高

排程時機

排程什麼時候發生?即:schedule()函式什麼時候被呼叫

排程的發生有兩種方式:

1.主動式:在核心中直接呼叫schedule()。當程序需要等待資源而暫時停止執行時,會把狀態置於掛起(睡眠),並主動請求排程,讓出CPU

例:current->state = TASK_INTERRUPTIBLE;

Schedule();//排程不會選上述程序,因為schedule只會選就緒態的程序

2.被動式(搶佔):使用者搶佔(linux2.4linux2.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檔案系統是一種在使用者態檢驗核心狀態的機制


特點

——每個檔案都規定了嚴格許可權

——可以用文字編譯程式讀取(morecat命令、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_procwrite_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:讀到檔案尾時,需要把*peof1

——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_procmodeownersizewrite_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.暫存器