王 天 偉
(天津通博視源技術(shù)有限公司 天津 300000)
一種Linux系統(tǒng)下的線程通信方法
王 天 偉
(天津通博視源技術(shù)有限公司 天津 300000)
提出一種結(jié)合數(shù)據(jù)和資源管理的線程通信方法,從數(shù)據(jù)結(jié)構(gòu)上對消息進行定義和描述,并在此基礎(chǔ)上設(shè)計和實現(xiàn)具備消息供給/回收策略的消息工廠。每個消息的內(nèi)部會維護一個棧式數(shù)據(jù)結(jié)構(gòu),用于存放需要傳遞給其他線程的數(shù)據(jù);然后基于POSIX消息隊列實現(xiàn)用于傳遞消息的郵箱。發(fā)送線程從消息工廠取出一條消息并通過對數(shù)據(jù)棧進行壓棧操作將待發(fā)送數(shù)據(jù)提交給消息,然后將消息郵遞到郵箱里,接收線程從郵箱中取出消息并將數(shù)據(jù)依次從消息的數(shù)據(jù)棧中彈出,并在數(shù)據(jù)處理完成后將消息返還給消息工廠。
多線程通信 消息工廠 郵箱
在嵌入式應(yīng)用領(lǐng)域,越來越多的系統(tǒng)設(shè)計方案采用了基于操作系統(tǒng)的軟件架構(gòu),將諸如任務(wù)調(diào)度、硬件資源管理、圖形界面等方面的功能交由操作系統(tǒng)管理。而應(yīng)用軟件設(shè)計本身只需關(guān)注與系統(tǒng)數(shù)據(jù)流相關(guān)的業(yè)務(wù)邏輯和資源調(diào)度即可。Linux系統(tǒng)是一個開源的操作系統(tǒng),底層驅(qū)動越來越完善,在各個行業(yè)也都有比較成熟的開源軟件庫,因而基于Linux系統(tǒng)的開發(fā)具有軟件開發(fā)資源豐富、系統(tǒng)成本低、可定制程度高等特點。多線程常常是軟件設(shè)計中不可回避的問題,線程任務(wù)規(guī)劃、線程之間的同步和數(shù)據(jù)交互方式往往是實現(xiàn)過程中的一個難點,尤其會對后期的調(diào)試和維護帶來重大的影響。本文從基于數(shù)據(jù)流的角度出發(fā),提出了一種Linux系統(tǒng)下的多線程通信方法,并對其實現(xiàn)細節(jié)進行了詳細的論述。
一般出于對系統(tǒng)成本的考慮,應(yīng)用軟件可使用的物理資源往往是有限的,因而在設(shè)計的過程中必須要考慮對與數(shù)據(jù)相關(guān)資源的回收和重復(fù)使用的問題。此外,對于數(shù)據(jù)發(fā)送線程來說其職能只是將數(shù)據(jù)發(fā)送出去,并不關(guān)心數(shù)據(jù)接收線程拿到數(shù)據(jù)會以何種方式進行處理(直接進行數(shù)據(jù)處理或是轉(zhuǎn)發(fā)給其他線程)以及數(shù)據(jù)處理完成的時刻。而且還可能存在多個線程同時(多核處理器)對同一數(shù)據(jù)進行處理的情況。各線程對數(shù)據(jù)的處理時間可能各不相同,因而較難確定釋放資源的時間,所以需要實現(xiàn)一種通知機制,保證應(yīng)用軟件能夠捕獲任意消息所攜帶的數(shù)據(jù)被處理完成的時刻,以便進一步進行相關(guān)處理。這里借用工廠模式設(shè)計思想設(shè)計一個具備消息供給/回收機制的消息工廠,其功能如下:
1) 能夠?qū)崟r提供消息給發(fā)送線程使用,發(fā)送線程得到消息后可向消息中添加數(shù)據(jù)項;
2) 工廠的供給能力(支持的最大消息數(shù))及消息可攜帶的數(shù)據(jù)項的數(shù)量可配置;
3) 接收線程可實時將消息歸還給消息工廠以實現(xiàn)對消息的回收,同時保證工廠在回收消息期間能夠自動釋放與消息內(nèi)各數(shù)據(jù)項相關(guān)聯(lián)的資源;
4) 各資源釋放完成后發(fā)出通知,告知消息所攜帶的數(shù)據(jù)已被處理完成,以便應(yīng)用程序作相關(guān)處理。
定義消息的數(shù)據(jù)結(jié)構(gòu):
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;
};
結(jié)構(gòu)中各成員含義如表1所示。
表1 消息結(jié)構(gòu)描述
續(xù)表1
結(jié)構(gòu)struct msg_ops用于描述對消息中數(shù)據(jù)的操作方法,其定義如下:
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 對消息中數(shù)據(jù)的操作方法
結(jié)構(gòu)struct msg_payload用于描述一個數(shù)據(jù)項,其定義如下:
struct msg_payload
{
void *data;
__s32 len;
};
表3 消息中數(shù)據(jù)項
用一個全局鏈表描述消息工廠,定義如下:
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類型的根節(jié)點和若干條struct message類型的消息組成的,根節(jié)點的prev指向根節(jié)點本身,根節(jié)點的next指向消息工廠內(nèi)第一條消息;每條消息內(nèi)部有存在一個node節(jié)點,其prev指向工廠中上一個消息節(jié)點(對于第一條消息,其prev指向根節(jié)點),next指向工廠中下一個消息節(jié)點(對于最后一條消息,其next指向根節(jié)點)。
消息工廠內(nèi)數(shù)據(jù)組織關(guān)系如圖1所示。
圖1 消息工廠數(shù)據(jù)組織關(guān)系
定義函數(shù)message_factory_create實現(xiàn)(創(chuàng)建消息工廠):
__s32 message_factory_create(__u32 msg_num,
__u32 payload_num)
參數(shù):
msg_num:工廠支持的消息的個數(shù);
payload_num:消息內(nèi)數(shù)據(jù)項的個數(shù)。
成功返回0,失敗返回-1。
創(chuàng)建消息工廠將依次完成以下工作:
1) 創(chuàng)建根節(jié)點(以下稱其為“全局鏈表”),并將其prev和next賦值為根節(jié)點的首地址;
2) 創(chuàng)建msg_num個消息(為struct message分配內(nèi)存),為每個消息創(chuàng)建可以容納payload_num個數(shù)據(jù)項的數(shù)據(jù)棧;
3) 創(chuàng)建兩個指針數(shù)組,分別用于存放與payload_num個數(shù)據(jù)項相對應(yīng)的資源及其釋放方法;
4) 將各個消息依次加入到全局鏈表中。
上述過程的執(zhí)行流程如圖2所示。
圖2 創(chuàng)建消息工廠
定義函數(shù)msg_factory_provide(生產(chǎn)消息):
struct message *msg_factory_provide (__s32 id,
__s32 prio)
參數(shù):
id:郵箱id(指定消息將要進入的郵箱);
prio:指定消息在郵箱中的優(yōu)先級別,接收線程總是先取得最高優(yōu)先級的消息。
執(zhí)行成功返回一條消息,否則返回空指針。
該函數(shù)從全局鏈表頭取出一個節(jié)點給發(fā)送線程,發(fā)送線程根據(jù)業(yè)務(wù)邏輯,向其中加入數(shù)據(jù)項。
定義函數(shù)msg_factory_recycle(回收消息):
void message_factory_recycle(struct message *msg)
參數(shù):
msg:待回收的消息。
該函數(shù)將消息對應(yīng)的節(jié)點加到全局鏈表尾,接收線程在完成對消息處理后通過該函數(shù)實現(xiàn)對消息的回收。其流程如圖3所示。
圖3 消息回收流程
郵箱用于暫存消息,發(fā)送線程和接收線程之間通過郵箱進行數(shù)據(jù)交互,發(fā)送線程對從消息工廠中得到的消息進行加工,即將需要傳遞給接收線程的數(shù)據(jù)壓入消息的數(shù)據(jù)棧中,同時指定與各數(shù)據(jù)項相關(guān)聯(lián)的待釋放資源及其釋放方法,然后將消息發(fā)送到郵箱中;接收線程收到消息后依次將數(shù)據(jù)從數(shù)據(jù)棧中彈出,獲得各數(shù)據(jù)的首地址和有效字節(jié)長度,進而進行相應(yīng)的數(shù)據(jù)處理,當所有數(shù)據(jù)處理完成后調(diào)用函數(shù)msg_factory_recycle將消息歸還給消息工廠。
這里基于POSIX消息隊列設(shè)計了郵箱模塊,具備以下功能:
1) 創(chuàng)建郵箱時可指定其名稱和可暫存的最大消息數(shù);
2) 向郵箱發(fā)送消息時可提供超時時間,以保證當郵箱滿時,能夠及時釋放待發(fā)送數(shù)據(jù)相關(guān)資源能;
3) 當郵箱為空時接收線程阻塞;
4) 郵箱里有消息時,接收線程總是先得到當前優(yōu)先級最高的消息。
定義函數(shù)mailbox_create(創(chuàng)建郵箱):
__s32 mailbox_create(char *name, __s32 msg_num_max)
參數(shù):
name:郵箱名稱;
msg_num_max:郵箱內(nèi)能夠存放的消息數(shù);
執(zhí)行成功返回新創(chuàng)建郵箱的id。
定義函數(shù)mailbox_pend(接收消息):
__s32 mailbox_pend(struct message *msg)
參數(shù):
msg:存放接收消息;
郵箱為空時,接收線程會阻塞,直到有消息到達。
定義函數(shù)mailbox_post(發(fā)送消息),其原型如下:
__s32 mailbox_post(struct message *msg,
__u32 timeout)
參數(shù):
msg:待發(fā)送消息
timeout:超時時間(單位:us)
郵箱未滿時,函數(shù)直接返回,否則發(fā)送線程阻塞,若在超時時間到達之前發(fā)送出去,函數(shù)返回0,否則返回-1。
在進行嵌入式Linux內(nèi)核的剪裁或移植階段,需要保證內(nèi)核中已開啟對POSIX消息隊列的支持,方法是查看內(nèi)核編譯選項的“General setup->POSIX Message Queues”。
在Linux系統(tǒng)下有很多種方式可以用來實現(xiàn)線程之間的數(shù)據(jù)交互,比如共享內(nèi)存、socket、System V IPC或直接使用POSIX消息隊列等。對于文中提到的方法,其優(yōu)點在于能夠通過這樣一種方式從數(shù)據(jù)的角度出發(fā),按照對數(shù)據(jù)處理方式或引用方式的不同將系統(tǒng)功能劃分為可獨立設(shè)計和維護的子功能模塊,業(yè)務(wù)邏輯清晰,而且線程之間的數(shù)據(jù)傳遞是“零拷貝”(拷貝的只是數(shù)據(jù)的首地址)的,所以消息傳遞帶來的時間開銷基本上是可以忽略不計的。這一線程通信方法已成功應(yīng)用于某機器視覺類項目,運行穩(wěn)定,性能表現(xiàn)良好。此外,文中提到的方法雖然是基于Linux系統(tǒng)進行設(shè)計,但也適用于其他操作系統(tǒng),主要區(qū)別在于郵箱的實現(xiàn),不同的操作系統(tǒng)對于郵箱的“底層”支持不同,需要進行一定的調(diào)整或封裝工作。
[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環(huán)境高級編程[M].3版.北京:人民郵電出版社,2014:533-587.
[4] Steven J M,William C W.Java設(shè)計模式[M].2版.北京:電子工業(yè)出版社,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++數(shù)據(jù)結(jié)構(gòu)原理與經(jīng)典問題求解[M].北京:電子工業(yè)出版社,2008:153-199.
[7] 閻宏.Java與模式[M].北京:電子工業(yè)出版社,2002:127-142.
[8] Scott Meyers.Effective C++[M].3版.北京:電子工業(yè)出版社,2006:61-75.
[9] Bruce Powel Douglass.C嵌入式編程設(shè)計模式[M].北京:機械工業(yè)出版社,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。王天偉,碩士,主研領(lǐng)域:工控軟件。