一、如何區分xenomai、linux系統調用/服務(wù)
1. 引出問(wèn)題
上一篇文章xenomai內核解析--雙核系統調用(一)以X86處理器為例,分析了xenomai內核系統調用的流程,讀了以后可能會(huì )覺(jué)得缺了點(diǎn)什么,你可能會(huì )有以下疑問(wèn):
-
系統中的兩個(gè)內核都是POSIX接口實(shí)現系統調用,那么我們用POSIX接口寫(xiě)了一個(gè)應用程序,怎樣知道它調用的內核,或者如何區分這個(gè)應用是cobalt內核的應用,而不是普通linux應用?
-
對于同一個(gè)POSIX接口應用程序,可能既需要xenomai內核提供服務(wù)(xenomai 系統調用),又需要調用linux內核提供服務(wù)(linux內核系統調用),或者既有libcobalt,又有g(shù)libc庫,他們是如何實(shí)現和區分的?
2. 編譯鏈接
對于問(wèn)題1,答案是:由編譯的鏈接過(guò)程決定,鏈接的庫不同當然執行的也就不同,如果普通編譯,則該應用編譯后是一個(gè)普通linux運用。如果要編譯為xenomai應用,則需要鏈接到xenomai庫。但是我們應用程序調用的代碼函數symbol完全一樣,我們會(huì )有疑惑xenomai是如何貍貓換太子的?首先鏈接是通過(guò)符號表(symbol)來(lái)鏈接的,當然也就要從代碼符號(symbol)入手,首先來(lái)看一個(gè)常用的編譯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)
clean:
rm -f $(EXECUTABLE) $(obj)
其中最重要的就是編譯時(shí)需要 xeno-config
來(lái)生成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
例如編譯一個(gè)POSIX接口的實(shí)時(shí)應用,參數 --cflags
表示編譯,指定接口(skin) --posix
,就能得到編譯該程序的gcc參數了,看著(zhù)沒(méi)什么特別的:
$ /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
這一看就多出不少東西,重點(diǎn)就在這 cobalt.wrappers
、 modechk.wrappers
,兩個(gè)文件的內容如下:
...
--wrap open
--wrap open64
--wrap socket
--wrap close
--wrap ioctl
--wrap read
....
--wrap recv
--wrap send
--wrap getsockopt
--wrap setsockop
...
里面是一些posix系統調用,前面的 --wrap
是什么作用?這是執行鏈接過(guò)程的程序 ld
的一個(gè)參數,通過(guò) man ld
可以找到該參數的說(shuō)明:
--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 ", 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".
簡(jiǎn)單來(lái)說(shuō)就是:任何 對 symbol
未定義 的 引用 (undefined reference) 將 解析為 __wrap_symbol
. 任何 對 __real_symbol
未定義 的 引用 將 解析為 symbol
。意思就是我們代碼里使用到且是參數 --wrap
指定的符號 symbol
(即文件里的內容),鏈接的時(shí)候就認為它是 __wrap_symbol
,比如我們代碼用到了 open()
在鏈接的時(shí)候是與庫中的 __wrap_open()
鏈接的, __wrap_open
就是在xenomai 實(shí)時(shí)庫libcobalt中實(shí)現,現在我們明白我們的posix函數如何和xenomai對接上了。
對于
xeno-config
的其他更多參數可通過(guò)xenomai Manual Page了解。
這樣就將POSIX接口源碼編譯成一個(gè)xenomai可執行程序了。
我們還有另一個(gè)問(wèn)題,既然鏈接到了libcobalt,但我們是要Linux來(lái)提供服務(wù),又是怎么讓Linux來(lái)提供服務(wù)的呢?看libcobalt具體實(shí)現可以知道答案。
3. libcobalt中的實(shí)現
下面來(lái)看問(wèn)題2,既然我們已將一個(gè)接口鏈接到實(shí)時(shí)內核庫libcobalt,當然由實(shí)時(shí)內核庫libcobalt來(lái)區分該發(fā)起linux內核調用還是xenomai內核系統。與上一篇文章一樣,以一個(gè)POSIX接口 pthread_cretate()
來(lái)解析libcobalt中的實(shí)現。
xenomai線(xiàn)程的創(chuàng )建流程比較復雜,需要先讓linux創(chuàng )建普通線(xiàn)程,然后再由xenomai創(chuàng )建該線(xiàn)程的shadow 線(xiàn)程,即xenomai調度的實(shí)時(shí)線(xiàn)程,很符合我們上面的提出的問(wèn)題2。說(shuō)到這先簡(jiǎn)答介紹一下xenomai實(shí)時(shí)線(xiàn)程的創(chuàng )建,詳細的創(chuàng )建流程后面會(huì )寫(xiě)專(zhuān)門(mén)寫(xiě)一篇文章解析,敬請期待。
pthread_cretate()
不是一個(gè)系統調用,由NPTL(Native POSIX Threads Library)實(shí)現(NPTL是Linux 線(xiàn)程實(shí)現的現代版,由UlrichDrepper 和Ingo Molnar 開(kāi)發(fā),以取代LinuxThreads),NPTL負責一個(gè)用戶(hù)線(xiàn)程的用戶(hù)空間棧創(chuàng )建、內存分配、初始化等工作,與linux內核配合完成線(xiàn)程的創(chuàng )建。每一線(xiàn)程映射一個(gè)單獨的內核調度實(shí)體(KSE,Kernel Scheduling Entity)。內核分別對每個(gè)線(xiàn)程做調度處理。線(xiàn)程同步操作通過(guò)內核系統調用實(shí)現。
xenomai coblat作為實(shí)時(shí)任務(wù)的調度器,每個(gè)實(shí)時(shí)線(xiàn)程需要對應到 coblat調度實(shí)體,如果要創(chuàng )建實(shí)時(shí)線(xiàn)程就需要像linux那樣NPTL與linux 內核深度結合,那么coblat與libcoblat實(shí)現將會(huì )變得很復雜。在這里,xenomai使用了一種方式,由NPTL方式去完成實(shí)時(shí)線(xiàn)程實(shí)體的創(chuàng )建(linux部分),在普通線(xiàn)程的基礎上附加一些屬性,對應到xenomai cobalt內核實(shí)體時(shí)能被實(shí)時(shí)內核cobalt調度。
所以libcoblat庫中的實(shí)時(shí)線(xiàn)程創(chuàng )建函數 pthread_cretate
最后還是需要使用 glibc的 pthread_cretate
函數,xenomai只是去擴展glibc pthread_cretate
創(chuàng )建的線(xiàn)程,使這個(gè)線(xiàn)程可以在實(shí)時(shí)內核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
中如下,展開(kāi)上面宏,會(huì )為 pthread_create()
生成三個(gè)類(lèi)型函數:
__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()
函數的這三個(gè)宏意思為:
__RT(P):__cobalt_pthread_create
明確表示Cobalt實(shí)現的POSIX函數
__COBALT(P):與__RT()
等效。
__STD(P):__real_pthread_create
表示這是原始的POSIX函數(Linux glibc實(shí)現),cobalt庫內部通過(guò)它來(lái)表示調用原始的POSIX函數(glibc NPTL).
__WRAP(P):__wrap_pthread_create
是__cobalt_pthread_create
的弱別名,如果編譯器編譯時(shí)知道有該函數其它的實(shí)現,該函數就會(huì )被覆蓋。
主要關(guān)注前面兩個(gè),對于最后一個(gè)宏,如果外部庫想覆蓋已有的函數,應提供其自己的 __wrap_pthread_create()
實(shí)現,來(lái)覆蓋Cobalt實(shí)現的 pthread_create()
版本。原始的Cobalt實(shí)現仍可以引用為 __COBALT(pthread_create)
。由宏COBALT_IMPL來(lái)定義:
#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
實(shí)現主體為
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
函數及該函數的一個(gè)弱別名 __wrap_pthread_create
,調用這兩個(gè)函數執行的是同一個(gè)函數體。
對于 NPTL函數 pthread_create
,在Cobalt庫里使用 __STD()
修飾,展開(kāi)后即 __real_pthread_create()
,其實(shí)只是NPTL pthread_create()
的封裝, __real_pthread_create()
會(huì )直接調用 NPTL pthread_create
,在libcobaltwrappers.c實(shí)現如下:
/* 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
函數.同樣我們接著(zhù) __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));
.....
}
下面再看另一個(gè)例子,實(shí)時(shí)任務(wù)在代碼中使用了linux的網(wǎng)絡(luò )套接字(xenomai任務(wù)也是一個(gè)linux任務(wù),也可以使用linux來(lái)提供服務(wù),只不過(guò)會(huì )影響實(shí)時(shí)性),有以下代碼,:
....
int sockfd,ret;
struct sockaddr_in addr;
sockfd = socket(PF_INET, SOCK_STREAM, 0);
.....
bind(sockfd, (struct sockaddr *)&my_addr,sizeof(struct sockaddr_in));
....
該代碼編譯時(shí)鏈接到了libcobalt,socket()函數即libcobalt中的 __cobalt_socket()
,其定義在xenomai- 3.x.xlibcobalt tdm.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中的函數會(huì )先嘗試調用實(shí)時(shí)內核cobalt的系統調用, 當cobalt系統調用不成功的時(shí)候才繼續嘗試通過(guò) __STD()
宏來(lái)調用linux系統調用(cobalt內核根據socket協(xié)議類(lèi)型參數 PF_INET
, SOCK_STREAM
判斷),這樣就有效的分清了是linux系統調用還是xenomai系統調用,這也是所有libcobalt實(shí)現的posix都鏈接到libobalt庫的原因。
一般情況下,可以直接在代碼中使用 __STD()
宏指明我們調用的linux內核的服務(wù),修改如下:
....
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)));
....
現在一切都明了了,一個(gè)函數編譯時(shí)通過(guò)參數鏈接到xenomai庫后,通過(guò) __STD()
宏來(lái)表示使用linux接口。
4. 總結
-
在實(shí)時(shí)程序或實(shí)時(shí)庫libcobalt中,通過(guò)
__STD()
宏來(lái)表示使用linux接口。 -
對于一個(gè)未指明的接口,libcobalt會(huì )先嘗試發(fā)起xenomai系統調用,不成功會(huì )接著(zhù)嘗試linux內核系統調用或者調用glibc函數。
-
如果我們向libcobalt庫中新添加一個(gè)libcobalt庫中沒(méi)有的自定義POSIX函數/系統調用時(shí),一定要在內部先嘗試發(fā)起xenomai系統調用,不成功時(shí)接著(zhù)嘗試linux內核系統調用,此外還必須將該接口添加到文件
xenomailibcobaltcobalt.wrappers
中,這樣才能正確鏈接,否則編譯后的應用還是原來(lái)的。
二、 如何為xenomai添加一個(gè)系統調用
1. 添加系統調用
有的時(shí)候我們需要給系統添加一個(gè)特殊功能的系統調用,比如我們在對xenomai做benchmark測試的時(shí)候,需要測試每個(gè)系統服務(wù)操作系統需要消耗多長(cháng)時(shí)間,比如測量獲取信號量操作系統的耗時(shí)。我們知道中斷優(yōu)先級最高,會(huì )強占前臺的操作系統和應用,為了更準確的測量獲取信號量時(shí)操作系統的耗時(shí),這里需要將這種情況下的結果丟棄,我們就需要一個(gè)獲取xenomai中斷次數的系統調用。
假設該系統沒(méi)有任何實(shí)時(shí)驅動(dòng)運行,且設置了xenomai.supportedcpus和linux irqaffinity,supportedcpus啟用tickless,下面給xenomai添加一個(gè)系統調用 get_timer_hits()
,用于獲取應用程序運行CPU的定時(shí)器中斷產(chǎn)生的次數(類(lèi)似于VxWorks里的tickGet(),VxWorks是采用周期tick的方式來(lái)驅動(dòng)系統運作,tickGet()獲取的也就是tick定時(shí)器中斷的次數)。以該系統調用來(lái)舉例如何為xenomai添加一個(gè)實(shí)時(shí)系統調用。
在前兩篇文中說(shuō)到,xenomai每個(gè)系統的系統系統調用號在 cobaltuapisyscall.h
中:
......
在此添加 sc_cobalt_get_timer_hits
的系統,為了避免與xenomai系統調用沖突(xenomai官方添加的系統調用號從小到大),那我們就從最后一個(gè)系統調用添加,即127號系統調用,如下。
......
/*Powerof2*/
先確定一下我們這個(gè)函數的API形式,由于是一個(gè)非標準的形式,這里表示如下:
int get_timer_hits(unsigned long *u_tick);
參數為保存hits的變量地址;
返回值:成功0;出錯 <0;
系統調用的頭文件,然后添加一個(gè)系統調用的聲明,覺(jué)得它和clock相關(guān),那就放在 kernelxenomaiposixclock.h
中吧。
COBALT_SYSCALL_DECL(get_timer_hits,(unsignedlong__user*u_tick));
然后是該函數的內核實(shí)現,放在 /kernelxenomaiposixclock.c
,如下:
COBALT_SYSCALL(get_timer_hits,primary,
(unsignedlong__user*u_tick))
{
structxnthread*thread;
unsignedlongtick;
intcpu;
intret=0;
unsignedintirq;
thread=xnthread_current();
if(thread==NULL)
return-EPERM;
/*得到當前任務(wù)CPU號*/
cpu=xnsched_cpu(thread->sched);
irq=per_cpu(ipipe_percpu.hrtimer_irq,cpu);
/*讀取該CPU中斷計數*/
tick=__ipipe_cpudata_irq_hits(&xnsched_realtime_domain,cpu,
irq);
if(cobalt_copy_to_user(u_tick,&tick,sizeof(tick)))
return-EFAULT;
returnret;
}
需要注意的是該系統調用的權限,這里使用 primary
,表示只有cobalt上下文(實(shí)時(shí)線(xiàn)程)才能調用。
修改完成后重新編譯內核并安裝。
2.Cobalt庫添加接口
在前兩篇文中說(shuō)到,xenomai系統調用由libcobalt發(fā)起,所以修改應用庫來(lái)添加該函數接口,添加聲明 includecobalt ime.h
:
COBALT_DECL(int,get_timer_hits(unsignedlongtick));
xenomai3.x.xlibcobaltclock.c
添加該接口定義:
COBALT_IMPL(int,get_timer_hits,(unsignedlong*tick))
{
intret;
ret=-XENOMAI_SYSCALL1(sc_cobalt_get_tick,
tick);
returnret;
}
因為該系統調用和posix接口沒(méi)有符號重名,不需要修改wrappers文件,完成上述步驟后重新編譯并安裝xenomai庫。
3. 應用使用
由于我們添加 get_timer_hits()
系統調用時(shí),指定了系統調用的權限為primary,這里創(chuàng )建一個(gè)實(shí)時(shí)任務(wù),使用宏 __RT()
指定鏈接到libcobalt庫。
void test(void *cookie)
{
unsigned long tick;
int ret;
ret = __RT(get_timer_hits(&tick));
if (ret){
fprintf(stderr,
"%s: failed to get_tick,%s ",
__func__,strerror(-ret));
return ret;
}
fprintf(stdout,"timer_hits:%ld ",tick);
/*....*/
return 0;
}
int main(int argc, char *const *argv)
{
struct sigaction sa __attribute__((unused));
int sig, cpu = 0;
char sem_name[16];
sigset_t mask;
RT_TASK task;
int ret;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGALRM);
pthread_sigmask(SIG_BLOCK, &mask, NULL);
setlinebuf(stdout);
ret = rt_task_spawn(&task, "test_task", 0, PRIO,
T_JOINABLE, test, NULL);
if (ret){
fprintf(stderr,
"%s: failed to create task,%s ",
__func__,strerror(-ret));
return ret;
}
__STD(sigwait(&mask, &sig));
rt_task_join(&task);
rt_task_delete(&task);
return 0;
}
編譯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 := get-timer-hits
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)
clean:
rm -f $(EXECUTABLE) $(obj)
運行結果:
$./get-timer-hits
timer_hits:3
可以看到,雖然系統已經(jīng)啟動(dòng)十幾分鐘了,但一直沒(méi)有運行xenomai應用,xenomai tick相關(guān)中斷才產(chǎn)生了3次,這就是tickless,后面會(huì )出xenomai調度及時(shí)間子系統和xenomai benchmark相關(guān)文章,敬請關(guān)注。
審核編輯 :李倩-
Linux
+關(guān)注
關(guān)注
87文章
11026瀏覽量
207156 -
Xenomai
+關(guān)注
關(guān)注
0文章
10瀏覽量
7950
原文標題:xenomai內核解析--雙核系統調用(二)--應用如何區分xenomai/linux系統調用或服務(wù)
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
中斷服務(wù)中調用FreeRTOS系統函數,是否必須用FromISR結尾?
linux服務(wù)器和windows服務(wù)器
linux用gdb調試遇到函數調用怎么辦?
Linux內核中信號相關(guān)的系統調用
linux內核系統調用之參數傳遞
Linux系統中調用腳本的常見(jiàn)方法
如何查看Linux systemd下正在運行的服務(wù)
![如何查看<b class='flag-5'>Linux</b> systemd下正在運行的<b class='flag-5'>服務(wù)</b>](https://file1.elecfans.com/web2/M00/B4/3E/wKgZomVtddeAY-fWAAFajMajhIE813.jpg)
linux系統的用途
malloc在Linux上執行的是哪個(gè)系統調用
![malloc在<b class='flag-5'>Linux</b>上執行的是哪個(gè)<b class='flag-5'>系統</b><b class='flag-5'>調用</b>](https://file1.elecfans.com/web2/M00/AD/E3/wKgaomVRi3GAS76QAAC2n2OPe3I409.jpg)
Linux內核中系統調用詳解
![<b class='flag-5'>Linux</b>內核中<b class='flag-5'>系統</b><b class='flag-5'>調用</b>詳解](https://file1.elecfans.com/web2/M00/94/68/wKgZomTlcWiAEOJgAAAQ5XaBP0g428.jpg)
系統調用:用戶(hù)棧與內核棧的切換(上)
![<b class='flag-5'>系統</b><b class='flag-5'>調用</b>:用戶(hù)棧與內核棧的切換(上)](https://file1.elecfans.com/web2/M00/8E/6D/wKgZomTHKQeARCFYAADCLKwpNvQ383.jpg)
Linux驅動(dòng)移植 Linux系統架構優(yōu)點(diǎn)
![<b class='flag-5'>Linux</b>驅動(dòng)移植 <b class='flag-5'>Linux</b><b class='flag-5'>系統</b>架構優(yōu)點(diǎn)](https://file1.elecfans.com/web2/M00/8D/FE/wKgaomTCMl6Ac6BIAAUQu3YjnY0617.jpg)
評論