Linux系統調用始末續...

在上次的getpid系統調用中,發現getpid函數只能第一次執行進入系統調用,后面的就直接執行,似乎沒利用系統調用。

先查一下直接利用int $0x80的系統調用流程。

函數如下:

int GetpidAsm(int argc, char **argv)
{
    pid_t pid;
    asm volatile(
    "mov $20, %%eax\n\t"
    "int $0x80\n\t"
    "mov %%eax, %0\n\t"
    :"=m"(pid)
    );
    printf("current process's pid(ASM):%d\n",pid);
    return 0;
}

在系統調用執行的時候,函數就停在了設置的斷點sys_getpid處,如下圖:

圖中的SYSCALL_DEFINE宏甚是顯眼,有資料解釋如下:

It is used (obviously) to define the given block of code as a system call. For example, fs/ioctl.c has the following code :

SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
{
/* do freaky ioctl stuff */
}

Such a definition means that the ioctl syscall is declared and takes three arguments. The number next to the SYSCALL_DEFINE means the number of arguments. For example, in the case of getpid(void), declared in kernel/timer.c, we have the following code :

SYSCALL_DEFINE0(getpid)
{
        return task_tgid_vnr(current);
}

只不過getpid(void)現在的位置挪到了kernel/sys.c中。

  • 邏輯似乎清晰了些,不妨去查查SYSCALL_DEFINE的水表。

Linux/include/linux/syscalls.h這個目錄下,我們可以看到

Linux系統調用之SYSCALL_DEFINE已經有前輩解釋了這樣做的目的,以及為什么要這樣。不得不服其中的精妙。

這就是為什么,在我們使用getpid()這個函數的時候,我們并不知道系統究竟做了什么,因為系統里面并沒有這個函數的直接實現。而是通過一堆宏定義在預處理的時候展開。

那么索性也來做一次展開,對getpid的展開。
系統停在斷點sys_time的時候,代碼停在了這個位置:

   │816     SYSCALL_DEFINE0(getpid)                                       
b+>│817     {                                                             
   │818             return task_tgid_vnr(current);                        
   │819     }    

宏展開規則如下:

175 #define SYSCALL_METADATA(sname, nb, ...)
...
178 #define SYSCALL_DEFINE0(sname)                                  \
179         SYSCALL_METADATA(_##sname, 0);                          \
180         asmlinkage long sys_##sname(void)

使用的宏為SYSCALL_DEFINE0(getpid) ->asmlinkage long sys_getpid(void);
顯然,展開之后的函數變為:

   │816     asmlinkage long sys_getpid(void)                                       
b+>│817     {                                                             
   │818             return task_tgid_vnr(current);                        
   │819     }    

這個時候,對sys_getpid()的調用一目了然。
那么,顯然,接下來的調用是傳入的參數current,不是很明白。

   │10      DECLARE_PER_CPU(struct task_struct *, current_task);          
   │11                                                                    
   │12      static __always_inline struct task_struct *get_current(void)  
   │13      {                                                             
  >│14              return this_cpu_read_stable(current_task);            
   │15      }                                                             
   │16                                                                    
   │17      #define current get_current() 

#define this_cpu_read_stable(var) percpu_from_op("mov", var, "p" (&(var)))只是一個宏,在單CPU上,應該沒有效果。

  • 接下來執行到task_tgid_vnr
   │1770    static inline pid_t task_tgid_vnr(struct task_struct *tsk)    
   │1771    {                                                             
   │1772            return pid_vnr(task_tgid(tsk));                       
   │1773    }                                                             
  • 處理傳入的參數task_tgid,實際上返回了一個結構體pid。
   │1708    static inline struct pid *task_tgid(struct task_struct *task) 
   │1709    {                                                             
  >│1710            return task->group_leader->pids[PIDTYPE_PID].pid;     
   │1711    }                                                             
  • upid、pid、pid_link定義如下:
 44 /*
 45  * struct upid is used to get the id of the struct pid, as it is
 46  * seen in particular namespace. Later the struct pid is found with
 47  * find_pid_ns() using the int nr and struct pid_namespace *ns.
 48  */
 49 
 50 struct upid {
 51         /* Try to keep pid_chain in the same cacheline as nr for find_vpid */
 52         int nr;
 53         struct pid_namespace *ns;
 54         struct hlist_node pid_chain;
 55 };
 56 
 57 struct pid
 58 {
 59         atomic_t count;
 60         unsigned int level;
 61         /* lists of tasks that use this pid */
 62         struct hlist_head tasks[PIDTYPE_MAX];
 63         struct rcu_head rcu;
 64         struct upid numbers[1];
 65 };
 66 
 67 extern struct pid init_struct_pid;
 68 
 69 struct pid_link
 70 {
 71         struct hlist_node node;
 72         struct pid *pid;
 73 };
  • pid_vnr實際上調用了pid_nr_ns,傳入了一個task_active_pid_ns來獲取namespace,不太懂。。。
    其實最后返回的就是pid_nr_ns返回的nr;
   │497     pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns)    
   │498     {                                                             
   │499             struct upid *upid;                                    
   │500             pid_t nr = 0;                                         
   │501                                                                   
   │502             if (pid && ns->level <= pid->level) {                 
   │503                     upid = &pid->numbers[ns->level];              
   │504                     if (upid->ns == ns)                           
   │505                             nr = upid->nr;                        
   │506             }                                                     
   │507             return nr;                                            
   │508     }                                                             
   │509     EXPORT_SYMBOL_GPL(pid_nr_ns);                                    
   │510                                                                  
   │511     pid_t pid_vnr(struct pid *pid)                                
  >│512     {                                                             
   │513             return pid_nr_ns(pid, task_active_pid_ns(current));   
   │514     }  
  • 獲取namespace
   │542     struct pid_namespace *task_active_pid_ns(struct task_struct *tsk)
   │543     {                                                             
  >│544             return ns_of_pid(task_pid(tsk));                      
   │545     }                                                             
   │546     EXPORT_SYMBOL_GPL(task_active_pid_ns);                        
   │124     /*                                                            
   │125      * ns_of_pid() returns the pid namespace in which the specifie
   │126      * allocated.                                                 
   │127      *                                                            
   │128      * NOTE:                                                      
   │129      *      ns_of_pid() is expected to be called for a process (task) that has
   │130      *      an attached 'struct pid' (see attach_pid(), detach_pid()) i.e @pid
   │131      *      is expected to be non-NULL. If @pid is NULL, caller should handle
   │132      *      the resulting NULL pid-ns.                            
   │133      */    
   │134     static inline struct pid_namespace *ns_of_pid(struct pid *pid)
   │135     {                                                             
   │136             struct pid_namespace *ns = NULL;                      
  >│137             if (pid)                                              
   │138                     ns = pid->numbers[pid->level].ns;             
   │139             return ns;                                            
   │140     }                                                                                                                    
  • 數據結構太復雜,有些關鍵的地方并不理解什么意思。從字面上理解,分析到這里的時候,并沒有發現有貓膩。

突然有個想法,進程本身并沒有有局部變量或者全局變量來保存這個pid的值,因為沒必要(進程結束回收后,進程號直接作廢了,每次啟動的時候都會分配不同的pid)。

那么會不會是編譯器的原因,這個值放在了寄存器中了?畢竟是1號進程,這個值以后也不再會變動了,編譯器發現1號進程的pid不會變化,把這個值緩存起來了?每次需要讀取的時候,直接從這里拿?

在我們使用標準API的時候,一般都會包含unistd.h這個頭文件。
我們需要的信息都隱藏在這里面。

unistd.h 中所定義的接口通常都是大量針對 系統調用的封裝(英語:wrapper functions),如 fork、pipe 以及各種 I/O 原語(read、write、close 等等)

還沒有好的思路,下次再寫。。


stackoverflow上一個大牛的回答,還沒有完全理解。
What is better “int 0x80” or “syscall”?

My answer here covers your question.
In practice, recent kernels are implementing a VDSO, notably to dynamically optimize system calls (the kernel sets the VDSO to some code best for the current processor). So you should use the VDSO, and you'll better use, for existing syscalls, the interface provided by the libc.
Notice that, AFAIK, a significant part of the cost of simple syscalls is going from user-space to kernel and back. Hence, for some syscalls (probably gettimeofday, getpid...) the VDSO might avoid even that (and technically might avoid doing a real syscall). For most syscalls (like open, read, send, mmap ....) the kernel cost of the syscall is large enough to make any improvement of the user-space to kernel space transition (e.g. using SYSENTER or SYSCALL machine instructions instead of INT) insignificant.

  • 注意這一句:
    **Hence, for some syscalls (probably gettimeofday, getpid...) the VDSO might avoid even that (and technically might avoid doing a real syscall). **

  • 大牛的回答,要看這么多東西,給跪了。

System calls Implementation

It is explained in Linux Assembly Howto. And you should read wikipedia syscall page (and also about VDSO), and also intro(2) & syscalls(2) man pages. See also this answer and this one. Look also inside Gnu Libc & musl-libc source code. Learn also to use strace
to find out which syscalls are made by a given command or process.
See also the calling conventions and Application Binary Interface specification relevant to your system. For x86-64 it is here.

  • 又見一出資料,顯示,getpid 緩存了pids

C library/kernel differences
Since glibc version 2.3.4, the glibc wrapper function for getpid() caches PIDs, so as to avoid additional system calls when a process calls getpid() repeatedly. Normally this caching is invisible, but its correct operation relies on support in the wrapper functions for fork(2), vfork(2), and clone(2): if an application bypasses the glibc wrappers for these system calls by using syscall(2), then a call to getpid() in the child will return the wrong value (to be precise: it will return the PID of the parent process). See also clone(2) for discussion of a case where getpid() may return the wrong value even when invoking clone(2) via the glibc wrapper function.

思路斷掉了,不知道該怎么捋清楚。

但是在使用API getpid的時候,是如何和聯系上系統調用呢?兩者是如何對應起來的呢?glibc中有如下的代碼:

pid_t getpid(void)
{
pid_t (f)(void);
f = (pid_t (
)(void)) dlsym (RTLD_NEXT, "getpid");
if (f == NULL)
error (EXIT_FAILURE, 0, "dlsym (RTLD_NEXT, "getpid"): %s", dlerror ());
return (pid2 = f()) + 26;
}

這個dlsym的大概意思是說從打開的共享庫中找到getpid這個函數的地址,然后直接拿來調用。
之前看到過vdso,難道和這個有關系?
最后返回值加上26是什么意思,又不明白了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容