亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費注冊 查看新帖 |

Chinaunix

  平臺 論壇 博客 文庫
最近訪問板塊 發(fā)新帖
查看: 922 | 回復: 0
打印 上一主題 下一主題

Linux中斷處理之系統(tǒng)調(diào)用 [復制鏈接]

論壇徽章:
0
跳轉(zhuǎn)到指定樓層
1 [收藏(0)] [報告]
發(fā)表于 2008-11-11 17:04 |只看該作者 |倒序瀏覽
一:前言
有時候,用戶空間為了滿足某些要求,要從內(nèi)核空間去進行操作,比例建立文件,建立socket,查看內(nèi)核數(shù)據(jù)等等.因此操作系統(tǒng)必須提供一種方式.供用戶態(tài)轉(zhuǎn)入內(nèi)核態(tài).我們在前面分析過tarp_init()函數(shù).只有異常跟系統(tǒng)調(diào)用才能從用戶空間轉(zhuǎn)入到內(nèi)核空間(PL值為3).但是異常通常帶有很大的隨意性,用戶程序不好控制異常的發(fā)生點.所以,系統(tǒng)調(diào)用就成了溝通用戶空間與內(nèi)核空間的一座重要的橋梁.
二:系統(tǒng)調(diào)用在用戶空間的調(diào)用方式.
在前面分析過.系統(tǒng)調(diào)用的中斷號為0x80.所以,只要在用戶空間通過int 0x80軟中斷方式就可以陷入內(nèi)核了.為了區(qū)分不同的系統(tǒng)調(diào)用.必須為每一個調(diào)用指定一個序號.即系統(tǒng)調(diào)用號.通常,在用int 0x80中斷之前,先將中斷號放入寄存器eax.
三:系統(tǒng)調(diào)用的參數(shù)傳遞方式
系統(tǒng)調(diào)用是可以傳遞參數(shù)的.例如:int open(const char *pathname, int flags),那這些參數(shù)是如何傳遞的呢?系統(tǒng)調(diào)用采用寄存器來傳值,這樣,進入內(nèi)核空間之后,取值非常方便.這幾個寄存器依次是:ebx,ecx,edx,esi,edi,ebp.如果參數(shù)個數(shù)超過了6個,或者參數(shù)的大小大于32位,可以用傳遞參數(shù)地址的方法.陷入到內(nèi)核空間之后,再從地址中去取值.回憶一下:我們在前面分析過的系統(tǒng)調(diào)用過程:
ENTRY(system_call)
      pushl %eax            # save orig_eax(系統(tǒng)調(diào)用號)
      SAVE_ALL
      ……
SAVE_ALL:
#define __SAVE_ALL \
      cld; \
      pushl %es; \
      pushl %ds; \
      pushl %eax; \
      pushl %ebp; \
      pushl %edi; \
      pushl %esi; \
      pushl %edx; \
      pushl %ecx; \
      pushl %ebx; \
      movl $(__USER_DS), %edx; \
      movl %edx, %ds; \
      movl %edx, %es;
發(fā)現(xiàn)了吧,系統(tǒng)調(diào)用時,把ebp到ebx壓棧,再調(diào)用系統(tǒng)調(diào)用處理函數(shù).這里其實是模擬了一次函數(shù)調(diào)用過程.在系統(tǒng)調(diào)用處理函數(shù)中,會根據(jù)處理函數(shù)的參數(shù)個數(shù),到當前堆棧中去取參數(shù)值.
既然在系統(tǒng)調(diào)用的時候可以用地址作為參數(shù),那就必須要檢查這個地址的合法性了.在以前的內(nèi)核中.會對地址進行嚴格的檢查.即會查對進程的vma判斷此線性地址是否屬于進程所擁有.權(quán)限是否合法.這個過程是相當耗時的.其實雖然有地址非法的錯誤,但畢竟是少數(shù).犯不著為少數(shù)錯誤降低整個系統(tǒng)的效率.那還要不要檢查呢?當然要了.地址非法訪問會產(chǎn)生頁面異常,推遲到頁面異常程序中再處理
四:系統(tǒng)調(diào)用相關代碼分析:
在前面我們在 linux中斷處理之初始化>>一文中分析過系統(tǒng)調(diào)用的進入和返回過程.再來看下代碼:
ENTRY(system_call)
      pushl %eax            # save orig_eax(系統(tǒng)調(diào)用號)
      SAVE_ALL
      GET_THREAD_INFO(%ebp) #當前進程的task放入ebp
                            # system call tracing in operation
      #如果定義了系統(tǒng)調(diào)用跟蹤標志
      testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
      jnz syscall_trace_entry
      #判斷系統(tǒng)調(diào)用號是否合法(是否超過NR_syscalls).在x86中,這個值為285
      cmpl $(nr_syscalls), %eax
      #如果非法.跳至syscall_badsys:即返回-ENOSYS
      jae syscall_badsys
syscall_call:
      //調(diào)用sys_call_table中尋找第eax項(第項占四字節(jié)).
      call *sys_call_table(,%eax,4)
      movl %eax,EAX(%esp)         # store the return value
syscall_exit:
      cli                   # make sure we don't miss an interrupt
                            # setting need_resched or sigpending
                            # between sampling and the iret
      movl TI_flags(%ebp), %ecx
      testw $_TIF_ALLWORK_MASK, %cx     # current->work
      jne syscall_exit_work
restore_all:
      RESTORE_ALL
Sys_call_table定義如下:
ENTRY(sys_call_table)
      .long sys_restart_syscall   /* 0 - old "setup()" system call, used for restarting */
      .long sys_exit
      .long sys_fork
      ……
這個表通常被稱為系統(tǒng)調(diào)用表.如系統(tǒng)調(diào)用號1對應的處理函數(shù)為sys_exit.
下面以sys_sethostname為例進行分析:
asmlinkage long sys_sethostname(char __user *name, int len)
{
      int errno;
      char tmp[__NEW_UTS_LEN];

      //檢查是否為特權(quán)用戶?
      if (!capable(CAP_SYS_ADMIN))
           return -EPERM;
      //參數(shù)長度檢查
      if (len  __NEW_UTS_LEN)
           return -EINVAL;
      down_write(&uts_sem);
      errno = -EFAULT;
      //將用戶空間的值copy到內(nèi)核空間中
if (!copy_from_user(tmp, name, len)) {
      //如果成功的話,設置system_utsname.nodename
           memcpy(system_utsname.nodename, tmp, len);
           system_utsname.nodename[len] = 0;
           errno = 0;
      }
      up_write(&uts_sem);
      return errno;
}
Copy_from_user()是一個通用的api.詳細分析一下
unsigned long
copy_from_user(void *to, const void __user *from, unsigned long n)
{
      //判斷from,n的合法性
      if (access_ok(VERIFY_READ, from, n))
           n = __copy_from_user(to, from, n);
      else
           //參數(shù)非法
           memset(to, 0, n);
      return n;
}
Access_ok()用來初步檢查參數(shù)的合法性.定義如下:
#define access_ok(type,addr,size) (likely(__range_ok(addr,size) == 0))
#define __range_ok(addr,size) ({ \
      unsigned long flag,roksum; \
      __chk_user_ptr(addr); \
      asm("addl %3,%1 ; sbbl %0,%0; cmpl %1,%4; sbbl $0,%0" \
           :"=&r" (flag), "=r" (roksum) \
           :"1" (addr),"g" ((int)(size)),"rm" (current_thread_info()->addr_limit.seg)); \
      flag; })
實際上,在此只是檢查了add+size 是否大于current_thread_info()->addr_limit.seg(進程所允許的最大數(shù)據(jù)段)
轉(zhuǎn)入__copy_from_user():
static __always_inline unsigned long
__copy_from_user(void *to, const void __user *from, unsigned long n)
{
      //可能會引起睡眠.例如發(fā)生缺頁異常
      might_sleep();
      if (__builtin_constant_p(n)) {
           unsigned long ret;

           //對特殊情況的優(yōu)化
           switch (n) {
           case 1:
                 __get_user_size(*(u8 *)to, from, 1, ret, 1);
                 return ret;
           case 2:
                 __get_user_size(*(u16 *)to, from, 2, ret, 2);
                 return ret;
           case 4:
                 __get_user_size(*(u32 *)to, from, 4, ret, 4);
                 return ret;
           }
      }
      return __copy_from_user_ll(to, from, n);
}
__get_user_size的代碼比較簡單,我們轉(zhuǎn)入到__copy_from_user_ll()以便分析更普通的情況
unsigned long __copy_from_user_ll(void *to, const void __user *from,
                            unsigned long n)
{
      //在沒有定義CONFIG_X86_INTEL_USERCOPY的情況下,此函數(shù)恒為1
      if (movsl_is_ok(to, from, n))
           __copy_user_zeroing(to, from, n);
      else
           n = __copy_user_zeroing_intel(to, from, n);
      return n;
}
跟蹤進__copy_user_zeroing()
#define __copy_user_zeroing(to,from,size)                     \
do {                                              \
      int __d0, __d1, __d2;                             \
      __asm__ __volatile__(                             \
           "     cmp  $7,%0\n"                          \
           "     jbe  1f\n"                        \
           "     movl %1,%0\n"                          \
           "     negl %0\n"                        \
           "     andl $7,%0\n"                          \
           "     subl %0,%3\n"                          \
           "4:   rep; movsb\n"                          \
           "     movl %3,%0\n"                          \
           "     shrl $2,%0\n"                          \
           "     andl $3,%3\n"                          \
           "     .align 2,0x90\n"                  \
           "0:   rep; movsl\n"                          \
           "     movl %3,%0\n"                          \
           "1:   rep; movsb\n"                          \
           "2:\n"                                       \
           ".section .fixup,\"ax\"\n"                   \
           "5:   addl %3,%0\n"                          \
           "     jmp 6f\n"                         \
           "3:   lea 0(%3,%0,4),%0\n"                    \
           "6:   pushl %0\n"                            \
           "     pushl %%eax\n"                         \
           "     xorl %%eax,%%eax\n"                    \
           "     rep; stosb\n"                          \
           "     popl %%eax\n"                          \
           "     popl %0\n"                        \
           "     jmp 2b\n"                         \
           ".previous\n"                                \
           ".section __ex_table,\"a\"\n"                     \
           "     .align 4\n"                            \
           "     .long 4b,5b\n"                         \
           "     .long 0b,3b\n"                         \
           "     .long 1b,6b\n"                         \
           ".previous"                                  \
           : "=&c"(size), "=&D" (__d0), "=&S" (__d1), "=r"(__d2)   \
           : "3"(size), "0"(size), "1"(to), "2"(from)        \
           : "memory");                                 \
}
首先,我們先思考一個問題.怎么將用戶空間的數(shù)據(jù)拷貝到內(nèi)核空間?我們知道,32位平臺上,1~3G的線性地址屬于進程專用.3~4屬于內(nèi)核空間,所有進程共享.在前面進行內(nèi)存管理的時候,我們分析過內(nèi)核有內(nèi)核頁目錄.那進程的頁目錄與內(nèi)核的內(nèi)目錄有什么關系呢?從硬件的角度來看,系統(tǒng)調(diào)用從空戶空間切換到內(nèi)核空間的時候,并沒有重新裝載CR3寄存器,也就是說頁目錄沒有發(fā)生改變.事實上,所有進程的高1G映射頁目錄都是一樣的,都為內(nèi)核頁目錄.所以在內(nèi)核空間的尋址與用戶空間的尋址也是一樣的,所以就可以直接進行數(shù)據(jù)的拷貝了.
這段代碼從開始一直到  ".previous\n"前面是字串的copy操作,相當于我們使用 *(int *)dst = * (int *)src的操作.后半部份涉及到gcc的擴展語法: section 把后述代碼加至進程的相應段.
在前面分析過.在進行具體的拷貝之前,只是粗略的檢查了一下參數(shù).要是參數(shù)異;蛘咭截惖膬(nèi)存數(shù)據(jù)被交換怎么辦呢?這就需要do_page_fault()去處理了.在上面的代碼中,引起do_page_fault()只可能是由標號4,0,1引起的.在頁面異常的代碼分析過,我們說過,如果是一個非法的訪問,就會到異常表中找相應的處理函數(shù).回顧一下代碼:
asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
{
      ……
      ……
      no_context:
      /* Are we prepared to handle this kernel fault?  */
      if (fixup_exception(regs))
           return;
      ……
}
Fixup_exception()函數(shù)代碼如下:
int fixup_exception(struct pt_regs *regs)
{
      const struct exception_table_entry *fixup;

#ifdef CONFIG_PNPBIOS
      if (unlikely((regs->xcs & ~15) == (GDT_ENTRY_PNPBIOS_BASE
      {
           extern u32 pnp_bios_fault_eip, pnp_bios_fault_esp;
           extern u32 pnp_bios_is_utter_crap;
           pnp_bios_is_utter_crap = 1;
           printk(KERN_CRIT "PNPBIOS fault.. attempting recovery.\n");
           __asm__ volatile(
                 "movl %0, %%esp\n\t"
                 "jmp *%1\n\t"
                 : : "g" (pnp_bios_fault_esp), "g" (pnp_bios_fault_eip));
           panic("do_trap: can't hit this");
      }
#endif
      //從異常表中查找相應處理代碼的地址.對應的參數(shù)是引起異常的代碼地址
      fixup = search_exception_tables(regs->eip);
      if (fixup) {
           //將地址存入調(diào)用之前的eip寄存器.這樣在異常返回后,就會執(zhí)行對應的代碼
           regs->eip = fixup->fixup;
           return 1;
      }

      return 0;
}
轉(zhuǎn)入search_exception_tables()
const struct exception_table_entry *search_exception_tables(unsigned long addr)
{
      const struct exception_table_entry *e;

      //參數(shù):起始地址,結(jié)束地址,引起異常的地址
      e = search_extable(__start___ex_table, __stop___ex_table-1, addr);
      if (!e)
           e = search_module_extables(addr);
      return e;
}
//利用二分法查找
const struct exception_table_entry *
search_extable(const struct exception_table_entry *first,
             const struct exception_table_entry *last,
             unsigned long value)
{
      while (first
           const struct exception_table_entry *mid;

           mid = (last - first) / 2 + first;
           /*
            * careful, the distance between entries can be
            * larger than 2GB:
            */
           if (mid->insn
                 first = mid + 1;
           else if (mid->insn > value)
                 last = mid - 1;
           else
                 return mid;
        }
        return NULL;
}
exception_table_entry結(jié)構(gòu)如下示:
struct exception_table_entry
{
      //insn:產(chǎn)生異常指令的地址
      //fixup:修復地址
      unsigned long insn, fixup;
};
返回到我們上面討論的__copy_user_zeroing()的代碼:
……
".section __ex_table,\"a\"\n"                     \
           "     .align 4\n"                            \
           "     .long 4b,5b\n"                         \
           "     .long 0b,3b\n"                         \
           "     .long 1b,6b\n"                         \
…….
對應到異常表就是:
如果異常處理是在標號4處發(fā)生的,那么修復地址是標號5
如果異常處理是在標號0處發(fā)生的,那么修復地址是標號3
如果異常處理是在標號1處發(fā)生的,那么修復地址是標號6
不止是在copy_from_user()有實現(xiàn)中用了這樣的方法,在所有會引起異常的代碼中,就加入了相應的異常項.也就說誰,誰可能會引起異常,誰就負表在異常表中加入相應項.在鏈接的時候,ld會按照地址升序?qū)⑾嚓P項插入到異常表
五:再論異常表
系統(tǒng)常用/異常/IRQ中斷處理程序處理完后,都會經(jīng)過RESTORE_ALL返回,之前為了分析整個過程,沒有分析這個過程可能會引起的異常情況.
#define __RESTORE_INT_REGS \
      popl %ebx; \
      popl %ecx; \
      popl %edx; \
      popl %esi; \
      popl %edi; \
      popl %ebp; \
      popl %eax

#define __RESTORE_REGS \
      __RESTORE_INT_REGS; \
111:  popl %ds;  \
222:  popl %es;  \
.section .fixup,"ax"; \
444:  movl $0,(%esp);  \
      jmp 111b;  \
555:  movl $0,(%esp);  \
      jmp 222b;  \
.previous;       \
.section __ex_table,"a";\
      .align 4;  \
      .long 111b,444b;\
      .long 222b,555b;\
.previous

#define __RESTORE_ALL \
      __RESTORE_REGS   \
      addl $4, %esp;   \
333:  iret;      \
.section .fixup,"ax";   \
666:  sti;       \
      movl $(__USER_DS), %edx; \
      movl %edx, %ds; \
      movl %edx, %es; \
      pushl $11; \
      call do_exit;    \
.previous;       \
.section __ex_table,"a";\
      .align 4;  \
      .long 333b,666b;\
.previous
可以看到,在標號111,222,333處加入了異常處理,相應的指令分別是popl %ds, popl %es和iret.
為什么會這三條指令會引起異常呢?先看popl %ds 和popl %es
每當裝載一個段寄存器的時候,CPU都要根據(jù)這個段選擇碼在GDT/LDT中找到相應的描述項.并加以檢查.如果描述項和選擇項都正常,就會把它裝入CPU的”不可見”部份.在映射的時候就不必再去取相應的值.如果選擇碼或者描述符無效或者不存在時,就會產(chǎn)生一次 “全面”保護異常.當這樣的異常發(fā)生在系統(tǒng)空間時,都要為它建立相關的異常表項.在代碼中,異常處理代碼把esp置0.然后再執(zhí)行pop指令,實際上把ds,es置為零.這樣就可以返回到用戶空間了.至于用戶空間會發(fā)生什么,那就由相應的異常處理程序去處理,最多不過把這個進程kill掉.
那iret怎么會引起異常呢?
在>一文中分析過,中斷返回時,會從系統(tǒng)堆棧中取值恢復相關的寄存器.包括CS,EIP,如果運行級別不相同的會,還會恢復SS,ESP.同上面的DS,ES一樣,這里也有兩個段寄存器CS,SS.不過,CS,SS不接受0值,所以不能像DS,ES那樣處理了.那就沒有其它的辦法了.只能把當前進程kill掉,以保證整個系統(tǒng)的運行..
六:小結(jié)
在這一節(jié)里,主要分析了系統(tǒng)調(diào)用的流程,用戶空間與系統(tǒng)空間的數(shù)據(jù)交互.可能會引起異常指令的修正處理.看完整個流程之后,我們很容易添加自己的系統(tǒng)調(diào)用.但是,如果是在實際項目中的話,那就得仔細權(quán)衡添加系統(tǒng)調(diào)用帶來的利與弊了.

本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u/26185/showart_1405395.html
您需要登錄后才可以回帖 登錄 | 注冊

本版積分規(guī)則 發(fā)表回復

  

北京盛拓優(yōu)訊信息技術(shù)有限公司. 版權(quán)所有 京ICP備16024965號-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報專區(qū)
中國互聯(lián)網(wǎng)協(xié)會會員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關心和支持過ChinaUnix的朋友們 轉(zhuǎn)載本站內(nèi)容請注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP