偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

OS內(nèi)核的信號(hào)機(jī)制:所有的異步都可以是同步的

系統(tǒng) 其他OS
每個(gè)進(jìn)程,在OS內(nèi)核里都被一個(gè)task結(jié)構(gòu)體表示,這個(gè)結(jié)構(gòu)體的其中一個(gè)成員變量就是記錄信號(hào)的:我們給他起名叫sigmap,Linux的不一定要叫這個(gè)名字,但肯定有這一項(xiàng)。

今天條友@xiamenuser給我提了一個(gè)關(guān)于操作系統(tǒng)的問(wèn)題:怎么把定時(shí)器線程里的回調(diào)函數(shù),(在定時(shí)器觸發(fā)之后)挪到工作線程里運(yùn)行?

這個(gè)需求要做的事,跟Linux內(nèi)核的信號(hào)機(jī)制是一樣的。

OS內(nèi)核的信號(hào)機(jī)制,在1970年的Unix時(shí)代就有了,是一個(gè)上古話題。

在unix里,可以使用kill -9 pid命令殺掉進(jìn)程(pid為進(jìn)程號(hào)),在Linux里也可以。

1.OS內(nèi)核的信號(hào)

有個(gè)專有的宏定義#define SIGKILL 9,然后信號(hào)9就成了一個(gè)特別牛的信號(hào),大概除了0號(hào)idle進(jìn)程和1號(hào)init進(jìn)程之外,其他進(jìn)程都可以殺死。

0號(hào)進(jìn)程和1號(hào)進(jìn)程是不能殺死的,否則系統(tǒng)就崩潰了!

int sys_kill(int sig, int pid)
{
if (sig < 0 || pid < 0)
return -EINVAL;
if (0 == pid || 1 == pid) {
if (SIGKILL == sig) return -1;
}
tasks[pid]->sigmap |= 1 << sig;
return 0;
}

OS內(nèi)核里對(duì)應(yīng)著kill命令的sys_kill()系統(tǒng)調(diào)用,大概是上面這樣:

在進(jìn)程的task結(jié)構(gòu)體的sigmap成員變量上,設(shè)置1個(gè)標(biāo)志位,進(jìn)程就可以收到信號(hào)了。

每個(gè)進(jìn)程,在OS內(nèi)核里都被一個(gè)task結(jié)構(gòu)體表示,這個(gè)結(jié)構(gòu)體的其中一個(gè)成員變量就是記錄信號(hào)的:我們給他起名叫sigmap,Linux的不一定要叫這個(gè)名字,但肯定有這一項(xiàng)。

這個(gè)信號(hào)在什么時(shí)候處理呢?

等到收信號(hào)的進(jìn)程下一次被調(diào)度運(yùn)行的時(shí)候。

當(dāng)前運(yùn)行的進(jìn)程,肯定是發(fā)信號(hào)的進(jìn)程,否則它沒(méi)法主動(dòng)發(fā)起kill()系統(tǒng)調(diào)用。

發(fā)信號(hào)的進(jìn)程做的事,只是把信號(hào)設(shè)置到接收進(jìn)程的信號(hào)圖上,這時(shí)信號(hào)實(shí)際上已經(jīng)發(fā)到了:但是接收進(jìn)程并不會(huì)馬上因?yàn)镾IGKILL信號(hào)而被殺死。

SIGKILL信號(hào)的殺進(jìn)程,實(shí)際上進(jìn)程是自殺的!

當(dāng)收到信號(hào)的進(jìn)程再次被調(diào)度運(yùn)行的時(shí)候,操作系統(tǒng)會(huì)讓它先執(zhí)行信號(hào)的處理函數(shù),而SIGKILL的處理函數(shù),就是exit()系統(tǒng)調(diào)用:進(jìn)程退出。

這個(gè)過(guò)程可以是異步的,等到接收進(jìn)程下一次被調(diào)度時(shí)再處理,至于什么時(shí)候輪到它:等吧。

也可以讓它馬上同步處理,只需要在sys_kill()函數(shù)的末尾加一行代碼就行:

shedule_task( tasks[pid] );

直接選擇接收進(jìn)程是下一個(gè)要調(diào)度的進(jìn)程,并且馬上調(diào)度它運(yùn)行:接下來(lái)它就完事了。

不需要等OS內(nèi)核統(tǒng)計(jì)時(shí)間片,確定調(diào)度的優(yōu)先級(jí)了,既然用戶想讓它掛掉,OS當(dāng)然要馬上讓它掛掉。

畢竟Linux系統(tǒng)也惹不起用戶啊,用戶是可以重裝windows的?

接下來(lái),說(shuō)說(shuō)shedule_task()之后的細(xì)節(jié)。

2.信號(hào)是怎么處理的

每個(gè)信號(hào)都有一個(gè)處理函數(shù),叫信號(hào)處理函數(shù)。

信號(hào)處理函數(shù),是在用戶態(tài)的代碼里運(yùn)行的。

所以,程序員可以自己給部分信號(hào)編寫處理函數(shù),用signal()系統(tǒng)調(diào)用注冊(cè)到OS內(nèi)核,就可以(在收到信號(hào)時(shí))運(yùn)行這個(gè)自己編寫的函數(shù)了。

如果信號(hào)處理函數(shù)是在內(nèi)核狀態(tài)運(yùn)行的,那顯然用戶編寫的函數(shù)是沒(méi)法運(yùn)行的,因?yàn)橛脩艉瘮?shù)的內(nèi)存地址在用戶空間(它在進(jìn)程的代碼段里)。

OS內(nèi)核在信號(hào)處理時(shí)要做的是,把進(jìn)程從內(nèi)核返回后要運(yùn)行的代碼地址,改成信號(hào)處理函數(shù)的地址。

修改過(guò)程如下:

系統(tǒng)內(nèi)核的信號(hào)處理過(guò)程

1)進(jìn)程從內(nèi)核返回時(shí)的狀態(tài),如上圖。

內(nèi)核棧上的寄存器排布順序不一定是對(duì)的,這要查intel的手冊(cè),但是這些項(xiàng)肯定都有。

在進(jìn)程使用iret指令(中斷返回)從內(nèi)核返回的那一刻,內(nèi)核棧上的這些數(shù)據(jù)都要彈出到對(duì)應(yīng)的寄存器。

然后,進(jìn)程就會(huì)運(yùn)行EIP指向的用戶代碼,同時(shí)用戶態(tài)的棧頂就是ESP。

EIP和ESP指向的內(nèi)容到底是什么,內(nèi)核不需要管:這是由程序員寫代碼時(shí)確定的。

進(jìn)程從內(nèi)核返回之后的錯(cuò)誤,錯(cuò)的是程序員,不是系統(tǒng)內(nèi)核。

但要是返不回來(lái),或者不能處理信號(hào),錯(cuò)的就是系統(tǒng)內(nèi)核了。

2)OS內(nèi)核要做的是,修改內(nèi)核棧上、保存的、用戶態(tài)的、EIP和ESP(注意這3個(gè)定語(yǔ)):

A,讓EIP指向信號(hào)處理函數(shù),

B,讓ESP指向信號(hào)處理函數(shù)的參數(shù),

C,在信號(hào)處理函數(shù)的下方,放上“真正的”返回地址,

D,在信號(hào)處理函數(shù)運(yùn)行完之后,丟掉(信號(hào)處理函數(shù)的)參數(shù),彈出真正的返回地址:讓程序恢復(fù)正常的狀態(tài),繼續(xù)運(yùn)行。

如上圖中的綠字部分。

如果一次要處理多個(gè)信號(hào)的話,就順著用戶棧繼續(xù)疊加就行。

siska內(nèi)核demo里的信號(hào)處理代碼,如下的3張圖:

因?yàn)樾盘?hào)處理函數(shù)有參數(shù),而參數(shù)要壓在用戶態(tài)的棧上,所以信號(hào)處理函數(shù)運(yùn)行完之后還要清理它。

所以,與一般的C函數(shù)不同,信號(hào)處理函數(shù)是被調(diào)函數(shù)清理堆棧的:即它是pascal調(diào)用,而不是C調(diào)用!

C調(diào)用,都是主調(diào)函數(shù)清理堆棧的。

所以,信號(hào)處理函數(shù)的總?cè)肟谑且欢螀R編代碼,用來(lái)在C語(yǔ)言里完成這個(gè)pascal調(diào)用。

這么看來(lái),pascal這種老語(yǔ)言,也不是想象的那么差?

這個(gè)信號(hào)處理方式,是我給出來(lái)的解決方案?

至于Linux是不是也這么做的,我就不知道了。

但是,這么做是可行的。

siska信號(hào)處理,pascal調(diào)用的匯編

上圖95行的call *(%eax),就是調(diào)用信號(hào)處理的函數(shù)指針。

它前后的匯編代碼,都是準(zhǔn)備參數(shù)和清理堆棧。

3.回到開頭的問(wèn)題

怎么讓定時(shí)器線程在觸發(fā)之后,讓回調(diào)函數(shù)在工作線程里運(yùn)行?

回調(diào)函數(shù)一般有一個(gè)參數(shù),表示回調(diào)上下文,但沒(méi)有返回值。

因?yàn)槎〞r(shí)器的添加和處理在2個(gè)線程里,回調(diào)函數(shù)的返回值沒(méi)有意義。

如果回調(diào)函數(shù)的處理出錯(cuò)了,就在上下文里設(shè)置錯(cuò)誤碼作為提示。

所以,它的函數(shù)聲明是這樣的:void callback(void* ctx);

要讓它正常運(yùn)行,必須把回調(diào)上下文的指針添加到工作線程的用戶棧上,同時(shí)讓工作線程的內(nèi)核棧上保存的EIP指向回調(diào)函數(shù)。

這個(gè)處理方式,與OS內(nèi)核的信號(hào)處理方式是一樣的。

信號(hào)處理函數(shù)的聲明:void sighandler(int sig); 也是一個(gè)參數(shù)、無(wú)返回值。

在定時(shí)器觸發(fā)之后,定時(shí)器線程可以發(fā)起一個(gè)系統(tǒng)調(diào)用,把這些信息給到內(nèi)核,然后內(nèi)核修改工作線程的數(shù)據(jù),讓定時(shí)器的回調(diào)處理“像個(gè)信號(hào)”一樣就可以了?

這個(gè)系統(tǒng)調(diào)用如果Linux沒(méi)有提供的話,就只能自己修改Linux內(nèi)核代碼,或者給Linus大牛提個(gè)需求了(他有可能看不過(guò)來(lái)你的郵件)。

PS:

工作線程和定時(shí)器線程在同一個(gè)進(jìn)程里,所以它們的用戶態(tài)內(nèi)存的代碼段、數(shù)據(jù)段、堆都是共享的,只是內(nèi)核棧和用戶棧不一樣。

內(nèi)核棧:在內(nèi)核看來(lái),每個(gè)線程也是一個(gè)可調(diào)度的進(jìn)程,它必須有自己的內(nèi)核棧和頁(yè)表。

同一個(gè)進(jìn)程的不同線程之間共享內(nèi)存,靠的是頁(yè)表的映射:把它們映射到同一個(gè)物理內(nèi)存頁(yè)上。

用戶棧:不同的線程可以并發(fā)運(yùn)行,它們的用戶??隙ㄊ遣煌?,否則局部變量就互相覆蓋了:這肯定是不可能的。

siska里信號(hào)處理的代碼,如下:

siska信號(hào)處理,1

siska信號(hào)處理,2

責(zé)任編輯:武曉燕 來(lái)源: 今日頭條
相關(guān)推薦

2023-04-03 09:44:18

2025-02-03 07:00:00

2011-07-05 18:40:19

QT 信號(hào) 機(jī)制

2024-07-16 10:52:09

2011-07-05 18:32:52

QT 信號(hào) 機(jī)制

2009-07-21 09:31:00

Scala操作符

2025-04-29 06:53:36

2021-12-10 00:01:53

Vsync信號(hào)機(jī)制

2017-05-15 11:39:36

2018-02-25 09:00:00

LinuxQ4OS開源

2011-10-11 16:57:13

windows8TechEd 2011

2022-03-31 08:15:59

遞歸代碼非遞歸

2009-11-12 09:51:11

Visual C++項(xiàng)

2014-09-24 13:04:13

微信企業(yè)號(hào)

2022-03-11 11:40:26

AI數(shù)據(jù)技術(shù)

2022-03-07 10:27:03

Linux開源社區(qū)

2013-04-08 12:41:35

JavaScriptJS

2011-06-17 14:36:50

Linux

2023-03-20 18:34:02

營(yíng)銷大促質(zhì)量保障穩(wěn)定性

2017-09-14 09:40:32

PythonUbuntu信號(hào)機(jī)制
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)