xenomai內核解析--雙核系統調用(二)--應用如何區分xenomai/linux系統調用或服務

版權聲明:本文爲本文爲博主原創文章,轉載請註明出處。若有錯誤,歡迎指正。html

1. 引出問題

上一篇文章xenomai內核解析--雙核系統調用(一)以X86處理器爲例,分析了xenomai內核調用的流程,讀了之後可能會以爲缺了點什麼,你可能會有如下疑問:linux

  1. 系統中的兩個內核都是POSIX接口實現系統調用,那麼我寫一個POSIX接口的應用程序,怎樣知道它調用的內核,或者說怎樣成爲運行在cobalt內核的RT應用,而不是普通linux應用?
  2. 對於同一個POSIX接口,可能個人程序中,既須要xenomai內核提供服務(xenomai 系統調用),又須要調用linux內核提供服務(linux內核系統調用),或者說既有libcobalt,又有glibc庫,他們是如何實現或區分的?

2. 編譯連接

對於問題1,答案是:由編譯時連接的庫決定,若是普通的編譯,則該應用編譯後是一個普通linux運用。若是要編譯爲xenomai應用,則須要連接到xenomai庫,那如何設置編譯連接參數?編譯安裝xenomai庫後,可經過執行/usr/bin/xeno-config來獲取。shell

$ /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參數了:bootstrap

$ /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表示連接,以下獲得連接參數:bash

$ /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
複製代碼

其餘更多參數可經過xenomai Manual Page瞭解。網絡

這樣就將POSIX接口源碼編譯成一個xenomai可執行程序了。一般咱們會將獲取編譯參數的操做直接放到Makefile裏,編譯時直接執行獲取使用,這裏給一個簡單的Makefile示例以下:app

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-task

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)
複製代碼

3. libcobalt中的實現

下面來看問題2,既然咱們已將一個接口連接到實時內核庫libcobalt,固然由實時內核庫libcobalt來區分該發起linux內核調用仍是xenomai內核系統。與上一篇文章同樣,以一個POSIX接口pthread_cretate()來解析libcobalt中的實現。socket

xenomai線程的建立流程比較複雜,須要先讓linux建立普通線程,而後再由xenomai建立該線程的shadow 線程,即xenomai調度的實時線程,很符合咱們上面的提出的問題2。 說到這先簡答介紹一下xenomai實時線程的建立,詳細的建立流程後面會寫專門寫一篇文章解析,敬請期待。async

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函數

  • __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系統調用。

通常狀況下,能夠直接在代碼中使用__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內核系統調用。
相關文章
相關標籤/搜索