Original from:?http://blog.chinaunix.net/uid-28458801-id-3468966.html
使用的 glibc : glibc-2.17
使用的 linux kernel :linux-3.2.07
系統調用是內核向用戶進程提供服務的唯一方法,應用程序調用操作系統提供的功能模塊(函數)。
用戶程序通過系統調用從用戶態(user mode)切換到核心態(kernel mode ),從而可以訪問
相應的資源。這樣做的好處是:
為用戶空間提供了一種硬件的抽象接口,使編程更加容易。
有利于系統安全。
有利于每個進程度運行在虛擬系統中,接口統一有利于移植。
運行模式、地址空間、上下文
:
運行模式(mode)
Linux 使用了其中的兩個:
特權級0和特權級3
?,即
內核模式(kernel mode) 和用戶模式(user mode )
地址空間(space )
a)每個進程的虛擬地址空間可以劃分為兩個部分:用
戶空間和內核空間
b)在用戶態下只能訪問用戶空間;而在核心態下,既可以訪問用戶空間,又可以訪問內核空間。?
c)內核空間在每個進程的虛擬地址空間中都是固定的
(虛擬地址為3G~4G的地址空間
)。
上下文(context )
一個進程的上下文可以分為三個部分:用戶級上下文、寄存器上下文以及系統級上下文。
a)用戶級上下文:正文、數據、用戶棧以及共享存儲區;
b)寄存器上下文:通用寄存器、程序寄存器(IP )、處理機狀態寄存器(EFLAGS)、棧指針(ESP);
c)系統級上下文:進程控制塊task_struct 、內存管理信息(mm_struct 、vm_area_struct、pgd 、pmd、
?? pte 等)、核心棧等。
系統調用、API和C 庫
?
a)Linux 的
應用編程接口(API)
遵循POSIX標準
?
b)
Linux 的系統調用作為c庫的一部分提供
。c庫中實現了Linux 的主要API,
?? 包括標準c庫函數和系統調用。
?
c)
應用編程接口(API)
其實是一組函數定義,這些函數說明了如何獲得一個給定的服務;
?? 而
系統調用
是通過軟中斷向內核發出一個明確的請求,
每個系統調用對應一個封裝例程
? (wrapper routine,唯一目的就是發布系統調用)
。一些API應用了封裝例程。
????? @a@ API還包含各種編程接口,如:C庫函數、OpenGL 編程接口等
?
d)
系統調用的實現是在內核完成的,而用戶態的函數是在函數庫中實現的
系統調用與操作系統命令
a)
操作系統命令
相對應用編程接口更高一層,每個操作系統命令都是一個可執行程序,
?? 比如ls 、hostname 等,
?
b)
操作系統命令的實現調用了系統調用
?
c)通過?
strace
?命令可以查看操作系統命令所調用的系統調用,如:
?? strace ls
?? strace hostname
?系統調用與內核函數
a)
內核函數
在形式上與普通函數一樣,但它是在內核實現的,需要滿足一些內核編程的要求
?
b)系統調用是用戶進程進入內核的接口層,它本身并非內核函數,但它是由內核函數實現的
?
c)進入內核后,不同的系統調用會找到各自對應的內核函數,
?? 這些
內核函數被稱為系統調用的“服務例程 ”
系統調用處理程序及服務例程
a)當用戶態的進程調用一個系統調用時,CPU切換
到內核態并開始執行一個內核函數
?
b)系統調用處理程序執行下列操作:
?? @a@ 在內核棧
保存大多數寄存器的內容
?
?? @b@ 調用名為
系統調用服務例程
(system call service routine)的相應的C函數來
?????
處理系統調用
?
?? @c@ 通過ret_from_sys_call(? ) 函數從系統調用返回
系統調用流程
系統調用中參數傳遞
a)每個系統調用至少有一個參數,即通過
?eax 寄存器傳遞來的系統調用號
?
b)用寄存器傳遞參數
必須滿足兩個條件
:?
?? @a@?
每個參數的長度不能超過寄存器的長度
?
?? @b@?
參數的個數不能超過6 個(包括eax 中傳遞的系統調用號)
,否則,
???????
用一個單獨的寄存器指向進程地址空間中這些參數值所在的一個內存區
?
c)在少數情況下,系統調用不使用任何參數
?
d)服務例程的
返回值必須寫到eax 寄存器
中
很多系統調用需要不止一個參數
普通C函數的參數傳遞是通過把參數值寫入堆棧(用戶態堆棧或內核態堆棧)來實現的。
但因為系統調用是一種特殊函數,它由用戶態進入了內核態,所以既不能使用用戶態的堆棧
也不能直接使用內核態堆棧
在int $0x80匯編指令之前,系統調用的參數被寫入CPU的寄存器。然后,在進入內核態調用系統調用服務例程之前,內核再把存放在CPU寄存器中的參數拷貝到內核態堆棧中。因為畢竟服務例程是C函數,它還是要到堆棧中去尋找參數的
?
系統調用小結
程序執行系統調用大致可歸結為以下幾個步驟:
?
1、程序調用libc 庫的封裝函數。
?
2、調用軟中斷int 0x80? 進入內核。
?
3、在內核中首先執行system_call 函數(首先將系統調用號(eax)和可以
???用到的所有CPU寄存器保存到相應的堆棧中(由SAVE_ALL完成)),
?? 接著根據系統調用號在系統調用表中查找到對應的系統調用服務例程。
?
4、執行該服務例程。
?
5、執行完畢后,轉入ret_from_sys_call 例程,從系統調用返回
在深入討論內核和用戶空間庫如何實現系統調用的技術細節之前,
簡要看一下內核以系統調用形式實際提供的各個函數是很有用處的。
每個系統調用都通過一個符號常數標識,符號常數的定義是平臺相關的,
在內核源碼?
<include/asm_xx/unistd.h>
?中指定,
XX
表示平臺相關,有些是?
asm_arch
,有的是
?asm_generic?
等
用于實現系統調用的處理程序函數,在形式上有如下幾個共同的特性:
1,
每個函數的名稱前綴都是 sys_?
,將該函數唯一地標識為一個系統調用,
?? 更精確的說,標識為一個系統調用的處理程序函數。
2,
所有的處理程序函數都最多接收 5 個參數
。
否則,
???
用一個單獨的寄存器指向進程? 地址空間中這些參數值所在的一個內存區即可
3,
所有的系統調用都在內核態執行
。
系統調用由內核分配的一個編號唯一標識。
所有的系統調用都由一處中樞代碼處理,根據調用編號和一個靜態表,將調用分派到具體的函數。
傳遞的參數也是由中樞代碼處理,這樣參數的傳遞獨立于實際的系統調用。
從用戶態到內核態,以及調用分派和參數傳遞,都是由匯編語言代碼實現的。
為容許用戶態和內核態之間的切換,用戶進程必須通過一條專用的機器指令,引起處理器/內核
對該進程的關注,這需要 C 標準庫的協助。內核也必須提供一個例程,來滿足切換請求并執行
相關操作。該例程不能在用戶空間中實現,因為其中需要執行普通應用程序不允許執行的命令。
系統調用表 <linux/arch/arm/kernel/calls.S> (armV7)
點擊(此處)折疊或打開
/* * linux/arch/arm/kernel/calls.S * * Copyright (C) 1995-2005 Russell King * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This file is included thrice in entry-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) /* was sys_waitpid */ CALL(sys_creat) CALL(sys_link) /* 10 */ CALL(sys_unlink) CALL(sys_execve_wrapper) CALL(sys_chdir) CALL(OBSOLETE(sys_time)) /* used by libc4 */
以系統調用 open() 函數為例:
1,X86 平臺:
1,用戶空間
?? 1,函數 open() 的聲明
????? @1@ 在使用 open()函數時,要 include <fcntl.h>
點擊(此處)折疊或打開? <glibc-2.17\include\fcntl.h>
#ifndef _FCNTL_H
#include <io/fcntl.h>
#ifndef _ISOMAC
/*?Now?define the internal interfaces.?*/
extern?int?__open64?(const?char?*__file,?int?__oflag,?...);
libc_hidden_proto?(__open64)
extern?int?__libc_open64?(const?char?*file,?int?oflag,?...);
extern?int?__libc_open?(const?char?*file,?int?oflag,?...);
libc_hidden_proto?(__libc_open)
extern?int?__libc_creat?(const?char?*file,?mode_t mode);
extern?int?__libc_fcntl?(int?fd,?int?cmd,?...);
...
點擊(此處)折疊或打開?? <glibc-2.17\io\fcntl.h>
...
/*?Open FILE?and?return a new file descriptor?for?it,?or?-1?on?error.
???OFLAG determines the type of access used.?If?O_CREAT?is?on?OFLAG,
???the third argument?is?taken as a `mode_t',?the mode of the created file.
???This?function?is?a cancellation point?and?therefore?not?marked with
???__THROW.?*/
#ifndef __USE_FILE_OFFSET64
extern?int?open?(const?char?*__file,?int?__oflag,?...)?__nonnull?((1));
#else
# ifdef __REDIRECT
extern?int?__REDIRECT?(open,?(const?char?*__file,?int?__oflag,?...),?open64)
?????__nonnull?((1));
#?else
# define open open64
# endif
#endif
#ifdef __USE_LARGEFILE64
extern?int?open64?(const?char?*__file,?int?__oflag,?...)?__nonnull?((1));
#endif
...
點擊(此處)折疊或打開
/*?Define a macro which expands inline into the wrapper code?for?a system
???call.?*/
# undef INLINE_SYSCALL
# define INLINE_SYSCALL(name,?nr,?args...)?\
??({?????????????????????????????????????\
????unsigned long?int?resultvar?=?INTERNAL_SYSCALL?(name,?,?nr,?args);?????\
????if?(__builtin_expect?(INTERNAL_SYSCALL_ERROR_P?(resultvar,?),?0))?????\
??????{?????????????????????????????????????\
????__set_errno?(INTERNAL_SYSCALL_ERRNO?(resultvar,?));?????????\
????resultvar?=?(unsigned long?int)?-1;?????????????????\
??????}?????????????????????????????????????\
????(long?int)?resultvar;?})
2,內核空間
(1)系統啟動時,對INT 0x80進行一定的初始化。
使用匯編子程序setup_idt(linux/arch/i386/kernel/head.S)初始化idt表(中斷描述符表),這時所有的入口函數偏移地址都被設為ignore_int ,如下圖所示。
2)用戶程序需要系統提供服務的時候,會通過系統調用產生一個int 0x80的軟中斷,就會進入到系統調用的入口函數,入口函數存放在以下文件當中
點擊(此處)折疊或打開??? <arch\x86\kernel\entry_32.S>
ENTRY(system_call)?
??? RING0_INT_FRAME # cant unwind into user?space?anyway?
? ? pushl?%eax ? ?? # save orig_eax ,將系統調用號壓入棧中
??? CFI_ADJUST_CFA_OFFSET 4?
??? SAVE_ALL ? ? ? #將寄存器的值壓入堆棧當中,壓入堆棧的順序對應著結構體struct pt_regs , ?????????????????? #當出棧的時候,就將這些值傳遞到結構體struct pt_regs里面的成員, ?????????????????? #從而實現匯編代碼向C程序傳遞參數
?
??? GET_THREAD_INFO(%ebp)?
????????????????? # system?call?tracing?in?operation?/?emulation? ?????????????? #GET_THREAD_INFO宏獲得當前進程的thread_info結構的地址,獲取當前進程的信息。 ?????????????? #thread_inof結構中flag字段的_TIF_SYSCALL_TRACE或_TIF_SYSCALL_AUDIT?
?????????????? #被置1。如果發生被跟蹤的情況則轉向相應的處理命令處。?
??? testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp)?
?? jnz syscall_trace_entry??? #比較結果不為零的時候跳轉。?
????????????????????????????? #對用戶態進程傳遞過來的系統調用號的合法性進行檢查
????????????????????????????? #如果不合法則跳到syscall_badsys標記的命令處。?
?? cmpl $(nr_syscalls),?%eax?
?? jae syscall_badsys ? ? ? ? #比較結果大于或者等于最大的系統調用號的時候跳轉,不合法?
????????????????????????????? #合法則跳轉到相應系統調用號所對應的服務例程當中,?
????????????????????????????? #也就是在sys_call_table表中找到了相應的函數入口點。?
?????????? #由于sys_call_table表的表項占4字節字節字節字節,因此獲得服務例程指針的具體方法 ?????????? #是將由eax保存的系統調用號乘以4再與sys_call_table表的基址相加。?
syscall_call:? ??? call?*sys_call_table(,%eax,4)?
? ? movl?%eax,PT_EAX(%esp)??? # store the return value 將保存的結果返回。
點擊(此處)折疊或打開?? <arch\x86\include\asm\ptrace.h>
struct pt_regs?{
????unsigned long bx;
????unsigned long cx;
????unsigned long dx;
????unsigned long si;
????unsigned long di;
????unsigned long bp;
????unsigned long ax;
????unsigned long ds;
????unsigned long es;
????unsigned long fs;
????unsigned long gs;
????unsigned long orig_ax;
????unsigned long ip;
????unsigned long cs;
????unsigned long flags;
????unsigned long sp;
????unsigned long ss;
};
MicrosoftInternetExplorer402DocumentNotSpecified7.8Normal0
接下來,會進入到系統調用表查找到系統調用服務程序的入口函數的地址,再進行跳轉,
整個過程如下圖所示:
??
MicrosoftInternetExplorer402DocumentNotSpecified7.8Normal0
1,系統調用號:
點擊(此處)折疊或打開?????? <arch\x86\include\asm\unistd_32.h>
#ifndef _ASM_X86_UNISTD_32_H
#define _ASM_X86_UNISTD_32_H
/*
?*?This file contains the system?call?numbers.
?*/
#define __NR_restart_syscall 0
#define __NR_exit???????? 1
#define __NR_fork???????? 2
#define __NR_read???????? 3
#define __NR_write???????? 4
#define __NR_open???????? 5
#define __NR_close???????? 6
#define __NR_waitpid???????? 7
MicrosoftInternetExplorer402DocumentNotSpecified7.8Normal02,系統調用原型:
點擊(此處)折疊或打開??? <include\linux\syscalls.h>
asmlinkage long sys_open(const?char __user?*filename,
????????????????int?flags,?int?mode);
???
MicrosoftInternetExplorer402DocumentNotSpecified7.8Normal0
其中這里使用了一個宏asmlinkage?,我們再看一下它在系統里的定義:
點擊(此處)折疊或打開??? <arch\x86\include\asm\linkage.h>
#ifdef CONFIG_X86_32
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
MicrosoftInternetExplorer402DocumentNotSpecified7.8Normal0
后面的?__attribute__((regparm(0)))表示的是不通過寄存器來傳遞參數,通過棧來傳遞
??? 所以系統調用的入口函數里面參數的傳遞:
點擊(此處)折疊或打開???? <arch\x86\kernel\entry_32.S>
ENTRY(system_call)
????SAVE_ALL ????#將寄存器的值壓入堆棧當中,壓入堆棧的順序對應著結構體struct pt_regs , ???????????????? #當出棧的時候,就將這些值傳遞到結構體struct pt_regs里面的成員, ???????????????? #從而實現從匯編代碼向C程序傳遞參數。
??? 定義 SAVE_ALL 是將參數壓到堆棧中,然后通過堆棧來進行參數的傳遞
3,獲取系統調用入口函數:
點擊(此處)折疊或打開??? <arch\x86\kernel\entry_32.S>
syscall_call:
????call?*sys_call_table(,%eax,4)
sys_call_table 每一項占用4個字節。system_call函數可以讀取 eax 寄存器,獲取當前系統調用的
系統調用號,將其乘以 4 生成偏移地址,然后以 sys_call_table 為基址,基址加上偏移地址
所指向的內容,既是應該調用的服務程序的地址。
點擊(此處)折疊或打開???????? <arch\x86\kernel\syscall_table_32.S>
ENTRY(sys_call_table)
????.long sys_restart_syscall?/*?0?-?old?"setup()"?system?call,?used?for?restarting?*/
????.long sys_exit
????.long ptregs_fork
????.long sys_read
????.long sys_write
????.long sys_open?/*?5?*/
????.long sys_close
????.long sys_waitpid
????.long sys_creat
????.long sys_link
????.long sys_unlink?/*?10?*/
?? 在本例中,sys_open 是系統調用服務程序的入口地址
4,調用系統調用函數:(在新的內核中,函數的實現并不是直接通過 sys_xxx 函數,
?????????????????????? 而是通過一個宏的封裝)
sys_open -> do_sys_open -> do_filp_open ->do_last-> nameidata_to_filp -> __dentry_open?
點擊(此處)折疊或打開????????? <fs/open.c>
SYSCALL_DEFINE3(open,?const?char __user?*,?filename,?int,?flags,?int,?mode)
{
????long ret;
????if?(force_o_largefile())
????????flags?|=?O_LARGEFILE;
????ret?=?do_sys_open(AT_FDCWD,?filename,?flags,?mode);
????/*?avoid REGPARM breakage?on?x86:?*/
????asmlinkage_protect(3,?ret,?filename,?flags,?mode);
????return ret;
}
其中宏 SYSCALL_DEFINE3 定義如下:
點擊(此處)折疊或打開?????? <include\linux\syscalls.h>
#ifdef CONFIG_FTRACE_SYSCALLS
#define SYSCALL_DEFINE0(sname)????????????????????\
????SYSCALL_TRACE_ENTER_EVENT(_##sname);????????????\
????SYSCALL_TRACE_EXIT_EVENT(_##sname);????????????\
????static struct syscall_metadata __used????????????\
???? __syscall_meta__##sname?=?{????????????????\
????????.name ????????=?"sys_"#sname,????????????\
????????.syscall_nr????=?-1,????/*?Filled?in?at boot?*/????\
????????.nb_args ????=?0,????????????????\
????????.enter_event????=?&event_enter__##sname,????\
????????.exit_event????=?&event_exit__##sname,????????\
????????.enter_fields????=?LIST_HEAD_INIT(__syscall_meta__##sname.enter_fields),?\
????};????????????????????????????\
????static struct syscall_metadata __used????????????\
???? __attribute__((section("__syscalls_metadata")))????\
?????*__p_syscall_meta_##sname?=?&__syscall_meta__##sname;????\
????asmlinkage long sys_##sname(void)
#else
#define SYSCALL_DEFINE0(name)???? asmlinkage long sys_##name(void)
#endif
#define SYSCALL_DEFINE1(name,?...)?SYSCALL_DEFINEx(1,?_##name,?__VA_ARGS__)
#define SYSCALL_DEFINE2(name,?...)?SYSCALL_DEFINEx(2,?_##name,?__VA_ARGS__)
#define SYSCALL_DEFINE3(name,?...)?SYSCALL_DEFINEx(3,?_##name,?__VA_ARGS__)
#define SYSCALL_DEFINE4(name,?...)?SYSCALL_DEFINEx(4,?_##name,?__VA_ARGS__)
#define SYSCALL_DEFINE5(name,?...)?SYSCALL_DEFINEx(5,?_##name,?__VA_ARGS__)
#define SYSCALL_DEFINE6(name,?...)?SYSCALL_DEFINEx(6,?_##name,?__VA_ARGS__)
在本例中,結合 sys_open 的定義:
點擊(此處)折疊或打開??? <include\linux\syscalls.h>
asmlinkage long sys_open(const?char __user?*filename,
????????????????int?flags,?int?mode);
可以知道,SYSCALL_DEFINE3 中的數字 3 表示這個函數需要傳遞 3 個參數。
其中 “##”表示宏中字符直接,即:
SYSCALL_DEFINEx(3,_open,__VA_ARGS__)
其中 SYSCALL_DEFINEx 定義如下:
點擊(此處)折疊或打開???? <include\linux\syscalls.h>
#define SYSCALL_DEFINEx(x,?sname,?...)????????????????\
????__SYSCALL_DEFINEx(x,?sname,?__VA_ARGS__)
其中 __SYSCALL_DEFINEx 定義如下:
點擊(此處)折疊或打開????? <include\linux\syscalls.h>
#define __SYSCALL_DEFINEx(x,?name,?...)????????????????????\
????asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__));????????\
????static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__));????\
????asmlinkage long SyS##name(__SC_LONG##x(__VA_ARGS__))????????\
????{????????????????????????????????\
????????__SC_TEST##x(__VA_ARGS__);????????????????\
????????return?(long)?SYSC##name(__SC_CAST##x(__VA_ARGS__));????\
????}????????????????????????????????\
????SYSCALL_ALIAS(sys##name,?SyS##name);????????????????\
????static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__))
在本例中如下:
點擊(此處)折疊或打開????? <include\linux\syscalls.h>
#define __SYSCALL_DEFINEx(3,?_open,?...)????????????????????\
????asmlinkage long sys_open(__SC_DECL3(__VA_ARGS__));????????\
????static inline long SYSC_open(__SC_DECL3(__VA_ARGS__));????\
????asmlinkage long SyS_open(__SC_LONG3(__VA_ARGS__))????????\
????{????????????????????????????????\
????????__SC_TEST3(__VA_ARGS__);????????????????\
????????return?(long)?SYSC_open(__SC_CAST3(__VA_ARGS__));????\
????}????????????????????????????????\
????SYSCALL_ALIAS(sys_open,?SyS_open);????????????????\
????static inline long SYSC_open(__SC_DECL3(__VA_ARGS__))
MicrosoftInternetExplorer402DocumentNotSpecified7.8Normal0
當我們自己定義一個不需要傳遞參數的系統調用的時候,可以這樣定義我們的函數:
SYSCALL_DEFINE0(mycall)
{
printk("This?is?my_sys_call\n");
return?0;
}
5,對調用的函數進行解析
點擊(此處)折疊或打開????????? <fs/open.c>
SYSCALL_DEFINE3(open,?const?char __user?*,?filename,?int,?flags,?int,?mode)
{
????long ret;
????if?(force_o_largefile())
????????flags?|=?O_LARGEFILE;
????ret?=?do_sys_open(AT_FDCWD,?filename,?flags,?mode);
????/*?avoid REGPARM breakage?on?x86:?*/
????asmlinkage_protect(3,?ret,?filename,?flags,?mode);
????return ret;
}
@a1@? open的核心處理在函數 do_sys_open() 中
點擊(此處)折疊或打開?????? <fs/open.h>
long do_sys_open(int?dfd,?const?char __user?*filename,?int?flags,?int?mode)
{
????struct open_flags op;
????int?lookup?=?build_open_flags(flags,?mode,?&op);
??? /*獲取文件名,getname()函數內部首先創建存取文件名的內存空間, ????? 然后從用戶空間把文件名拷貝到內存空間來*/
????char?*tmp?=?getname(filename);??
????int?fd?=?PTR_ERR(tmp);
????if?(!IS_ERR(tmp))?{
/*獲取一個可用的 fd,該函數通過調用 alloc_fd() 函數從 fd_table 中獲取一個可用的 fd, ?*并做一些簡單的初始化。 ?*需要注意的是:文件描述符 fd,只對本進程有效,也就是說這個 fd 只在該進程中可見, ????????????? 在別的進程中可能不沒有使用或是表示別的文件。
?*/
????????fd?=?get_unused_fd_flags(flags);
????????if?(fd?>=?0)?{
/*文件描述符 fd 獲取成功,則打開文件,創建一個 file 對象
?*/
????????????struct file?*f?=?do_filp_open(dfd,?tmp,?&op,?lookup);
????????????if?(IS_ERR(f))?{ ??????????????? //打開失敗,釋放 fd
????????????????put_unused_fd(fd);
????????????????fd?=?PTR_ERR(f);
????????????}?else?{
/*如果文件已經打開,根據 inode 所指定的信息進行打開函數,函數(參數為 f)將該文件加入到 ?*文件監控的系統中。該系統是用來監控文件被打開,創建,讀寫,關閉,修改等操作的。
?*/
????????????????fsnotify_open(f);
/*將文件指針安裝在 fd 數組中, ?*將 struct file *f 加入到 fd 索引位置處 的數組中。在后續過程中,有對這個文件描述符操作的話, ?*就會通過查找該數組得到對應的文件結構,然后進行相關的操作
?*/
????????????????fd_install(fd,?f);
????????????}
????????} ??????? //釋放放置從用戶空間拷貝過來的文件名的存儲空間
????????putname(tmp);
????}
????return fd;
}
點擊(此處)折疊或打開??? <include/linux/namei.h>
struct nameidata?{
????struct path????path;?? //當前目錄的數據結構
????struct qstr????last;?? //用以保存當前目錄的名稱
????struct path????root;
????struct inode????*inode;?/*?path.dentry.d_inode?*/
????unsigned?int????flags;
????unsigned????seq;
????int????????last_type;
????unsigned????depth;???? //連接文件的深度
????char?*saved_names[MAX_NESTED_LINKS?+?1];
????/*?Intent data?*/
????union?{
????????struct open_intent open;
????}?intent;
};
do_filp_open()解析:
點擊(此處)折疊或打開??????? <fs/namei.c>
struct file?*do_filp_open(int?dfd,?const?char?*pathname,
????????const?struct open_flags?*op,?int?flags)
{
????struct nameidata nd;
????struct file?*filp;
??? /*根據目錄打開文件 ???? */
????filp?=?path_openat(dfd,?pathname,?&nd,?op,?flags?|?LOOKUP_RCU);
????if?(unlikely(filp?==?ERR_PTR(-ECHILD)))
????????filp?=?path_openat(dfd,?pathname,?&nd,?op,?flags);
????if?(unlikely(filp?==?ERR_PTR(-ESTALE)))
????????filp?=?path_openat(dfd,?pathname,?&nd,?op,?flags?|?LOOKUP_REVAL);
????return filp;
}
path_openat():
點擊(此處)折疊或打開??????? <fs/namei.c>
static struct file?*path_openat(int?dfd,?const?char?*pathname,
????????struct nameidata?*nd,?const?struct open_flags?*op,?int?flags)
{
????struct file?*base?=?NULL;
????struct file?*filp;
????struct path path;
????int?error;
...
????current->total_link_count?=?0;
??? //對路徑進行解析
????error = link_path_walk(pathname, nd);
????if?(unlikely(error))
????????goto out_filp;
????filp?=?do_last(nd,?&path,?op,?pathname);
...
}
link_path_walk():
點擊(此處)折疊或打開????????? <fs/namei.c>
/*
?*?Name resolution.
?*?This?is?the basic name resolution?function,?turning a pathname into
?*?the final dentry.?We expect?'base'?to?be positive?and?a directory.
?*
?*?Returns 0?and?nd will have valid dentry?and?mnt?on?success.
?*?Returns?error?and?drops reference?to?input namei data?on?failure.
?*/
static?int?link_path_walk(const?char?*name,?struct nameidata?*nd)
{
????struct path?next;
????int?err;
????
...
??????? //查找文件
???? err = walk_component(nd, &next, &this, type, LOOKUP_FOLLOW);
????????if?(err?<?0)
????????????return?err;
...
}
walk_component
():
點擊(此處)折疊或打開????????? <fs/namei.c>
static inline?int?walk_component(struct nameidata?*nd,?struct path?*path,
????????struct qstr?*name,?int?type,?int?follow)
{
????struct inode?*inode;
????int?err;
????/*
?????*?"."?and?".."?are special?-?".."?especially so because it has
?????*?to?be able?to?know about the current root directory?and
?????*?parent relationships.
?????*/ ... ??? //查找文件的具體實現
????err = do_lookup(nd, name, path, &inode);
????if?(unlikely(err))?{
????????terminate_walk(nd);
????????return?err;
????}
? ...
}
最后根據條件打開設備:
點擊(此處)折疊或打開??????? <fs/namei.c>
/*
?*?Handle the last?step?of open()
?*/
static struct file?*do_last(struct nameidata?*nd,?struct path?*path,
?????????????const?struct open_flags?*op,?const?char?*pathname)
{
????struct dentry?*dir?=?nd->path.dentry;
????struct dentry?*dentry;
????int?open_flag?=?op->open_flag;
????int?will_truncate?=?open_flag?&?O_TRUNC;
????int?want_write?=?0;
????int?acc_mode?=?op->acc_mode;
????struct file?*filp;
????int?error;
????nd->flags?&=?~LOOKUP_PARENT;
????nd->flags?|=?op->intent;
...
common:
????error?=?may_open(&nd->path,?acc_mode,?open_flag);
????if?(error)
????????goto?exit; ??? //打開設備
????filp = nameidata_to_filp(nd);
...
}
點擊(此處)折疊或打開
/**
?*?nameidata_to_filp?-?convert a nameidata?to?an open filp.
?*?@nd:?pointer?to?nameidata
?*?@flags:?open flags
?*
?*?Note that this?function?destroys the original nameidata
?*/
struct file?*nameidata_to_filp(struct nameidata?*nd)
{
????const?struct cred?*cred?=?current_cred();
????struct file?*filp;
????/*?Pick up the filp from the open intent?*/
????filp?=?nd->intent.open.file;
????nd->intent.open.file?=?NULL;
????/*?Has the filesystem initialised the file?for?us??*/
????if?(filp->f_path.dentry?==?NULL)?{
????????path_get(&nd->path);
??????? /*填充一個 struct file 結構,打開設備的具體實現
???????? */
?filp = __dentry_open(nd->path.dentry, nd->path.mnt, filp,
?????????????? NULL, cred);
????}
????return filp;
}
點擊(此處)折疊或打開
static struct file?*__dentry_open(struct dentry?*dentry,?struct vfsmount?*mnt,
????????????????????struct file?*f,
????????????????????int?(*open)(struct inode?*,?struct file?*),
????????????????????const?struct cred?*cred)
{
????static?const?struct file_operations empty_fops?=?{};
????struct inode?*inode;
????int?error;
????f->f_mode?=?OPEN_FMODE(f->f_flags)?|?FMODE_LSEEK?|
????????????????FMODE_PREAD?|?FMODE_PWRITE;
????if?(unlikely(f->f_flags?&?O_PATH))
????????f->f_mode?=?FMODE_PATH;
...
????if?(!open?&&?f->f_op)
??????????//調用 def_chr_fops里的open函數,即chrdev_dev ????????open = f->f_op->open;??
????if?(open)?{
????????error = open(inode, f);???//執行該設備的 open 函數
????????if?(error)
????????????goto cleanup_all;
????}
...
}
總結
以上是生活随笔為你收集整理的linux下的系统调用函数到内核函数的追踪的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。