勇闖新世界︰ 《 Kernel 4.X 》之整裝蓄勢‧事件驅動‧一

古文觀止》‧卷十 ‧ 心術論  蘇洵

為將之道,當先治心。泰山崩於前而色不變,麋鹿興於左而目不瞬 ,然後可以制利害,可以待敵。

凡兵上義,不義,雖利勿動。非一動之為害,而他日將有所不可措手足也。夫惟義可以怒士,士以義怒,可與百戰。

凡戰之道,未戰養其財,將戰養其力,既戰養其氣,既勝養其心。謹烽燧,嚴斥堠,使耕者無所顧忌,所以養其財;豐犒而優游之,所以養其力;小勝益急,小挫益厲,所以養其氣;用人不盡其所欲為,所以養其心。故士常蓄其怒,懷其欲而不盡。怒不盡則有餘勇 ,欲不盡則有餘貪。故雖并天下,而士不厭兵。此黃帝之所以七十戰而兵不殆也。不養其心,一戰而勝,不可用矣。

凡將欲智而嚴,凡士欲愚。智則不可測,嚴則不可犯,故士皆委己而聽命,夫安得不愚?夫惟士愚,而後可與之皆死。

凡兵之動,知敵之主,知敵之將,而後可與動於險。鄧艾縋兵於蜀中,非劉禪之庸,則百萬之師可以坐縛,彼固有所侮而動也。故古之賢將,能以兵嘗敵,而又以敵自嘗,故去就可以決。

凡主將之道,知理而後可以舉兵,知勢而後可以加兵,知節而後可以用兵。知理則不屈,知勢則不沮,知節則不窮。見小利不動,見小患不避,小利小患,不足以辱吾技也,夫然後有以支大利大患。夫惟養技而自愛者,無敵於天下。故一忍可以支百勇,一靜可以制百動。

兵有長短,敵我一也。敢問:「吾之所長,吾出而用之,彼將不與吾校;吾之所短,吾蔽而置之,彼將強與吾角。奈何?」曰:「吾之所短,吾抗而暴之,使之疑而卻;吾之所長,吾陰而養之,使之狎而墮其中。此用長短之術也。」

善用兵者,使之無所顧,有所恃。無所顧,則知死之不足惜;有所恃,則知不至於必敗。尺箠當猛虎,奮呼而操擊;徒手遇蜥蜴,變色而卻步,人之情也。知此者,可以將矣。袒裼而按劍,則烏獲不敢逼;冠冑衣甲,據兵而寢,則童子彎弓殺之矣。故善用兵者,以形固。夫能以形固,則力有餘矣。

『泰山崩於前』且『麋鹿興於左』,眼見目睹可以置若無聞,可以觸而不動,此『定功』之強,可與『言兵』矣!若夫眼耳鼻舌身意常為『外物』所『觸動』而不得定靜,『事件』川流不斷,目眩而神迷,終將淹沒於『萬象』中,不知其所止耶?

『事件驅動』會意容易,鉤深索隱困難,因為萬象駁雜隨機而發,所以『事件』繁亂無序自來。在此就從如何知道『發生了一件事』說起,略探『事件驅動』的基本想法,講講『中斷服務程序』 ISR interrupt service routine 的概念。維基百科『中斷處理』詞條講︰

In computer systems programming, an interrupt handler, also known as an interrupt service routine or ISR, is a callback function in microcontroller firmware, an operating system or a device driver, whose execution is triggered by the reception of an interrupt. In general, interrupts and their handlers are used to handle high-priority conditions that require the interruption of the current code the processor is executing.[1][2]

Interrupt handlers have a multitude of functions, which vary based on the reason the interrupt was generated and the speed at which the interrupt handler completes its task. For example, pressing a key on a computer keyboard,[1] or moving the mouse, triggers interrupts that call interrupt handlers which read the key, or the mouse’s position, and copy the associated information into the computer’s memory.[2]

An interrupt handler is a low-level counterpart of event handlers. These handlers are initiated by either hardware interrupts or interrupt instructions in software, and are used for servicing hardware devices and transitions between protected modes of operation such as system calls.

 

這裡所說的『中斷interrupt 一般是指『事件告知』方法︰

電腦科學中,中斷英語Interrupt)是指處理器接收到來自硬體或軟體的信號,提示發生了某個事件,應該被注意,這種情況就稱為中斷。

通常,在接收到來自外圍硬體(相對於中央處理器記憶體)的非同步訊號,或來自軟體同步訊 號之後,處理器將會進行相應的硬體/軟體處理。發出這樣的訊號稱為進行中斷請求(interrupt request,IRQ)。硬體中斷導致處理器通過一個執行資訊切換(context switch)來儲存執行狀態(以程式計數器和程式狀態字等暫存器資訊為主);軟體中斷則通常作為CPU指令集中的一個指令 ,以可編程的方式直接指示這種 執行資訊切換,並將處理導向一段中斷處理代碼。中斷在電腦多工處理,尤其是即時系統中尤為有用 。這樣的系統,包括執行於其上的作業系統,也被稱為「中斷驅動的」(interrupt-driven)。

Interrupt_Process

 

然而知道『事件發生』不只有一種方法,比方說『輪詢polling

輪詢Polling)是一種CPU決策如何提供週邊設備服務的方式,又稱「程式控制輸出入」(Programmed I/O)。輪詢法的概念是,由CPU定時發出詢問,依序詢問每一個週邊設備是否需要其服務,有即給予服務,服務結束後再問下一個週邊,接著不斷週而復始。

也是一種常見的方式。這兩種方法的主要差異在於,『中斷法』是由『外部』軟硬體『偵測』事件發生了與否,然後才『告知』系統軟體 ;然而『輪詢法』是作業軟體自己『偵測』事件發生了與否。其中『中斷法』因『效能高』常為作業系統所習用。

linux 是一個『多人多工』的作業系統,假使只有一顆 CPU 要如何完成『多人多工』的呢?靠著 CPU 的『分時分享』 time sharing ,背後的主要『軟硬件』就是『Kernel Timer System』核心計時系統的『中斷處理』流程︰

Timer Wheel, Jiffies and HZ

The original kernel timer system (called the “timer wheel) was based on incrementing a kernel-internal value (jiffies) every timer interrupt. The timer interrupt becomes the default scheduling quamtum, and all other timers are based on jiffies. The timer interrupt rate (and jiffy increment rate) is defined by a compile-time constant called HZ. Different platforms use different values for HZ. Historically, the kernel used 100 as the value for HZ, yielding a jiffy interval of 10 ms. With 2.4, the HZ value for i386 was changed to 1000, yeilding a jiffy interval of 1 ms. Recently (2.6.13) the kernel changed HZ for i386 to 250. (1000 was deemed too high).

Ingo Molnar’s explanation of timer wheel performance

Ingo Molnar did an in-depth explanation about the performance of the current “timer wheel” implementation of timers. This was part of a series of messages trying to justify the addition of ktimers (which have different characteristics).

It is possibly the best explanation of the timer wheel avaiable: See http://lkml.org/lkml/2005/10/19/46 and http://lwn.net/Articles/156329/

 

或許因為樹莓派上並沒有真實時鐘『RTC』 Real Time Clock ,因此引發好奇,那個系統『滴答聲』到底怎麼來的︰

[BareMetal] Timer on Raspi

by dom » Sun Jul 01, 2012 9:45 am
There is a GPU timer which has 4 comparison registers, and 4 interrupts.
There is an ARM timer interrupt.
The ARM timer is derived from the GPU core clock, which unless you force it (with core_freq= in config.txt) is variable due to power management, so not much use as a timer.
So, linux uses the GPU timer for its interrupts.
Note the GPU uses timers 0 and 2. 3 is reserved for linux, so would be most suitable for a bare metal OS. 1 is currently unallocated, so could be used.

 

『事件處理』通常建構於『中斷處理』程序之上,維基百科 event handler 詞條裡說了一個模型︰

Delegate event model

A very common and very “programmer-friendly” variant is the delegate event model, which is provided by the most popular graphic frameworks.

Delegate event model. clickme is the event source –a button in this example–, and it contains a list of listeners.

This model is based on three entities:

  • a control, which is the event source,
  • consumers, also called listeners, that receive the events from the source,
  • interfaces (in the broader meaning of the term) that describe the protocol by which every event is to be communicated.

Furthermore, the model requires that

  • every listener must implement the interface for the event it wants to listen to
  • every listener must register with the source to declare its desire to listen to some particular event
  • every time the source generates an event, it communicates it to the registered listeners, following the protocol of the interface.

也許能對『事件處理』程序之內容,多些一般性的了解。

最後就讓我們用 PiTFT 上的四個 GPIO 按鍵為範例,嘗試以派生三來解說什麼是『輪詢法』和『中斷法』︰

【 polling 】

import RPi.GPIO as GPIO
import time

# 採用 BCM 編號制
GPIO.setmode(GPIO.BCM)

# PiTFT GPIO BCM 之編號
入浮針上 = 17
入浮針中上 = 22
入浮針中下 = 23
入浮針下 = 27

# GPIO 設定
GPIO.setup(入浮針上, GPIO.IN)
GPIO.setup(入浮針上, GPIO.IN,pull_up_down = GPIO.PUD_UP)

GPIO.setup(入浮針中上, GPIO.IN)
GPIO.setup(入浮針中上, GPIO.IN,pull_up_down = GPIO.PUD_UP)

GPIO.setup(入浮針中下, GPIO.IN)
GPIO.setup(入浮針中下, GPIO.IN,pull_up_down = GPIO.PUD_UP)

GPIO.setup(入浮針下, GPIO.IN)
GPIO.setup(入浮針下, GPIO.IN,pull_up_down = GPIO.PUD_UP)

while True:

    if GPIO.input(入浮針上) == 0:
        print("按下入浮針上")
    if GPIO.input(入浮針中上) == 0:
        print("按下入浮針中上")
    if GPIO.input(入浮針中下) == 0:
        print("按下入浮針中下")
    if GPIO.input(入浮針下) == 0:
        print("按下入浮針下")

# 其它作業

# 注意間隔時距的給定。太短,同一事件輸出多次;太長,那個事件或被忽略。
    time.sleep(0.1)

 

【 interrupt 】

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)

入浮針上 = 17
入浮針中上 = 22
入浮針中下 = 23
入浮針下 = 27

GPIO.setup(入浮針上, GPIO.IN)
GPIO.setup(入浮針上, GPIO.IN,pull_up_down = GPIO.PUD_UP)

GPIO.setup(入浮針中上, GPIO.IN)
GPIO.setup(入浮針中上, GPIO.IN,pull_up_down = GPIO.PUD_UP)

GPIO.setup(入浮針中下, GPIO.IN)
GPIO.setup(入浮針中下, GPIO.IN,pull_up_down = GPIO.PUD_UP)

GPIO.setup(入浮針下, GPIO.IN)
GPIO.setup(入浮針下, GPIO.IN,pull_up_down = GPIO.PUD_UP)

# 定義總事件處理程式。可以分別定義。
def 回呼程序(來源通道):
    print(來源通道)

# 設定回呼程序。注意 bouncetime 的數值。
GPIO.add_event_detect(入浮針上, GPIO.FALLING, callback=回呼程序, bouncetime=300)
GPIO.add_event_detect(入浮針中上, GPIO.FALLING, callback=回呼程序, bouncetime=300)
GPIO.add_event_detect(入浮針中下, GPIO.FALLING, callback=回呼程序, bouncetime=300)
GPIO.add_event_detect(入浮針下, GPIO.FALLING, callback=回呼程序, bouncetime=300)


while True:
# 其它作業
    time.sleep(1)