2015年8月22日 星期六

Linux Device Drivers

An Introduction to Device Drivers (LDD3 ch1 PDF)
  • The Role of the Device Driver
  • Splitting the Kernel
  • Classes of Devices and Modules
  • Security Issues
  • Version Numbering
  • License Terms
  • Joining the Kernel Development Community
  • Overview of the Book
Building and Running Modules (LDD3 ch2 PDF)
  • 建立測試系統
  • 簡易模組:hello
  • Kernel Modules Versus Applications
  • Compiling and Loading
  • The Kernel Symbol Table
  • Preliminaries
  • Initialization and Shutdown
  • 模組參數
  • Doing It in User Space
  • Quick Reference
  • Linux Modules(1.1)module parameters
Char Drivers (LDD3 ch3 PDF)
  • scull (Simple Character Utility for Loading Localities) 是用一塊取得的記憶體作為 device 的 char driver,有多種形式示範不同功能:
    • scull0 ~ scull3:每個包含 global 且 persistent 的記憶體區塊。global 表示 device 開啟後,記憶體區塊是共享的。persistent 表示 device 關閉後再開啟,資料還是在。可用 cp、cat、shell I/O 轉向等指令存取和 tested。
    • scullpipe0 ~ scullpipe3 (ch6 A Blocking I/O Example):FIFO (first-in-first-out) device,行為類似 pipe,一個 process 讀另一個 process 寫的。如果多個 process 同時讀,競爭取得資料。scullpipe 內部不用中斷實作 blocking and non-blocking read and write,雖然真實 driver 使用硬體中斷同步 device, the topic of blocking and nonblocking operations is an important one and is separate from interrupt handling (covered in Chapter 10).
    • scullsingle, scullpriv, sculluid, scullwuid (ch6 Access Control on a Device File):類似 scull0 但有一些開啟限制。scullsingle 只允許一個 process 使用。scullpriv 對每個 virtual console 或 X terminal session 會取到不同記憶體區域而各自獨立。sculluid 和 scullwuid 同一個 user 可以開啟多次,但如果有其它 user 使用時開啟,前者會回「Device Busy」的錯誤,後者實作會 blocking。雖然看起來混淆 policy 和 mechanism,但一些真實 device 需要這類的管理而值得了解。
  • Major and Minor Numbers
  • Some Important Data Structures:包括 File Operations、The file Structure、The inode Structure
  • Char Device Registration
  • open and release
  • scull’s Memory Usage
  • read and write
  • Playing with the New Devices
  • Quick Reference

Debugging Techniques (LDD3 ch4 PDF)
Concurrency and Race Conditions (LDD3 ch5PDF)
Advanced Char Driver Operations (LDD3 ch6 PDF)

  • 在 LDD Chap. 3 建立了一個可以 read()/write() 的 device driver,但真實的 device 提供的功能不只這樣。現在可以進一步,萬一出現 concurrency 問題造成混亂,也有除錯工具可徹底了解。ioctl() 系統呼叫是 device 的共通控制界面。synchronizing with user space 的各種方式; 如何 put processes to sleep (and wake them up), implement nonblocking I/O, and inform user space when your devices are available for reading or writing. a look at how to implement a few different device access policies within drivers. 這些透過 a couple of modified versions of the scull driver 示範,所有實作仍使用 in-memory virtual devices,而不需要特殊硬體。LDD Chap. 9 才會用到真正的硬體。
  • ioctlioctl()
  • Blocking I/O
  • poll and select
  • Asynchronous Notification
  • Seeking a Device
  • Access Control on a Device File
  • Quick Reference

Time, Delays, and Deferred Work (LDD3 ch7 PDF)

Allocating Memory (LDD3 ch8 PDF)
Communicating with Hardware(LDD3 ch9 PDF)
Interrupt Handling (LDD3 ch10 PDF)
Data Types in the Kernel (LDD3 ch11 PDF)
PCI Drivers (LDD3 ch12 PDF)
USB Drivers (LDD3 ch13 PDF)
The Linux Device Model (LDD3 ch14 PDF)
Memory Mapping and DMA (LDD3 ch15 PDF)
Block Drivers (LDD3 ch16 PDF)
Network Drivers (LDD3 ch17 PDF)
TTY Drivers (LDD3 ch18 PDF)

參考來源

  1. https://lwn.net/Kernel/LDD3/ (Table of ContentsIndex、調整過的範例程式:martinezjavier, jesstess)
  • Free Tutorials: Linux Device Drivers
  • Linux Device Drivers 4th, ISBN 978-1-4493-7161-6, Published 2017 O'Reilly Media, Authors:Jessica McKellarAlessandro RubiniJonathan CorbetGreg Kroah-Hartman

SPI

Serial Port Interface,由 Motorola 開始發展,已變成業界標準,也用在 SD card

一個 SPI bus 包含四個訊號
  • SCLK (SCK)
  • MOSI (Master Out Slave In)
  • MISO (Master In Slave Out):slave 輸出,大部份 slave 不在輸出時可以 tri-state,得以共接。
  • SS:Slave Slect 來選擇一個 slave。
一般 SCLK、MOSI、MISO 共接,每個 slave 有各自的 SS。但也有共用 SS 的接法,採用 daisy chain 或 device 定址。

SCK 不一定要持續送,平常可維持在 high,falling 時送資料,rising 時取樣資料。

基本上 MOSI 跟 MISO 不會同時送訊號 (half-duplex),MISO 大多支援 tri-state,如果 MOSI 也支援 tri-state,這樣可以把 MISO 跟 MOSI 接在一起,變成只有兩條共用線。

MISO tri-state 的時機可能是最後 bit 後或 SS 結束

參考來源
  1. http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus
延伸閱讀

busybox ntpd

busybox ntpd 是 NTP client,也可作為 NTP server。

參數
  • -d:Verbose
  • -n:不常駐
  • -q:(quit) 時間設完後結束
  • -N:高優先權執行
  • -w:不設定時間,只是詢問 peers,隱含 -n
  • -l:作為 NTP server
  • -S PROG:在特定時候執行 PROG,包括時間調整 (stepping time, step)、stratum 等級改變 (stratum)、每 11 分鐘 (period)、及失去同步 (unsync) 時,並透過環境變數 stratum、freq_drift_ppm、poll_interval、及 offset 提供同步狀態。stratum 最大 16,表示沒取到時間。
  • -p PEER:從 PEER 取得時間,可有多個
程式說明
  • ntp_init():初始化及讀取設定
    • stratum = MAXSTRAT
    • poll_exp = BURSTPOLL
    • 如果有 peer add_peers(),否則
  • 主迴圈
    • 預設下次動作時間為
    • 如果亦作為 server,將自己加到 poll 列表
    • 檢查每個 peer
      • 動作時間到了
        • socket 未建立,嘗試送詢問封包
          • resolve peer:失敗 => 下次動作時間 5 秒後
          • socket()、bind()、sendto():失敗 =>下次動作時間 5 秒後
          • 下次動作時間 15 秒 (回覆的 timeout) 後
        • socket 已建立:表示 timeout 了,關閉 socket,下次動作時間加快兩級
      • 紀錄所有 peer 中,最近的詢問時間為下次動作時間
      • 活動中的 socket 加到 poll 列表
    • poll(),如有 peer socket,嘗試 timeout 1 秒,否則依照最近動作時間 + 1 秒作為 timeout。
    • 沒 poll() 到:上次執行 script 超過 11 分鐘則執行 script
    • 有 poll() 到:recv_and_process_client_pkt() 或 recv_and_process_peer_pkt()
    • 檢查 unsync
  • 結束動作
recv_and_process_peer_pkt()
  • 接收封包:如果接收錯誤,用 retry_interval() 設定下次動作時間為隨機 5 或 6 秒後 
  • 封包有誤:忽略
  • 關閉 socket
  • 還未同步:poll_interval(0) 設定下次詢問時間
  • 取得 T1, T2, T3, T4
    • T1:client 發出請求的時間
    • T2:server 收到請求的時間
    • T3:server 回覆的時間
    • T4:client 收到回覆的時間
  • 計算往返的延遲 (delay) = (T4 - T1) - (T3 - T2)
    • 在 server 跟 client 的 clock 跑的速度不同且網路非常快時,算出來有可能是負的,為了避免違反 Principle of Least Astonishment (最小驚訝原則),delay 最小設為系統精度 0.002 秒
  • 往返的延遲如果比上次大四倍以上,則不採用:poll_interval(0) 設定下次動作時間
  • 有八組 datapoint,內容包括 T4、offset = ((T2 - T1) + (T3 - T4)) / 2、及 dispersion = LOG2D(msg.m_precision_exp) + 精度。如果首次 reachable,八組 offset 都設成一樣。
  • filter_datapoints()
    • filter_offset 用最近的 offset 算 filter_dispersion
    • 算 filter_jitter:(平均 offset - 各自 offset) 平方相加後平均,然後開更號 (均方根?),最小為「精度」
  • select_and_cluster() 回傳最佳 server
  • 有回傳最佳 server 時執行 update_local_clock(),除了設定時間外,亦決定詢問週期要調大還是調小
  • 調整詢問週期
  • 設定下次詢問時間:一般是依據誤差 32 秒 ~ 1.1 小時間變動,誤差小週期就變大。如果 offset 變動過大,調整下次動作時間不要大於 32 秒

Linux pipe and FIFO

pipe 是單向的程序間通訊通道,訊息由寫入端寫入,由讀取端讀出。pipe 透過系統呼叫 pipe() 建立,傳回兩個 fd 分別為讀取端跟寫入端。

FIFO 是有名子的 pipe (named pipe),名子就是一個 S_FIFO 的特殊檔案,由 mkfifo() 或 mknod() 所建立。任何 process 只要權限允許,都可以開啟這個檔案讀取 (O_WRONLY) 或寫入 (O_RDONLY) 來交換訊息。FIFO 跟 pipe 只是建立跟開啟的方式不同,後續運作是相同的。雖然 FIFO 是個檔案,但資料實際上並不會寫入背後的檔案系統。

一些特性:
  • open() 會等待,直到讀取端跟寫入端都有開啟了,除非以 O_NONBLOCK 來開啟。
  • read() 時會等待到有資料。如果此時已經沒有任何寫入端,就不會等待,會讀到長度 0。如果開啟為 O_NONBLOCK,就不會等待資料。如果開啟為 O_ASYNC,當有心資料時,讀取端會產生 SIGIO。
  • write() 的訊息如果 ≤ PIPE_BUF = 4096,會一次寫入,如果空間不夠會等候;但開啟為 O_NONBLOCK 的話,就直接回錯誤碼 EAGAIN。Linux 2.6.11 開始,容量有 65536 bytes。如果寫入時沒有任何讀取端,會導致 SIGPIPE 或 EPIPE。

參考來源:
  1. man fifo
  2. man 7 pipe
  3. http://www.gnu.org/software/libc/manual/html_node/Pipe-Atomicity.html
延伸閱讀

OpenWrt netifd

netifd (Network Interface Daemon) 用來做網路設定,相當於一般電腦用的 NetworkManager。netifd 取代 OpenWrt 舊的網路設定指令檔,可以管理複雜的網路設定組合,並處理好相依性而不用做太多無謂的動作,其中一項最大的好處就是當 /etc/config/network 改變時,不用所有界面都重啟,只需要執行 /etc/init.d/network reload,就會發出 ubus 呼叫給 netifd,只針對跟目前狀態的不同處設定。

netifd 是一個具有 RPC 能力的 daemon,以便於透過 netlink events 存取 kernel。

/lib/network/*.sh,
/sbin/ifup
some scripts in /etc/hotplug.d

一般電腦用 glib、dbus、udev,而 OpenWrt 使用 libubox、ubus、hotplug2,不需要龐大的 library 及相依,便能提供不錯的功能。

參考來源:
延伸閱讀:
  • procd -- 取代 busybox initd, klogd, syslogd, watchdog
  • ubox -- 取代 block-mount
  • http://data.pavlix.net/installfest/2014/openwrt-software.pdf
     

IMS PSI

Public Services:IMS 或其它網路的用戶不用特別申請就可以存取的服務。

Public Service Identifier (PSI):公眾服務識別碼,使用 SIP URI (定義在 RFC 3261) 或 tel URI (定義在 RFC 3966)。可分成 distinct PSI 跟 wildcard PSI 兩種,前者是單一識別碼,後者是符合正規表示式的識別碼集合,可簡化節點的運作及維護。

wildcard PSI 在 SIP URI 的使用者部份或 tel URI 的 telephone-subscriber 部份包含一個前後由「!」界定的正規表示式,使用 IEEE 1003.1-2004 Part 1 的第 9 章定義的 Extended Regular Expressions (ERE)

比較:Public User Identifier

參考來源
延伸閱讀

Asterisk LOW_MEMORY

  • 少部份 strncpy() 取代 ast_copy_string()
    • ast_copy_string() 比 strncpy() 多了強制最後複製的位元組填入 0,確保複製的字串有 0 結尾。
  • 省略 lock 除錯機制,core set debug 功能較少
  • AST_BACKGROUND_STACKSIZE 由 240 KiB 變為 48 KiB
  • AST_RTP_MAX_PT 由 196 變為 128
  • AST_INLINE_API 不使用 inline
  • 省略 file version, profile
  • 省略指令 show threads, show sysinfo, show settings
  • NUM_CHANNEL_BUCKETS 由 1567 變為 61
  • 沒有 frame cache
  • CONFIG_OPT_BUCKETS 由 53 變為 5
  • 載入 text 檔的 buffer 由 8192 變為 512
  • EXT_DATA_SIZE 由 8192 變為 256
  • HASH_EXTENHINT_SIZE 由 563 變為 17
  • show dialplan 的 buffer 較小
  • NUM_CACHE_BUCKETS 由 563 變為 17
  • DUNDI
  • LUA_EXT_DATA_SIZE 由 8192 變為 256
  • 載入設定檔的 buffer 較小
  • IAX
  • SIP 的 HASH_PEER_SIZE 跟 HASH_DIALOG_SIZE 由 563 變為 17
  • res_phoneprov.c

ioctl 系統呼叫

驅動程式除了 read()/write() 外,通常可以透過 ioctl() 系統呼叫進行一些控制或設定,如鎖門、退片、回報錯誤、改變 baud rate、或 self destruct 等。

在 user space 格式如下:

#include <sys/ioctl.h>
int ioctl(int fd, unsigned long cmd, ...);

其中 fd 是開的 file descriptor,cmd 是指令碼,和之後可能的參數。在這裡,... 只會有一個選擇性的第三個參數,看 cmd 需要來定,可以沒有、可以是整數、可以是指標傳送任意量的任意資料。

每個驅動程式的 ioctl cmd 基本上是不同的、通常未文件化的,而無法 audit these calls in any sort of comprehensive manner. It is also difficult to make the unstructured ioctl arguments work identically on all systems; for example, consider 64-bit systems with a user-space process running in 32-bit mode. 以致於有實作其它各種方式的控制操作的驅求,包括 embedding commands into the data stream 或使用 sysfs 或驅動程式特有的 virtual filesystems 等。

基本上 cmd 的編碼只要和對應的驅動程式配合好就好,但如果 cmd 編號在整個系統是唯一的,這樣 cmd 發給錯誤的 device 就可以偵測到,不處理而回 EINVAL。為協助建立唯一的 cmd 編號,在 <linux/ioctl.h> 將 cmd 分成幾個欄位:

  • 2-bit dir:bit 0 = write,bit 1 = read
  • 14-bit size:資料長度,並不強制使用,kernel 本身並不檢查。
  • 8-bit type:magic number (神奇數字),Documentation/ioctl-number.txt 有列表,可用來選擇不重複的 magic number。
  • 8-bit number:command number (指令編號)

0x54 is just a magic number to make these relatively unique ('T')

ioctl() 和 read() 或 write() 一樣,可能需要 copy_from_user() 在 user-space 和 kernel-space 之間搬移資料。

如果只是設定的話,第三個參數、甚至 cmd 可直接放設定的資料,只要大小不要超過 unsigned long,未必需要是一個指標指到設定的資料。這樣不用取資料位址,也不用 copy_from_user(),效能會好一點點吧。

ioctl() 錯誤時回傳 -1,並設定 errno。如果只是讀取 1 個或 2 個 byte,也可以直接用回傳正值,不用第三個參數指標指到回傳 buffer,也不用 copy_to_user(),效能會好一點點吧。

Kernel 內部是執行 vfs_ioctl(filp, fd, cmd, arg),如果需要 security 檢查會先執行 security_file_ioctl()。vfs_ioctl() 會處理 cmd FIOCLEX、FIONCLEX、FIONBIO、FIOASYNC、和 FIOQSIZE,這些 cmd 的 magic 使用 'T',有些架構用 'f'。其它 cmd,如果是一般檔案則執行 file_ioctl(),否則執行 do_ioctl()。do_ioctl() 執行 unlocked_ioctl() 或 Big Kernel Lock 執行 ioctl()。

另一個會呼叫 vfs_ioctl() 的地方是 compat_sys_ioctl(),在 64 位元系統才有,有一些相容設計。

在驅動程式的格式是

int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);

inode 和 filp 來自應用程式提供的 file descriptor fd。cmd 不變地來自應用程式。unsigned long arg 來自應用程式的第三個參數,無論是整數或指標、或者無。

大部分驅動程式實作,會有個針對 cmd 的 大 switch,選擇對應的行為,不同 cmd 有不同數值,通常在 header 檔定義成 symbolic names,給驅動程式和應用程式共用。

參考

  1. LDD ch. 6
  2. Linux Kernel

SIP header Via

所有 SIP 訊息 都要有 Via,縮寫 v。一開始的 UAC 和後續途經的每個 proxy 都會疊加一個 Via 放傳送的位址,依序作為回應的路徑。 格式 sent-protocol sent-by [ ;branch= branch ][ ; 參數 ...] s...