王 天 偉
(天津通博視源技術有限公司 天津 300000)
一種Linux系統下的線程通信方法
王 天 偉
(天津通博視源技術有限公司 天津 300000)
提出一種結合數據和資源管理的線程通信方法,從數據結構上對消息進行定義和描述,并在此基礎上設計和實現具備消息供給/回收策略的消息工廠。每個消息的內部會維護一個棧式數據結構,用于存放需要傳遞給其他線程的數據;然后基于POSIX消息隊列實現用于傳遞消息的郵箱。發送線程從消息工廠取出一條消息并通過對數據棧進行壓棧操作將待發送數據提交給消息,然后將消息郵遞到郵箱里,接收線程從郵箱中取出消息并將數據依次從消息的數據棧中彈出,并在數據處理完成后將消息返還給消息工廠。
多線程通信 消息工廠 郵箱
在嵌入式應用領域,越來越多的系統設計方案采用了基于操作系統的軟件架構,將諸如任務調度、硬件資源管理、圖形界面等方面的功能交由操作系統管理。而應用軟件設計本身只需關注與系統數據流相關的業務邏輯和資源調度即可。Linux系統是一個開源的操作系統,底層驅動越來越完善,在各個行業也都有比較成熟的開源軟件庫,因而基于Linux系統的開發具有軟件開發資源豐富、系統成本低、可定制程度高等特點。多線程常常是軟件設計中不可回避的問題,線程任務規劃、線程之間的同步和數據交互方式往往是實現過程中的一個難點,尤其會對后期的調試和維護帶來重大的影響。本文從基于數據流的角度出發,提出了一種Linux系統下的多線程通信方法,并對其實現細節進行了詳細的論述。
一般出于對系統成本的考慮,應用軟件可使用的物理資源往往是有限的,因而在設計的過程中必須要考慮對與數據相關資源的回收和重復使用的問題。此外,對于數據發送線程來說其職能只是將數據發送出去,并不關心數據接收線程拿到數據會以何種方式進行處理(直接進行數據處理或是轉發給其他線程)以及數據處理完成的時刻。而且還可能存在多個線程同時(多核處理器)對同一數據進行處理的情況。各線程對數據的處理時間可能各不相同,因而較難確定釋放資源的時間,所以需要實現一種通知機制,保證應用軟件能夠捕獲任意消息所攜帶的數據被處理完成的時刻,以便進一步進行相關處理。這里借用工廠模式設計思想設計一個具備消息供給/回收機制的消息工廠,其功能如下:
1) 能夠實時提供消息給發送線程使用,發送線程得到消息后可向消息中添加數據項;
2) 工廠的供給能力(支持的最大消息數)及消息可攜帶的數據項的數量可配置;
3) 接收線程可實時將消息歸還給消息工廠以實現對消息的回收,同時保證工廠在回收消息期間能夠自動釋放與消息內各數據項相關聯的資源;
4) 各資源釋放完成后發出通知,告知消息所攜帶的數據已被處理完成,以便應用程序作相關處理。
定義消息的數據結構:
struct message
{
__s32 id;
__s32 prio;
struct msg_ops *ops;
__s32 (*cb_finish)(void *msg);
__s32 *cb_finish_arg;
void **resource;
void (**release)(void *data);
struct msg_payload *payload;
__s32 payload_num;
__s32 cur_payload_idx;
void *node;
};
結構中各成員含義如表1所示。

表1 消息結構描述

續表1
結構struct msg_ops用于描述對消息中數據的操作方法,其定義如下:
struct msg_ops
{
__s32 (*push)(void *msg, void *data,
__u32 len,
void (*release)(void *resource),
void *resource);
void* (*pop)(void *msg, __s32 *len);
__s32 (*count)(void *msg);
};

表2 對消息中數據的操作方法
結構struct msg_payload用于描述一個數據項,其定義如下:
struct msg_payload
{
void *data;
__s32 len;
};

表3 消息中數據項
用一個全局鏈表描述消息工廠,定義如下:
struct list_head
{
struct list_head *next;
struct list_head *prev;
void *owner;
};
其中owner指向消息,next指向工廠中下一條消息的node成員(見struct message的定義),實際上node的類型是struct list_head;prev指向工廠中上一條消息的node成員。
消息工廠是由一個struct list_head類型的根節點和若干條struct message類型的消息組成的,根節點的prev指向根節點本身,根節點的next指向消息工廠內第一條消息;每條消息內部有存在一個node節點,其prev指向工廠中上一個消息節點(對于第一條消息,其prev指向根節點),next指向工廠中下一個消息節點(對于最后一條消息,其next指向根節點)。
消息工廠內數據組織關系如圖1所示。

圖1 消息工廠數據組織關系
定義函數message_factory_create實現(創建消息工廠):
__s32 message_factory_create(__u32 msg_num,
__u32 payload_num)
參數:
msg_num:工廠支持的消息的個數;
payload_num:消息內數據項的個數。
成功返回0,失敗返回-1。
創建消息工廠將依次完成以下工作:
1) 創建根節點(以下稱其為“全局鏈表”),并將其prev和next賦值為根節點的首地址;
2) 創建msg_num個消息(為struct message分配內存),為每個消息創建可以容納payload_num個數據項的數據棧;
3) 創建兩個指針數組,分別用于存放與payload_num個數據項相對應的資源及其釋放方法;
4) 將各個消息依次加入到全局鏈表中。
上述過程的執行流程如圖2所示。

圖2 創建消息工廠
定義函數msg_factory_provide(生產消息):
struct message *msg_factory_provide (__s32 id,
__s32 prio)
參數:
id:郵箱id(指定消息將要進入的郵箱);
prio:指定消息在郵箱中的優先級別,接收線程總是先取得最高優先級的消息。
執行成功返回一條消息,否則返回空指針。
該函數從全局鏈表頭取出一個節點給發送線程,發送線程根據業務邏輯,向其中加入數據項。
定義函數msg_factory_recycle(回收消息):
void message_factory_recycle(struct message *msg)
參數:
msg:待回收的消息。
該函數將消息對應的節點加到全局鏈表尾,接收線程在完成對消息處理后通過該函數實現對消息的回收。其流程如圖3所示。

圖3 消息回收流程
郵箱用于暫存消息,發送線程和接收線程之間通過郵箱進行數據交互,發送線程對從消息工廠中得到的消息進行加工,即將需要傳遞給接收線程的數據壓入消息的數據棧中,同時指定與各數據項相關聯的待釋放資源及其釋放方法,然后將消息發送到郵箱中;接收線程收到消息后依次將數據從數據棧中彈出,獲得各數據的首地址和有效字節長度,進而進行相應的數據處理,當所有數據處理完成后調用函數msg_factory_recycle將消息歸還給消息工廠。
這里基于POSIX消息隊列設計了郵箱模塊,具備以下功能:
1) 創建郵箱時可指定其名稱和可暫存的最大消息數;
2) 向郵箱發送消息時可提供超時時間,以保證當郵箱滿時,能夠及時釋放待發送數據相關資源能;
3) 當郵箱為空時接收線程阻塞;
4) 郵箱里有消息時,接收線程總是先得到當前優先級最高的消息。
定義函數mailbox_create(創建郵箱):
__s32 mailbox_create(char *name, __s32 msg_num_max)
參數:
name:郵箱名稱;
msg_num_max:郵箱內能夠存放的消息數;
執行成功返回新創建郵箱的id。
定義函數mailbox_pend(接收消息):
__s32 mailbox_pend(struct message *msg)
參數:
msg:存放接收消息;
郵箱為空時,接收線程會阻塞,直到有消息到達。
定義函數mailbox_post(發送消息),其原型如下:
__s32 mailbox_post(struct message *msg,
__u32 timeout)
參數:
msg:待發送消息
timeout:超時時間(單位:us)
郵箱未滿時,函數直接返回,否則發送線程阻塞,若在超時時間到達之前發送出去,函數返回0,否則返回-1。
在進行嵌入式Linux內核的剪裁或移植階段,需要保證內核中已開啟對POSIX消息隊列的支持,方法是查看內核編譯選項的“General setup->POSIX Message Queues”。
在Linux系統下有很多種方式可以用來實現線程之間的數據交互,比如共享內存、socket、System V IPC或直接使用POSIX消息隊列等。對于文中提到的方法,其優點在于能夠通過這樣一種方式從數據的角度出發,按照對數據處理方式或引用方式的不同將系統功能劃分為可獨立設計和維護的子功能模塊,業務邏輯清晰,而且線程之間的數據傳遞是“零拷貝”(拷貝的只是數據的首地址)的,所以消息傳遞帶來的時間開銷基本上是可以忽略不計的。這一線程通信方法已成功應用于某機器視覺類項目,運行穩定,性能表現良好。此外,文中提到的方法雖然是基于Linux系統進行設計,但也適用于其他操作系統,主要區別在于郵箱的實現,不同的操作系統對于郵箱的“底層”支持不同,需要進行一定的調整或封裝工作。
[1] Chris Simmonds.Mastering Embedded Linux Programming[M].Birmingham:Packt Publishing Ltd,2015:247-265.
[2] Alex González.Embedded Linux Projects Using Yocto Project Cookbook[M].Birmingham:Packt Publishing Ltd,2015:185-192.
[3] 史蒂文斯, 拉戈.UNIX環境高級編程[M].3版.北京:人民郵電出版社,2014:533-587.
[4] Steven J M,William C W.Java設計模式[M].2版.北京:電子工業出版社,2012:158-164.
[5] Texas Instruments Incorporated.TMS320C6000 DSP/BIOS 5.x Application Programming Interface (API) Reference Guide[EB/OL].2012.http://www.ti.com/lit/ug/spru403s/spru403s.pdf.
[6] 左飛.C++數據結構原理與經典問題求解[M].北京:電子工業出版社,2008:153-199.
[7] 閻宏.Java與模式[M].北京:電子工業出版社,2002:127-142.
[8] Scott Meyers.Effective C++[M].3版.北京:電子工業出版社,2006:61-75.
[9] Bruce Powel Douglass.C嵌入式編程設計模式[M].北京:機械工業出版社,2012:116-198.
[10] Texas Instruments Incorporated.SYS/BIOS (TI-RTOS Kernel) v6.46 User’s Guide[EB/OL].2016.http://www.ti.com/lit/ug/spruex3q/spruex3q.pdf.
AMETHODOFCOMMUNICATIONBETWEENTHREADSUNDERLINUXSYSTEM
Wang Tianwei
(TianjinTongboshiyuanTechnologyCo.,Ltd.,Tianjin300000,China)
A multi-thread communication method based on data and resource management is proposed. The concept of message is defined by a data structure, on the basis of which a message factory with supply/recycling strategy is designed and realized. First, each message maintained a stack of data structure, which was used to store the data that need to be passed to other threads. Afterwards, the mailbox based on POSIX message queue was implemented for passing messages. The sending thread took a message from the message factory and the data was submitted to the message by pushing onto a data stack. Then the message would be sent to a mailbox. The receiving thread fetched a message from the mailbox and popped the data sequentially from the message stack. At last the message would be returned to the message factory after the data had been processed.
Multi-thread communication Message factory Mailbox
TP3
A
10.3969/j.issn.1000-386x.2017.10.059
2016-09-01。王天偉,碩士,主研領域:工控軟件。