新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > ARM Linux異常處理之data abort二

        ARM Linux異常處理之data abort二

        作者: 時間:2016-11-09 來源:網絡 收藏
        上文提到data abort的正常處理過程中,最終會調用do_DataAbort函數,下面分析一下該函數的處理過程。

        do_DataAbort

        asmlinkage void __exception do_DataAbort(

        本文引用地址:http://www.104case.com/article/201611/317936.htm

        unsigned long addr,//導致異常的內存地址

        unsigned int fsr,//異常發生時CP15中的寄存器值,見前文

        struct pt_regs *regs)//異常發生前的寄存器值列表

        {

        const struct fsr_info *inf = fsr_info + (fsr & 15) + ((fsr & (1 << 10)) >> 6);

        if (!inf->fn(addr, fsr, regs))

        return;

        info.si_signo = inf->sig;

        info.si_errno = 0;

        info.si_code= inf->code;

        info.si_addr= (void __user *)addr;

        arm_notify_die("", regs, &info, fsr, 0);

        }

        處理data abort時,首先根據fsr的值得到產生abort的原因,然后根據此原因從一個全局數組fsr_info中得到處理此種abort的struct fsr_info結構,然后調用結構中的fn函數處理。如果fn函數為空,或者函數返回不為0,則調用arm_notify_die函數。

        arm_notify_die

        首先看一下比較簡單的情形,即fsr_info中fn未定義,此時調用arm_notify_die處理:

        void arm_notify_die(const char *str, struct pt_regs *regs,

        struct siginfo *info, unsigned long err, unsigned long trap)

        {

        if (user_mode(regs)) {

        //。。。

        force_sig_info(info->si_signo, info, current);

        } else {

        die(str, regs, err);

        }

        }

        該函數首先使用user_mode判斷abort時是屬于用戶模式還是內核模式,判斷方法是看cpsr寄存器中的模式位。按照arm的定義,模式位為0代表用戶模式。

        l如果是用戶模式,那么強制發送一個信號給導致abort的任務(注意這里的任務可能是一個線程)。具體哪個信號被發送由struct fsr_info結構體中定義的值決定,一般來說,是一個能使進程停止的信號,比如SIGSEGV等等(SIGSEGV之類的信號即使被發給一個線程,也會停止整個進程,具體可看get_signal_to_deliver函數)。

        l如果是內核模式,那么調用die函數,這是kernel處理OOPS的標準函數。

        fsr_info

        fsr_info數組定義在fault.c中,對于每一種可能導致data abort的原因,都有一個fsr_info結構與之對應。

        static struct fsr_info fsr_info[] = {

        { do_bad,SIGSEGV, 0,"vector exception"},

        //。。。

        {do_translation_fault,SIGSEGV, SEGV_MAPERR, "section translation fault"},

        { do_bad,SIGBUS,0,"external abort on linefetch"},

        {do_page_fault,SIGSEGV, SEGV_MAPERR,"page translation fault"},

        { do_bad,SIGBUS,0,"external abort on non-linefetch"},

        { do_bad,SIGSEGV, SEGV_ACCERR,"section domain fault"},

        { do_bad,SIGBUS,0,"external abort on non-linefetch"},

        { do_bad,SIGSEGV, SEGV_ACCERR,"page domain fault"},

        { do_bad,SIGBUS,0,"external abort on translation"},

        {do_sect_fault,SIGSEGV, SEGV_ACCERR,"section permission fault"},

        { do_bad,SIGBUS,0,"external abort on translation"},

        {do_page_fault,SIGSEGV, SEGV_ACCERR,"page permission fault"},

        { do_bad,SIGBUS,0,"unknown 16"},

        //。。。

        { do_bad,SIGBUS,0,"unknown 30"},

        { do_bad,SIGBUS,0,"unknown 31"}

        };

        fsr_info對大多數abort都調用do_bad函數處理,do_bad函數簡單返回1,這樣就可以繼續執行上面提到的arm_notify_die。

        fsr_info對以下四種特殊abort將作單獨處理:

        l"section translation fault"do_translation_fault
        段轉換錯誤,即找不到二級頁表

        l"page translation fault"do_page_fault
        頁表錯誤,即線性地址無效,沒有對應的物理地址

        l"section permission fault"do_sect_fault
        段權限錯誤,即二級頁表權限錯誤

        l"page permission fault"do_page_fault
        頁權限錯誤

        段權限錯誤do_sect_fault

        do_sect_fault函數直接調用do_bad_area作處理,并返回0,所以不會再經過arm_notify_die。do_bad_area中,判斷是否屬于用戶模式。如果是用戶模式,調用__do_user_fault函數;否則調用__do_kernel_fault函數。

        void do_bad_area(unsigned long addr, unsigned int fsr, struct pt_regs *regs)

        if (user_mode(regs))

        __do_user_fault(tsk, addr, fsr, SIGSEGV, SEGV_MAPERR, regs);

        else

        __do_kernel_fault(mm, addr, fsr, regs);

        __do_user_fault中,會發送信號給當前線程。

        __do_kernel_fault則比較復雜:

        l調用fixup_exception進行修復操作,fixup的具體細節可在內核文檔exception.txt中找到,它可用于處理get_user之類函數傳入的地址參數無效的情況。

        l如果不能修復,調用die函數處理oops。

        l如果沒有進程上下文,內核會在上一步的oops中panic。所以到這里肯定有一個進程與之關聯,于是調用do_exit(SIGKILL)函數退出進程,SIGKILL會被設置在task_struct的exit_code域。

        段表錯誤do_translation_fault

        do_translation_fault函數中,會首先判斷引起abort的地址是否處于用戶空間。

        l如果是用戶空間地址,調用do_page_fault,轉入和頁表錯誤、頁權限錯誤同樣的處理流程。

        l如果是內核空間地址,會判斷該地址對應的二級頁表指針是否在init_mm中。如果在init_mm里面,那么該二級頁表指針到當前進程的一級頁表;否則,調用do_bad_area處理(可能會調用到fixup)。

        對段表錯誤的處理邏輯的個人理解如下(不保證完全準確):
        Linux產生段表錯誤,除了fixup之外還有兩種原因:一個是用戶空間映射的線性地址出現異常,另一個是內核中調用vmalloc分配的線性地址出現異常。對用戶空間地址的異常處理很容易理解。對于內核地址,從vmalloc的實現代碼中可以看到,它分配的線性空間的映射關系都會保存到全局變量init_mm中,所以,任何vmalloc生成的線性空間的二級頁表都應該在init_mm中找到。(init_mm是內核的mm_struct,管理整個內核的內存映射)。

        從這里也可以看出,對vmalloc的地址訪問可能會產生兩次異常:第一次是段表錯誤,生成二級頁表;第二次是頁表錯誤,分配真正的物理頁面到線性空間。

        關于init_mm的細節可以參考http://my.chinaunix.net/space.php?uid=25471613&do=blog&id=323374,大概意思是內核頁表改變時只改變init進程的內核頁表init_mm,其它進程通過缺頁異常從init_mm更新自己維護的內核頁表。

        頁表錯誤do_page_fault

        頁權限錯誤do_page_fault

        do_page_fault完成了真正的物理頁面分配工作,另外棧擴展、mmap的支持等也都在這里。對于物理頁面的分配,會調用到do_anonymous_page->。。。-> __rmqueue,__rmqueue中實現了物理頁面分配的伙伴算法。

        如果當前沒有足夠物理頁面供內存分配,即分配失敗:

        l內核模式下的abort會調用__do_kernel_fault,這與段權限錯誤中的處理一樣。

        l用戶模式下,會調用do_group_exit退出該任務所屬的進程。

        用戶程序申請內存空間時,如果庫函數本身的內存池不能滿足分配,會調用brk系統調用向系統申請擴大堆空間。但此時擴大的只是線性空間,直到真正使用到那塊線性空間時,系統才會通過data abort分配物理頁面。用戶空間的malloc函數返回不為NULL只能說明得到了線性空間的資源,但物理內存可能并沒有映射上去,所以真正物理內存分配失敗時,進程還是會以資源不足為由,直接退出。



        評論


        技術專區

        關閉
        主站蜘蛛池模板: 仁布县| 汶上县| 扬中市| 米泉市| 舟山市| 苍溪县| 林州市| 乌鲁木齐县| 隆德县| 溧阳市| 朝阳市| 商洛市| 衡南县| 临潭县| 梅河口市| 鹤岗市| 汾西县| 吉林市| 宁晋县| 新竹县| 新宁县| 玉山县| 木里| 慈利县| 敦化市| 玉田县| 广水市| 成武县| 益阳市| 石景山区| 岳普湖县| 寻乌县| 台东县| 绥化市| 西畴县| 循化| 县级市| 成安县| 太湖县| 项城市| 北碚区|