版權聲明:本文為本文為博主原創文章,轉載請注明出處。如有錯誤,歡迎指正。
1. 引出問題
上一篇文章xenomai內核解析--雙核系統調用(一)以X86處理器為例,分析了xenomai內核調用的流程,讀了以后可能會覺得缺了點什么,你可能會有以下疑問:
- 系統中的兩個內核都是POSIX接口實現系統調用,那么我寫一個POSIX接口的應用程序,怎樣知道它調用的內核,或者說怎樣成為運行在cobalt內核的RT應用,而不是普通linux應用?
- 對於同一個POSIX接口,可能我的程序中,既需要xenomai內核提供服務(xenomai 系統調用),又需要調用linux內核提供服務(linux內核系統調用),或者說既有libcobalt,又有glibc庫,他們是如何實現或區分的?
2. 編譯鏈接
對於問題1,答案是:由編譯的鏈接過程決定,鏈接的庫不同當然執行的也就不同,如果普通的編譯,則該應用編譯后是一個普通linux運用。如果要編譯為xenomai應用,則需要鏈接到xenomai庫,但鏈接時是通過符號表(symbol)來鏈接的,當然也就要從代碼符號(symbol)入手,我們會有疑惑xenomai是如何狸貓換太子的?首先來看一個常用的編譯xenomai 應用的makefile:
XENO_CONFIG := /usr/xenomai/bin/xeno-config
PROJPATH = .
CFLAGS := $(shell $(XENO_CONFIG) --posix --alchemy --cflags)
LDFLAGS := $(shell $(XENO_CONFIG) --posix --alchemy --ldflags)
INCFLAGS= -I$(PROJPATH)/include/
EXECUTABLE := rt-app
src = $(wildcard ./*.c)
obj = $(patsubst %.c, %.o, $(src))
all: $(EXECUTABLE)
$(EXECUTABLE): $(obj)
$(CC) -g -o $@ $^ $(INCFLAGS) $(CFLAGS) $(LDFLAGS)
%.o:%.c
$(CC) -g -o $@ -c $< $(INCFLAGS) $(CFLAGS) $(LDFLAGS)
.PHONY: clean
clean:
rm -f $(EXECUTABLE) $(obj)
其中最重要的就是編譯時需要xeno-config
來生成gcc參數。xeno-config
在我們編譯安裝xenomai庫后,默認放在/usr/bin/xeno-config
。
$ /usr/bin/xeno-config --help
xeno-config --verbose
--core=cobalt
--version="3.1"
--cc="gcc"
--ccld="/usr/bin/wrap-link.sh gcc"
--arch="x86"
--prefix="/usr"
--library-dir="/usr/lib"
Usage xeno-config OPTIONS
Options :
--help
--v,--verbose
--version
--cc
--ccld
--arch
--prefix
--[skin=]posix|vxworks|psos|alchemy|rtdm|smokey|cobalt
--auto-init|auto-init-solib|no-auto-init
--mode-check|no-mode-check
--cflags
--ldflags
--lib*-dir|libdir|user-libdir
--core
--info
--compat
例如編譯一個POSIX接口的實時應用,參數--cflags
表示編譯,指定接口(skin)--posix
,就能得到編譯該程序的gcc參數了,看着沒什么特別的:
$ /usr/bin/xeno-config --posix --cflags
-I/usr/include/xenomai/cobalt -I/usr/include/xenomai -D_GNU_SOURCE -D_REENTRANT -fasynchronous-unwind-tables -D__COBALT__ -D__COBALT_WRAP__
再看鏈接,--ldflags
表示鏈接,如下得到鏈接參數:
$ /usr/bin/xeno-config --ldflags --posix
-Wl,--no-as-needed -Wl,@/usr/lib/cobalt.wrappers -Wl,@/usr/lib/modechk.wrappers /usr/lib/xenomai/bootstrap.o -Wl,--wrap=main -Wl,--dynamic-list=/usr/lib/dynlist.ld -L/usr/lib -lcobalt -lmodechk -lpthread -lrt
這一看就多出不少東西,重點就在這cobalt.wrappers
、modechk.wrappers
,兩個文件的內容如下:
...
--wrap open
--wrap open64
--wrap socket
--wrap close
--wrap ioctl
--wrap read
....
--wrap recv
--wrap send
--wrap getsockopt
--wrap setsockop
...
里面是一些posix系統調用,前面的--wrap
是什么作用?這是執行鏈接過程的程序ld
的一個參數,通過man ld
可以找到該參數的說明:
--wrap symbol
Use a wrapper function for symbol. Any undefined reference to symbol will be resolved to "__wrap_symbol". Any undefined reference to "__real_symbol" will be resolved to symbol.
This can be used to provide a wrapper for a system function. The wrapper function should be called "__wrap_symbol". If it wishes to call the system function, it should call "__real_symbol".
Here is a trivial example:
void *
__wrap_malloc (size_t c)
{
printf ("malloc called with %zu\n", c);
return __real_malloc (c);
}
If you link other code with this file using --wrap malloc, then all calls to "malloc" will call the function "__wrap_malloc" instead. The call to "__real_malloc" in "__wrap_malloc" will call the real "malloc"
function.
You may wish to provide a "__real_malloc" function as well, so that links without the --wrap option will succeed. If you do this, you should not put the definition of "__real_malloc" in the same file as
"__wrap_malloc"; if you do, the assembler may resolve the call before the linker has a chance to wrap it to "malloc".
簡單來說就是:任何 對symbol
未定義 的 引用 (undefined reference) 將 解析為 __wrap_symbol
. 任何 對__real_symbol
未定義 的 引用 將 解析為 symbol
。意思就是我們代碼里使用到且是參數--wrap
指定的符號symbol
(即文件里的內容),鏈接的時候就認為它是__wrap_symbol
,比如我們代碼用到了open()
在鏈接的時候是與庫中的__wrap_open()
鏈接的,__wrap_open
就是在xenomai 實時庫libcobalt中實現,我們還有另一個問題,既然鏈接到了libcobalt,但我們是要Linux來服務,又是怎么讓Linux來提供服務的呢?看libcobalt具體實現可以知道答案。
對於
xeno-config
的其他更多參數可通過xenomai Manual Page了解。
這樣就將POSIX接口源碼編譯成一個xenomai可執行程序了。通常我們會將獲取編譯參數的操作直接放到Makefile里,編譯時直接執行獲取使用,這里給一個簡單的Makefile示例如下:
3. libcobalt中的實現
下面來看問題2,既然我們已將一個接口鏈接到實時內核庫libcobalt,當然由實時內核庫libcobalt來區分該發起linux內核調用還是xenomai內核系統。與上一篇文章一樣,以一個POSIX接口pthread_cretate()
來解析libcobalt中的實現。
xenomai線程的創建流程比較復雜,需要先讓linux創建普通線程,然后再由xenomai創建該線程的shadow 線程,即xenomai調度的實時線程,很符合我們上面的提出的問題2。 說到這先簡答介紹一下xenomai實時線程的創建,詳細的創建流程后面會寫專門寫一篇文章解析,敬請期待。
pthread_cretate()
不是一個系統調用,由NPTL(Native POSIX Threads Library)實現(NPTL是Linux 線程實現的現代版,由UlrichDrepper 和Ingo Molnar 開發,以取代LinuxThreads),NPTL負責一個用戶線程的用戶空間棧創建、內存分配、初始化等工作,與linux內核配合完成線程的創建。每一線程映射一個單獨的內核調度實體(KSE,Kernel Scheduling Entity)。內核分別對每個線程做調度處理。線程同步操作通過內核系統調用實現。
xenomai coblat作為實時任務的調度器,每個實時線程需要對應到 coblat調度實體,如果要創建實時線程就需要像linux那樣NPTL與linux 內核深度結合,那么coblat與libcoblat實現將會變得很復雜。在這里,xenomai使用了一種方式,由NPTL方式去完成實時線程實體的創建(linux部分),在普通線程的基礎上附加一些屬性,對應到xenomai cobalt內核實體時能被實時內核cobalt調度。
所以libcoblat庫中的實時線程創建函數pthread_cretate
最后還是需要使用 glibc的pthread_cretate
函數,xenomai只是去擴展glibc pthread_cretate
創建的線程,使這個線程可以在實時內核cobalt調度。
pthread_cretate()
在libcobalt中pthread.h文件中定義如下:
COBALT_DECL(int, pthread_create(pthread_t *ptid_r,
const pthread_attr_t *attr,
void *(*start) (void *),
void *arg));
COBALT_DECL
宏在wrappers.h
中如下,展開上面宏,會為pthread_create()
生成三個類型函數:
#define __WRAP(call) __wrap_ ## call
#define __STD(call) __real_ ## call
#define __COBALT(call) __cobalt_ ## call
#define __RT(call) __COBALT(call)
#define COBALT_DECL(T, P) \
__typeof__(T) __RT(P); \
__typeof__(T) __STD(P); \
__typeof__(T) __WRAP(P)
int __cobalt_pthread_create(pthread_t *ptid_r,
const pthread_attr_t *attr,
void *(*start) (void *),
void *arg);
int __wrap_pthread_create(pthread_t *ptid_r,
const pthread_attr_t *attr,
void *(*start) (void *),
void *arg);
int __real_pthread_create(pthread_t *ptid_r,
const pthread_attr_t *attr,
void *(*start) (void *),
void *arg);
聲明pthread_create()
函數的這三個宏意思為:
-
__RT(P):
__cobalt_pthread_create
明確表示Cobalt實現的POSIX函數 -
__COBALT(P):與
__RT()
等效。 -
__STD(P):
__real_pthread_create
表示這是原始的POSIX函數(Linux glibc實現),cobalt庫內部通過它來表示調用原始的POSIX函數(glibc NPTL). -
__WRAP(P):
__wrap_pthread_create
是__cobalt_pthread_create
的弱別名,如果編譯器編譯時知道有該函數其它的實現,該函數就會被覆蓋。
主要關注前面兩個,對於最后一個宏,如果外部庫想覆蓋已有的函數,應提供其自己的__wrap_pthread_create()
實現,來覆蓋Cobalt實現的pthread_create()
版本。 原始的Cobalt實現仍可以引用為__COBALT(pthread_create)
。由宏COBALT_IMPL來定義:
#define COBALT_IMPL(T, I, A) \
__typeof__(T) __wrap_ ## I A __attribute__((alias("__cobalt_" __stringify(I)), weak)); \
__typeof__(T) __cobalt_ ## I A
最后cobalt庫函數pthread_create
實現主體為(xenomai3.x.x\lib\cobalt\thread.c):
COBALT_IMPL(int, pthread_create, (pthread_t *ptid_r,
const pthread_attr_t *attr,
void *(*start) (void *), void *arg))
{
pthread_attr_ex_t attr_ex;
......
return pthread_create_ex(ptid_r, &attr_ex, start, arg);
}
COBALT_IMPL定義了__cobalt_pthread_create
函數及該函數的一個弱別名__wrap_pthread_create
,調用這兩個函數執行的是同一個函數體。
對於 NPTL函數pthread_create
,在Cobalt庫里使用__STD()
修飾,展開后即__real_pthread_create()
,其實只是NPTL pthread_create()
的封裝,__real_pthread_create()
會直接調用 NPTL pthread_create
,在lib\cobalt\wrappers.c實現如下:
/* pthread */
__weak
int __real_pthread_create(pthread_t *ptid_r,
const pthread_attr_t * attr,
void *(*start) (void *), void *arg)
{
return pthread_create(ptid_r, attr, start, arg);
}
它調用的就是glibc中的pthread_create
函數.同樣我們接着__cobalt_pthread_create()
看哪里調用的.
int pthread_create_ex(pthread_t *ptid_r,
const pthread_attr_ex_t *attr_ex,
void *(*start) (void *), void *arg)
{
......
__STD(sem_init(&iargs.sync, 0, 0));
ret = __STD(pthread_create(&lptid, &attr, cobalt_thread_trampoline, &iargs));/*__STD 調用標准庫的函數*/
if (ret) {
__STD(sem_destroy(&iargs.sync));
return ret;
}
__STD(clock_gettime(CLOCK_REALTIME, &timeout));
.....
}
下面再看另一個例子,實時任務在代碼中使用了linux的網絡套接字(xenomai任務也是一個linux任務,也可以使用linux來提供服務,只不過會影響實時性),有以下代碼,:
....
int sockfd,ret;
struct sockaddr_in addr;
sockfd = socket(PF_INET, SOCK_STREAM, 0);
.....
bind(sockfd, (struct sockaddr *)&my_addr,sizeof(struct sockaddr_in));
....
該代碼編譯時鏈接到了libcobalt,socket()函數即libcobalt中的__cobalt_socket()
,其定義在xenomai-3.x.x\lib\cobalt\rtdm.c
,如下:
COBALT_IMPL(int, socket, (int protocol_family, int socket_type, int protocol))
{
int s;
s = XENOMAI_SYSCALL3(sc_cobalt_socket, protocol_family,
socket_type, protocol);
if (s < 0) {
s = __STD(socket(protocol_family, socket_type, protocol));
}
return s;
}
可以看到,libcobalt中的函數會先嘗試調用實時內核cobalt的系統調用, 當cobalt系統調用不成功的時候才繼續嘗試通過__STD()
宏來調用linux系統調用(cobalt內核根據socket協議類型參數PF_INET
,SOCK_STREAM
判斷),這樣就有效的分清了是linux系統調用還是xenomai系統調用,這也是所有libcobalt實現的posix都鏈接到libobalt庫的原因。
一般情況下,可以直接在代碼中使用__STD()
宏指明我們調用的linux內核的服務,修改如下:
....
int sockfd,ret;
struct sockaddr_in addr;
sockfd = __STD(socket(PF_INET, SOCK_STREAM, 0));
.....
__STD(bind(sockfd, (struct sockaddr *)&my_addr,sizeof(struct sockaddr_in)));
....
現在一切都明了了,一個函數編譯時通過參數鏈接到xenomai庫后,通過__STD()
宏來表示使用linux接口。
4. 總結
- 在實時程序或實時庫libcobalt中,通過
__STD()
宏來表示使用linux接口。 - 對於一個未指明的接口,libcobalt會先嘗試發起xenomai系統調用,不成功會接着嘗試linux內核系統調用。
- 如果我們向libcobalt庫中新添加一個libcobalt庫中沒有的自定義POSIX函數/系統調用時,一定要在內部先嘗試發起xenomai系統調用,不成功時接着嘗試linux內核系統調用,此外還必須將該接口添加到文件
xenomai\lib\cobalt\cobalt.wrappers
中,這樣才能正確鏈接,否則編譯后的應用還是原來的。