浅析基于ARM的Linux下的系统调用的实现
2014-07-30 16:09
260 查看
在Linux下系统调用是用软中断实现的,下面以一个简单的open例子简要分析一下应用层的open是如何调用到内核中的sys_open的。
t8.c
[code]#include<sys/types.h>
[/code]
这里需要注意的是:open是C库提供的库函数,并不是系统调用,系统调用时在内核空间的,应用空间无法直接调用。在《Linux内核设计与实现》中说:要访问系统调用(在Linux中常称作syscall),通常通过C库中定义的函数调用来进行。
将t8.c进行静态编译,然后反汇编,看一下是如何调用open的?
[code]arm-linux-objdump-Da.out>a.dis
[/code]
下面我们截取a.dis中的一部分进行说明:
[code]00008228<main>:
13c58:ef000000svc0x00000000#产生软中断
[/code]
通过上面的代码注释,可以看到,系统调用sys_open的系统调用号是5,将系统调用号存放到寄存器R7当中,然后应用程序通过svc0x00000000产生软中断,陷入内核空间。
也许会好奇,ARM软中断不是用SWI吗,这里怎么变成了SVC了,请看下面一段话,是从ARM官网copy的:
SVC
超级用户调用。
语法
SVC{cond}#immed
其中:
cond
是一个可选的条件代码(请参阅条件执行)。
immed
是一个表达式,其取值为以下范围内的一个整数:
在ARM指令中为0到224–1(24位值)
在16位Thumb指令中为0-255(8位值)。
用法
SVC指令会引发一个异常。这意味着处理器模式会更改为超级用户模式,CPSR会保存到超级用户模式SPSR,并且执行会跳转到SVC向量(请参阅《开发指南》中的第6章处理处理器异常)。
处理器会忽略immed。但异常处理程序会获取它,借以确定所请求的服务。
Note
作为ARM汇编语言开发成果的一部分,SWI指令已重命名为SVC。在此版本的RVCT中,SWI指令反汇编为SVC,并提供注释以指明这是以前的SWI。
条件标记
此指令不更改标记。
体系结构
此ARM指令可用于所有版本的ARM体系结构。
t8.c
#include<stdio.h>
[code]#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
intmain(intargc,constchar*argv[])
{
intfd;
fd=open(".",O_RDWR);
close(fd);
return0;
}
[/code]
这里需要注意的是:open是C库提供的库函数,并不是系统调用,系统调用时在内核空间的,应用空间无法直接调用。在《Linux内核设计与实现》中说:要访问系统调用(在Linux中常称作syscall),通常通过C库中定义的函数调用来进行。
将t8.c进行静态编译,然后反汇编,看一下是如何调用open的?
arm-linux-gcct8.c--static
[code]arm-linux-objdump-Da.out>a.dis
[/code]
下面我们截取a.dis中的一部分进行说明:
......
[code]00008228<main>:
8228:e92d4800push{fp,lr}
822c:e28db004addfp,sp,#4;0x4
8230:e24dd010subsp,sp,#16;0x10
8234:e50b0010strr0,[fp,#-16]
8238:e50b1014strr1,[fp,#-20]
823c:e59f0028ldrr0,[pc,#40];826c<main+0x44>
8240:e3a01002movr1,#2;0x2;#defineO_RDWR00000002
8244:eb002e7dbl13c40<__libc_open>
8248:e1a03000movr3,r0
824c:e50b3008strr3,[fp,#-8]
8250:e51b0008ldrr0,[fp,#-8]
8254:eb002e9dbl13cd0<__libc_close>
8258:e3a03000movr3,#0;0x0
825c:e1a00003movr0,r3
8260:e24bd004subsp,fp,#4;0x4
8264:e8bd4800pop{fp,lr}
8268:e12fff1ebxlr
826c:00064b8c.word0x00064b8c
......
00013c40<__libc_open>:
13c40:e51fc028ldrip,[pc,#-40];13c20<___fxstat64+0x50>
13c44:e79fc00cldrip,[pc,ip]
13c48:e33c0000teqip,#0;0x0
13c4c:1a000006bne13c6c<__libc_open+0x2c>
13c50:e1a0c007movip,r7
13c54:e3a07005movr7,#5;0x5
#在arch/arm/include/asm/unistd.h中:#define__NR_open(__NR_SYSCALL_BASE+5)
其中,__NR_OABI_SYSCALL_BASE是0
13c58:ef000000svc0x00000000#产生软中断
13c5c:e1a0700cmovr7,ip
13c60:e3700a01cmnr0,#4096;0x1000
13c64:312fff1ebxcclr
13c68:ea0008d4b15fc0<__syscall_error>
......
[/code]
通过上面的代码注释,可以看到,系统调用sys_open的系统调用号是5,将系统调用号存放到寄存器R7当中,然后应用程序通过svc0x00000000产生软中断,陷入内核空间。
也许会好奇,ARM软中断不是用SWI吗,这里怎么变成了SVC了,请看下面一段话,是从ARM官网copy的:
SVC
超级用户调用。
语法
SVC{cond}#immed
其中:
cond
是一个可选的条件代码(请参阅条件执行)。
immed
是一个表达式,其取值为以下范围内的一个整数:
在ARM指令中为0到224–1(24位值)
在16位Thumb指令中为0-255(8位值)。
用法
SVC指令会引发一个异常。这意味着处理器模式会更改为超级用户模式,CPSR会保存到超级用户模式SPSR,并且执行会跳转到SVC向量(请参阅《开发指南》中的第6章处理处理器异常)。
处理器会忽略immed。但异常处理程序会获取它,借以确定所请求的服务。
Note
作为ARM汇编语言开发成果的一部分,SWI指令已重命名为SVC。在此版本的RVCT中,SWI指令反汇编为SVC,并提供注释以指明这是以前的SWI。
条件标记
此指令不更改标记。
体系结构
此ARM指令可用于所有版本的ARM体系结构。
在基于ARM的Linux中,异常向量表已经被放置在了0xFFFF0000这个位置。这个过程的完成:
start_kernel--->setup_arch--->early_trap_init
void__initearly_trap_init(void)
[code]{
unsignedlongvectors=CONFIG_VECTORS_BASE;//就是0xFFFF0000
externchar__stubs_start[],__stubs_end[];
externchar__vectors_start[],__vectors_end[];
externchar__kuser_helper_start[],__kuser_helper_end[];
intkuser_sz=__kuser_helper_end-__kuser_helper_start;
/*
*Copythevectors,stubsandkuserhelpers(inentry-armv.S)
*intothevectorpage,mappedat0xffff0000,andensurethese
*arevisibletotheinstructionstream.
*/
memcpy((void*)vectors,__vectors_start,__vectors_end-__vectors_start);
memcpy((void*)vectors+0x200,__stubs_start,__stubs_end-__stubs_start);
memcpy((void*)vectors+0x1000-kuser_sz,__kuser_helper_start,kuser_sz);
/*
*Copysignalreturnhandlersintothevectorpage,and
*setsigreturntobeapointertothese.
*/
memcpy((void*)KERN_SIGRETURN_CODE,sigreturn_codes,
sizeof(sigreturn_codes));
flush_icache_range(vectors,vectors+PAGE_SIZE);
modify_domain(DOMAIN_USER,DOMAIN_CLIENT);
}
[/code]
关于上面这个函数的详细解释,参见:
把异常中断向量表的位置设置为0xffff0000的话,需要修改协处理器CP15的寄存器C1的第13位,将其设置为1。以Tq2440的提供的内核2.6.30.4为例看一下:
arch/arm/kernel/head.S
adrlr,__enable_mmu@return(PIC)address
[code]addpc,r10,#PROCINFO_INITFUNC
[/code]
其中,PROCINFO_INITFUNC的值是16,r10的值是__arm920_proc_info的地址:
__arm920_proc_info:
[code].long0x41009200
.long0xff00fff0
.longPMD_TYPE_SECT|\
PMD_SECT_BUFFERABLE|\
PMD_SECT_CACHEABLE|\
PMD_BIT4|\
PMD_SECT_AP_WRITE|\
PMD_SECT_AP_READ
.longPMD_TYPE_SECT|\
PMD_BIT4|\
PMD_SECT_AP_WRITE|\
PMD_SECT_AP_READ
b__arm920_setup
.longcpu_arch_name
.longcpu_elf_name
......
.size__arm920_proc_info,.-__arm920_proc_info
[/code]
看一下__arm920_setup的实现(proc-arm920.S(arch\arm\mm)):
.type__arm920_setup,#function
[code]__arm920_setup:
movr0,#0
mcrp15,0,r0,c7,c7@invalidateI,Dcachesonv4
mcrp15,0,r0,c7,c10,4@drainwritebufferonv4
#ifdefCONFIG_MMU
mcrp15,0,r0,c8,c7@invalidateI,DTLBsonv4
#endif
adrr5,arm920_crval
ldmiar5,{r5,r6}@参看以下下面的arm920_crval的实现,本句话执行完后r5和r6分别为:0x3f3f和0x3135
mrcp15,0,r0,c1,c0@getcontrolregisterv4获取协处理器p15的寄存器才c1
bicr0,r0,r5
orrr0,r0,r6@我们只关注第13位,这里将r0的第13位设置为了1
movpc,lr
.size__arm920_setup,.-__arm920_setup
/*
*R
*.RVIZFRSBLDPWCAM
*..110001..110101
*
*/
.typearm920_crval,#object
arm920_crval:
crvalclear=0x00003f3f,mmuset=0x00003135,ucset=0x00001130
[/code]
在看一下crval的实现(proc-macros.S(arch\arm\mm)):
.macrocrval,clear,mmuset,ucset
[code]#ifdefCONFIG_MMU
.word\clear
.word\mmuset
#else
.word\clear
.word\ucset
#endif
.endm
[/code]
在__arm920_setup中执行完movpc,lr后,便跳入了下面的语句:
__enable_mmu:
[code]#ifdefCONFIG_ALIGNMENT_TRAP
orrr0,r0,#CR_A
#else
bicr0,r0,#CR_A
#endif
#ifdefCONFIG_CPU_DCACHE_DISABLE
bicr0,r0,#CR_C
#endif
#ifdefCONFIG_CPU_BPREDICT_DISABLE
bicr0,r0,#CR_Z
#endif
#ifdefCONFIG_CPU_ICACHE_DISABLE
bicr0,r0,#CR_I
#endif
movr5,#(domain_val(DOMAIN_USER,DOMAIN_MANAGER)|\
domain_val(DOMAIN_KERNEL,DOMAIN_MANAGER)|\
domain_val(DOMAIN_TABLE,DOMAIN_MANAGER)|\
domain_val(DOMAIN_IO,DOMAIN_CLIENT))
mcrp15,0,r5,c3,c0,0@loaddomainaccessregister
mcrp15,0,r4,c2,c0,0@loadpagetablepointer
b__turn_mmu_on
ENDPROC(__enable_mmu)
[/code]
看一下__turn_mmu_on的实现(head.S(arch\arm\kernel)):
.align5
[code]__turn_mmu_on:
movr0,r0
mcrp15,0,r0,c1,c0,0@writecontrolreg
mrcp15,0,r3,c0,c0,0@readidreg
movr3,r3
movr3,r3
movpc,r13
ENDPROC(__turn_mmu_on)
[/code]
在__turn_mmu_on中,将寄存器r0的值写到了cp15协处理器的寄存器C1中。到这里便完成了将异常中断向量表的位置放到了0xffff0000.
说完异常向量表的位置,接下来看看软中断的实现。
ARM提供的中断类型:
ARM的异常处理模型:
entry-armv.S(arch\arm\kernel)
.LCvswi:
[code]
.wordvector_swi
.globl__stubs_end
__stubs_end:
.equstubs_offset,__vectors_start+0x200-__stubs_start
.globl__vectors_start
__vectors_start:
swiSYS_ERROR0
bvector_und+stubs_offset
ldrpc,.LCvswi+stubs_offset@发生软中断后先跳到这里
bvector_pabt+stubs_offset
bvector_dabt+stubs_offset
bvector_addrexcptn+stubs_offset
bvector_irq+stubs_offset
bvector_fiq+stubs_offset
.globl__vectors_end
__vectors_end:
.data
.globlcr_alignment
.globlcr_no_alignment
cr_alignment:
.space4
cr_no_alignment:
.space4
[/code]
接下来看一下vector_swi的实现,根据实际的宏定义进行了简化
ENTRY(vector_swi)
[code]subsp,sp,#S_FRAME_SIZE
stmiasp,{r0-r12}@Callingr0-r12
addr8,sp,#S_PC
stmdbr8,{sp,lr}^@Callingsp,lr
mrsr8,spsr@calledfromnon-FIQmode,sook.
strlr,[sp,#S_PC]@SavecallingPC
strr8,[sp,#S_PSR]@SaveCPSR
strr0,[sp,#S_OLD_R0]@SaveOLD_R0
zero_fp
/*
*Getthesystemcallnumber.
*/
/*
*IfwehaveCONFIG_OABI_COMPATthenweneedtolookattheswi
*valuetodetermineifitisanEABIoranoldABIcall.
*/
ldrr10,[lr,#-4]
@getSWIinstructionr10中存放的就是引起软中断的那条指令的机器码
发生软中断的时候,系统自动将PC-4存放到了lr寄存器,由于是三级流水,
并且是ARM状态,还需要减4才能得到发生软中断的那条指令的机器码所在的地址
A710(andip,r10,#0x0f000000@checkforSWI)
A710(teqip,#0x0f000000)
A710(bne.Larm710bug)
ldrip,__cr_alignment
ldrip,[ip]
mcrp15,0,ip,c1,c0@updatecontrolregister
enable_irq@在发生中断的时候,相应的中断线在在所有CPU上都会被屏蔽掉
get_thread_infotsk@参看下面的介绍
adrtbl,sys_call_table
@loadsyscalltablepointer此时tbl(r8)中存放的就是sys_call_table的起始地址
ldrip,[tsk,#TI_FLAGS]@checkforsyscalltracing
/*
*Iftheswiargumentiszero,thisisanEABIcallandwedonothing.
*
*IfthisisanoldABIcall,getthesyscallnumberintoscnoand
*gettheoldABIsyscalltableaddress.
*/
bicsr10,r10,#0xff000000
eornescno,r10,#__NR_OABI_SYSCALL_BASE
ldr
ne
tbl,=sys_oabi_call_table
stmdbsp!,{r4,r5}@pushfifthandsixthargs
tstip,#_TIF_SYSCALL_TRACE@arewetracingsyscalls?
bne__sys_trace
cmpscno,#NR_syscalls@checkuppersyscalllimit
adrlr,ret_fast_syscall@returnaddress
ldrccpc,[tbl,scno,lsl#2]@callsys_*routine
addr1,sp,#S_OFF
2:movwhy,#0@nolongerarealsyscall
cmpscno,#(__ARM_NR_BASE-__NR_SYSCALL_BASE)
eorr0,scno,#__NR_SYSCALL_BASE@putOSnumberback
bcsarm_syscall
bsys_ni_syscall@notprivatefunc
ENDPROC(vector_swi)
[/code]
entry-common.S(arch\arm\kernel下面是entry-header.S(arch\arm\kernel)的部分内容:
/*
[code]*Thesearetheregistersusedinthesyscallhandler,andallowusto
*haveintheoryupto7argumentstoafunction-r0tor6.
*
*r7isreservedforthesystemcallnumberforthumbmode.
*
*Notethattbl==whyisintentional.
*
*Wemustsetatleast"tsk"and"why"whencallingret_with_reschedule.
*/
scno.reqr7@syscallnumber
tbl.reqr8@syscalltablepointer
why.reqr8@Linuxsyscall(!=0)
tsk.reqr9@currentthread_info
[/code]
.req是伪汇编,以scno.reqr7为例,表示scno是寄存器r7的别名。
get_thread_infotsk
其中,tsk是寄存器r9的别名,get_thread_info是一个宏定义,如下:
.macroget_thread_info,rd
[code]mov\rd,sp,lsr#13
mov\rd,\rd,lsl#13
.endm
[/code]
即:将sp进行8KB对齐后的值赋给寄存器r9,什么意思?
这个就涉及到Linux的内核栈了。Linux为每个进程都分配了一个8KB的内核栈,在内核栈的尾端存放有关于这个进程的structtherad_info结构:
structthread_info{
[code]unsignedlongflags;/*lowlevelflags*/
intpreempt_count;/*0=>preemptable,<0=>bug*/
mm_segment_taddr_limit;/*addresslimit*/
structtask_struct*task;/*maintaskstructure*/
structexec_domain*exec_domain;/*executiondomain*/
__u32cpu;/*cpu*/
__u32cpu_domain;/*cpudomain*/
structcpu_context_savecpu_context;/*cpucontext*/
__u32syscall;/*syscallnumber*/
__u8used_cp[16];/*threadusedcopro*/
unsignedlongtp_value;
structcrunch_statecrunchstate;
unionfp_statefpstate__attribute__((aligned(8)));
unionvfp_statevfpstate;
#ifdefCONFIG_ARM_THUMBEE
unsignedlongthumbee_state;/*ThumbEEHandlerBaseregister*/
#endif
structrestart_blockrestart_block;
};
[/code]
通过上面的操作,寄存器r9中就是这个进程的thread_info结构的起始地址。
sys_call_table
entry-common.S(arch\arm\kernel)
.typesys_call_table,#object
[code]ENTRY(sys_call_table)
#include"calls.S"
#undefABI
#undefOBSOLETE
[/code]
其中,calls.S的内容如下:
/*
[code]*linux/arch/arm/kernel/calls.S
*
*Copyright(C)1995-2005RussellKing
*
*Thisprogramisfreesoftware;youcanredistributeitand/ormodify
*itunderthetermsoftheGNUGeneralPublicLicenseversion2as
*publishedbytheFreeSoftwareFoundation.
*
*Thisfileisincludedthriceinentry-common.S
*/
/*0*/CALL(sys_restart_syscall)
CALL(sys_exit)
CALL(sys_fork_wrapper)
CALL(sys_read)
CALL(sys_write)
/*5*/CALL(sys_open)
CALL(sys_close)
CALL(sys_ni_syscall)/*wassys_waitpid*/
CALL(sys_creat)
CALL(sys_link)
/*10*/CALL(sys_unlink)
CALL(sys_execve_wrapper)
CALL(sys_chdir)
CALL(OBSOLETE(sys_time))/*usedbylibc4*/
CALL(sys_mknod)
......
/*355*/CALL(sys_signalfd4)
CALL(sys_eventfd2)
CALL(sys_epoll_create1)
CALL(sys_dup3)
CALL(sys_pipe2)
/*360*/CALL(sys_inotify_init1)
CALL(sys_preadv)
CALL(sys_pwritev)
#ifndefsyscalls_counted
.equsyscalls_padding,((NR_syscalls+3)&~3)-NR_syscalls
#definesyscalls_counted
#endif
.reptsyscalls_padding
CALL(sys_ni_syscall)
.endr
[/code]
关于这个部分的更多介绍参见:
bicsr10,r10,#0xff000000
执行这个操作的时候,r10中存放的是SWIinstruction,在我们的例子中就是(a.dis):
即:r10为0xEF000000
显然,bics这条指令下面的两个语句由于条件不成立,无法获得执行。这条指令的作用是获得系统调用号
可以参考这个手册,看一下svc执行的格式:
可以看到,[23:0]存放的就是svc指令后面的那个立即数,也即系统调用号。
不过需要注意的是:我们这里并没有这样做,我们的做法是(a.dis中可以看到):
使用的是svc0,后面跟的并不是系统调用号,而是0,这里把系统调用号存放在了寄存器r7中(a.dis中):
可以看到,由于使用的sys_open系统调用,所以把它的系统调用号5存放到了寄存器r7当中
ldrccpc,[tbl,scno,lsl#2]@callsys_*routine
这里的scno是就是寄存器r7的别名,它的值是sys_open的系统调用号5,由于在calls.S中每个系统调用标号占用4个字节,所以这个将scno的值乘以4然后再加上tbl,tbl是系统调用表sys_call_table的基地址。然后就跳入开始执行sys_open了。
asmlinkagelongsys_open(constchar__user*filename,
intflags,intmode);
那么sys_open在哪呢?在内核源码中直接搜索sys_open,无法搜到它的实现代码,实际上它是在fs/open.c中实现的:
SYSCALL_DEFINE3(open,constchar__user*,filename,int,flags,int,mode)
[code]{
longret;
if(force_o_largefile())
flags|=O_LARGEFILE;
ret=do_sys_open(AT_FDCWD,filename,flags,mode);
/*avoidREGPARMbreakageonx86:*/
asmlinkage_protect(3,ret,filename,flags,mode);
returnret;
}
[/code]
其中SYSCALL_DEFINE3是一个宏:
syscalls.h(include\linux)
#defineSYSCALL_DEFINE3(name,...)SYSCALL_DEFINEx(3,_##name,__VA_ARGS__)
SYSCALL_DEFINEx也是一个宏:
syscalls.h(include\linux)
#defineSYSCALL_DEFINEx(x,sname,...)\
__SYSCALL_DEFINEx(x,sname,__VA_ARGS__)
__SYSCALL_DEFINEx仍然是个宏:
syscalls.h(include\linux)
#define__SYSCALL_DEFINEx(x,name,...)\
asmlinkagelongsys##name(__SC_DECL##x(__VA_ARGS__))
所以展开后的结果就是:
asmlinkagelongsys_open(__SC_DECL3(__VA_ARGS__))
其中,__SC_DECL3定义如下:
syscalls.h(include\linux)
#define__SC_DECL1(t1,a1)t1a1
[code]#define__SC_DECL2(t2,a2,...)t2a2,__SC_DECL1(__VA_ARGS__)
#define__SC_DECL3(t3,a3,...)t3a3,__SC_DECL2(__VA_ARGS__)
[/code]
所以最终的结果如下:
asmlinkagelongsys_open(constchar__user*filename,intflags,intmode)
[code]{
longret;
if(force_o_largefile())
flags|=O_LARGEFILE;
ret=do_sys_open(AT_FDCWD,filename,flags,mode);
/*avoidREGPARMbreakageonx86:*/
asmlinkage_protect(3,ret,filename,flags,mode);
returnret;
}
[/code]
关于sys_open本身的实现这里就不深入分析了。
接下来看一下返回。
adrlr,ret_fast_syscall@returnaddress
当sys_open中return后,便跳入ret_fast_syscall处开始执行:
/*
[code]*Thisisthefastsyscallreturnpath.Wedoaslittleas
*possiblehere,andthisincludessavingr0backintotheSVC
*stack.
*/
ret_fast_syscall:
UNWIND(.fnstart)
UNWIND(.cantunwind)
disable_irq@disableinterrupts
ldrr1,[tsk,#TI_FLAGS]@将thread_info中的flags成员存放到r1中
tstr1,#_TIF_WORK_MASK
bnefast_work_pending
/*performarchitecturespecificactionsbeforeuserreturn*/
arch_ret_to_userr1,lr
@fast_restore_user_regs
ldrr1,[sp,#S_OFF+S_PSR]@getcallingcpsr
ldrlr,[sp,#S_OFF+S_PC]!@getpc
msrspsr_cxsf,r1@saveinspsr_svc
ldmdbsp,{r1-lr}^@getcallingr1-lr
movr0,r0
addsp,sp,#S_FRAME_SIZE-S_PC
movspc,lr@return&movespsr_svcintocpsr
UNWIND(.fnend)
/*
*Ok,weneedtodoextraprocessing,entertheslowpath.
*/
fast_work_pending:
strr0,[sp,#S_R0+S_OFF]!@returnedr0
work_pending:
tstr1,#_TIF_NEED_RESCHED@判断是否需要进行进程调度
bnework_resched
tstr1,#_TIF_SIGPENDING
beqno_work_pending
movr0,sp@'regs'
movr2,why@'syscall'
bldo_notify_resume
bret_slow_syscall@Checkworkagain
work_resched:
blschedule
/*
*"slow"syscallreturnpath."why"tellsusifthiswasarealsyscall.
*/
ENTRY(ret_to_user)
ret_slow_syscall:
disable_irq@disableinterrupts
ldrr1,[tsk,#TI_FLAGS]
tstr1,#_TIF_WORK_MASK
bnework_pending
no_work_pending:
/*performarchitecturespecificactionsbeforeuserreturn*/
arch_ret_to_userr1,lr
@slow_restore_user_regs
ldrr1,[sp,#S_PSR]@getcallingcpsr
ldrlr,[sp,#S_PC]!@getpc
msrspsr_cxsf,r1@saveinspsr_svc
ldmdbsp,{r0-lr}^@getcallingr0-lr
movr0,r0
addsp,sp,#S_FRAME_SIZE-S_PC
movspc,lr@return&movespsr_svcintocpsr
ENDPROC(ret_to_user)
[/code]
在返回的时候要看是否要进行进程调用。
先分析到这里。
相关文章推荐
- 如何来实现一个Linux内核的系统调用(基于tiny4412开发板)
- 基于arm的linux系统调用分析
- ARM linux系统调用的实现原理
- 基于ARM的Linux系统移植研究与实现
- ARM linux系统调用的实现原理
- 如何来实现一个Linux内核的系统调用(基于tiny4412开发板)
- 基于int的Linux的经典系统调用实现
- 转自EDN基于ARM的Linux系统移植研究与实现
- ARM linux系统调用的实现原理
- 基于Linux系统调用使用php实现一个在线编译运行C语言程序的系统
- u-boot 分析 - [嵌入式Linux系统开发技术详解-基于ARM]
- 基于Linux的集群系统(三)实现过程之理论先导
- 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】Linux系统调用的实现机制分析
- linux系统调用之文件:递归实现tree命令
- u-boot 分析 - [嵌入式Linux系统开发技术详解-基于ARM]
- linux下使用系统调用编程实现dir命令功能
- linux下使用系统调用实现进程后台运行
- linux系统调用之文件:递归实现pwd
- 基于ARM-uCLinux嵌入式系统启动引导的实现