linux源碼解讀(十二):系統調用(strace命令0和中斷&字節跳動HIDS簡要分析


  中斷是整個計算機體系最核心的功能之一,關於中斷硬件原理可以參考文章末尾的鏈接1(https://www.cnblogs.com/theseventhson/p/13068709.html),這里不再贅述;中斷常見的種類如下:

  • 硬件中斷:鍵盤、鼠標、網卡等輸入
  • 軟件中斷:int 3、int 0xe(page fault)
  • 自定義中斷
  • 信號中斷(kill -signum),比如kill -9 pid殺死進程
  • 系統異常和錯誤->利於排錯

   1、(1)本人剛開始學習的時候,看到很多資料把中斷、異常、陷阱放在一起介紹,很容易混淆這3個概念,這里詳細列舉一下各個概念之間的關系如下:

         

  •  異步中斷:都是由硬件產生的,比如鍵盤、鼠標、網卡等輸入,導致硬件中斷的事件是不可預測的(cpu也不可能知道用戶啥時候敲鍵盤、移動鼠標,也不可能提前知道網卡什么時候接收到數據);
  •  同步中斷:都是可預見的指令流產生的
    •   fault:最常見的是page fault缺頁異常;異常產生后可以重新執行產生異常的指令;逆向時用這個特性可以通過更改目標內存屬性產生的page fault異常達到定位關鍵代碼的目的
    •        trap:
      •   軟中斷:最常見的就是int 3了,常用於調試器單步調試;trap產生后會執行下一條指令,所以調試器調試時插入int 3才能達到單步調試的目的;
      •        系統調用:3環app要調用內核的api,比如讀寫文件、通過wifi收發數據、在屏幕打印日志等,需要進入0環執行;linux早期采用int 0x80做系統調用(早期的windows比如xp系統是通過int 0x2e進入內核的);后來x86架構的cpu硬件支持syscall、sysenter這種專門的系統調用(也就是從3環進入0環)指令;由於syscall/sysenter這種cpu原生支持的系統調用指令沒有特權級別檢查的處理,也沒有壓棧的操作,所以執行速度比 INT n/IRET 快了不少;如下:時間快了接近1倍! 
    •        abort:比如除0

   (2)上述所有的操作,統稱為中斷!再說直白一點,中斷的本質就是被打斷!舉個例子:cpu正在執行A進程的代碼,突然用戶敲了一下鍵盤,或者移動了鼠標,這時候就要馬上接受用戶的輸入,然后采取相應的措施處理用戶的輸入;

  •   接受用戶輸入的功能已經在硬件上實現了,接下來操作系統需要做的就是實現中斷響應的方法了,俗稱handler!
  •        中斷的種類有很多(linux有256種中斷),這么多中斷種類,為了方便管理,各自都是有自己的編號的!每個編號自然也會有對應的響應handler(官方叫做中斷處理routine);這么多的handler,執行的時候怎么才能快速找到了?
  •        cpu硬件層面有個IDTR寄存器,存放了IDT表的基址;IDT表本質上就是中斷號和中斷處理handler的映射;cpu硬件層面會根據中斷號找到中斷處理handler的入口地址,然后跳轉到handler執行代碼;有些病毒木馬會hook鍵盤輸入的handler,借此記錄用戶輸入的所有字符來盜取賬號
  •       早期windows系統下部分殺毒軟件為了確保內核或進程安全,也會通過驅動的方式hook一些系統調用SSDT來確保能及時發現惡意程序是否在作惡,比如hook openprocess來監控有沒有惡意程序打開自己的進程注入代碼(tp保護也是這個原理);

  2、操作系統關於中斷的開發,最核心的部分就是填充IDT了,本質就是先寫好不同中斷號的handler,再把handler函數的入口地址填寫到正確的IDT表項(當然格式要符合中斷描述符的要求)!接下來看看linux 4.9版本是怎樣一步一步填充IDT和使用中斷的!      

   (1)填充IDT,也就是中斷初始化,在arch\x86\kernel\traps.c種的trap_init函數中:

void __init trap_init(void)
{
    int i;

#ifdef CONFIG_EISA
    void __iomem *p = early_ioremap(0x0FFFD9, 4);

    if (readl(p) == 'E' + ('I'<<8) + ('S'<<16) + ('A'<<24))
        EISA_bus = 1;
    early_iounmap(p, 4);
#endif

    set_intr_gate(X86_TRAP_DE, divide_error);
    set_intr_gate_ist(X86_TRAP_NMI, &nmi, NMI_STACK);
    /* int4 can be called from all */
    set_system_intr_gate(X86_TRAP_OF, &overflow);
    set_intr_gate(X86_TRAP_BR, bounds);
    set_intr_gate(X86_TRAP_UD, invalid_op);
    set_intr_gate(X86_TRAP_NM, device_not_available);
#ifdef CONFIG_X86_32
    set_task_gate(X86_TRAP_DF, GDT_ENTRY_DOUBLEFAULT_TSS);
#else
    set_intr_gate_ist(X86_TRAP_DF, &double_fault, DOUBLEFAULT_STACK);
#endif
    set_intr_gate(X86_TRAP_OLD_MF, coprocessor_segment_overrun);
    set_intr_gate(X86_TRAP_TS, invalid_TSS);
    set_intr_gate(X86_TRAP_NP, segment_not_present);
    set_intr_gate(X86_TRAP_SS, stack_segment);
    set_intr_gate(X86_TRAP_GP, general_protection);
    set_intr_gate(X86_TRAP_SPURIOUS, spurious_interrupt_bug);
    set_intr_gate(X86_TRAP_MF, coprocessor_error);
    set_intr_gate(X86_TRAP_AC, alignment_check);
#ifdef CONFIG_X86_MCE
    set_intr_gate_ist(X86_TRAP_MC, &machine_check, MCE_STACK);
#endif
    set_intr_gate(X86_TRAP_XF, simd_coprocessor_error);

    /* Reserve all the builtin and the syscall vector:
    將前32個中斷號都設置為已使用狀態
     */
    for (i = 0; i < FIRST_EXTERNAL_VECTOR; i++)
        set_bit(i, used_vectors);
    //設置0x80系統調用的系統中斷門
#ifdef CONFIG_IA32_EMULATION
    set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_compat);
    set_bit(IA32_SYSCALL_VECTOR, used_vectors);
#endif

#ifdef CONFIG_X86_32
    set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_32);
    set_bit(IA32_SYSCALL_VECTOR, used_vectors);
#endif

    /*
     * Set the IDT descriptor to a fixed read-only location, so that the
     * "sidt" instruction will not leak the location of the kernel, and
     * to defend the IDT against arbitrary memory write vulnerabilities.
     * It will be reloaded in cpu_init() */
    __set_fixmap(FIX_RO_IDT, __pa_symbol(idt_table), PAGE_KERNEL_RO);
    idt_descr.address = fix_to_virt(FIX_RO_IDT);

    /*
     * Should be a barrier for any external CPU state:
     執行CPU的初始化,對於中斷而言,在 cpu_init() 中主要是將 idt_descr 放入idtr寄存器中
     */
    cpu_init();

    /*
     * X86_TRAP_DB and X86_TRAP_BP have been set
     * in early_trap_init(). However, ITS works only after
     * cpu_init() loads TSS. See comments in early_trap_init().
     */
    set_intr_gate_ist(X86_TRAP_DB, &debug, DEBUG_STACK);
    /* int3 can be called from all */
    set_system_intr_gate_ist(X86_TRAP_BP, &int3, DEBUG_STACK);

    x86_init.irqs.trap_init();

#ifdef CONFIG_X86_64
    memcpy(&debug_idt_table, &idt_table, IDT_ENTRIES * 16);
    set_nmi_gate(X86_TRAP_DB, &debug);
    set_nmi_gate(X86_TRAP_BP, &int3);
#endif
}

  used_vectors變量是一個bitmap,它用於記錄中斷向量表中哪些中斷已經被系統注冊和使用,哪些未被注冊使用;

  (2)trap_init()已經完成了異常和陷阱的初始化。對於linux而言,中斷號0~19是專門用於陷阱和故障使用的,20~31一般是intel用於保留的;而外部IRQ線使用的中斷為32~255(代碼中32號中斷被用作匯編指令異常中斷)。所以,在trap_init()代碼中,專門對0~19號中斷的門描述符進行了初始化,最后將新的中斷向量表起始地址放入idtr寄存器中;相應的handler定義和實現在arch\x86\kernel\traps.c中,舉個大家都熟悉的int 3為例,實現如下:

/* May run on IST stack. */
dotraplinkage void notrace do_int3(struct pt_regs *regs, long error_code)
{
#ifdef CONFIG_DYNAMIC_FTRACE
    /*
     * ftrace must be first, everything else may cause a recursive crash.
     * See note by declaration of modifying_ftrace_code in ftrace.c
     */
    if (unlikely(atomic_read(&modifying_ftrace_code)) &&
        ftrace_int3_handler(regs))
        return;
#endif
    if (poke_int3_handler(regs))
        return;

    ist_enter(regs);
    RCU_LOCKDEP_WARN(!rcu_is_watching(), "entry code didn't wake RCU");
#ifdef CONFIG_KGDB_LOW_LEVEL_TRAP
    if (kgdb_ll_trap(DIE_INT3, "int3", regs, error_code, X86_TRAP_BP,
                SIGTRAP) == NOTIFY_STOP)
        goto exit;
#endif /* CONFIG_KGDB_LOW_LEVEL_TRAP */

#ifdef CONFIG_KPROBES
    if (kprobe_int3_handler(regs))
        goto exit;
#endif

    if (notify_die(DIE_INT3, "int3", regs, error_code, X86_TRAP_BP,
            SIGTRAP) == NOTIFY_STOP)
        goto exit;

    /*
     * Let others (NMI) know that the debug stack is in use
     * as we may switch to the interrupt stack.
     */
    debug_stack_usage_inc();
    preempt_disable();
    cond_local_irq_enable(regs);
    do_trap(X86_TRAP_BP, SIGTRAP, "int3", regs, error_code, NULL);//核心代碼
    cond_local_irq_disable(regs);
    preempt_enable_no_resched();
    debug_stack_usage_dec();
exit:
    ist_exit(regs);
}

  (3)部分中斷比如網卡接受到數據后,通過中斷通知cpu來讀取;如果數據量很大,cpu讀取和處理數據的時候一直關閉中斷,可能導致其他中斷被延遲甚至忽略(大家肯定都遇到過電腦“卡死”的情況:敲擊鍵盤、移動鼠標都沒反應,很有可能是cpu還在處理舊中斷,來不及響應新的中斷);為了在處理上一個中斷的同時避免耽誤下一個中斷,linux把中斷分成了上中斷和下中斷兩部分(類似windows的DPC機制)。上部分代碼優先級高,但是代碼量較少,耗時不多;下半段執行優先級低但是耗時的代碼;上半段執行時依然關閉中斷,下半段就可以開中斷了;此過程稱之為softtirq,圖示如下:  

   arch\x86\entry\entry_64.S中的調用代碼:

/* Call softirq on interrupt stack. Interrupts are off. */
ENTRY(do_softirq_own_stack)
    pushq    %rbp
    mov    %rsp, %rbp
    incl    PER_CPU_VAR(irq_count)
    cmove    PER_CPU_VAR(irq_stack_ptr), %rsp
    push    %rbp                /* frame pointer backlink */
    call    __do_softirq
    leaveq
    decl    PER_CPU_VAR(irq_count)
    ret
END(do_softirq_own_stack)

     實際使用時,linux提供了workqueue的機制和接口(create、destroy、insert等)供用戶調用;

  (4)站在上層3環業務應用的角度,各行業不同的業務都有自己的需求,可能需要自定義大量的中斷處理程序,每個處理程序對應不同的中斷號用於區分。但是管理中斷的硬件設備的引腳是有限的;加上軟件中斷號也不超過256個中斷,萬一業務需要的中斷號超過256個后該怎么辦了? 這個個hashtable實現的原理一摸一樣了,以java的hashtable為了:用戶剛開始創建hashtable時,會分配一個固定大小的數組作為索引,查詢數組上元素的時間復雜度是O(1);如果有多個key的hash結果都一樣了,就用鏈表來存放(key,value),由此解決hash沖突;其實在中斷號的管理和hashtable完全一樣,一旦中斷號重復,同樣可以通過鏈表的方式記錄不同中斷號、業務所對應的handler,圖示如下:

  

        由此依賴,理論上用戶使用的中斷號數量就沒有任何限制了(只要內存容量支持),所以也能自由地讓用戶自己注冊中斷服務了(讓用戶直接操作IDT也危險),媽媽再也不用擔心中斷號不夠用🙂!調用的接口如上圖所示:request_irq(),如下:

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id)
  • unsigned int  irq:為要注冊中斷服務函數的中斷號,比如外部中斷0就是16,定義在mach/irqs.h
  • irq_handler_t  handler:為要注冊的中斷服務函數,就是(irq_desc+ irq )->action->handler
  • unsigned long  irqflags: 觸發中斷的參數,比如邊沿觸發, 定義在linux/interrupt.h。         
  • const char  *devname:中斷程序的名字,使用cat /proc/interrupt 可以查看中斷程序名字
  • void  *dev_id:傳入中斷處理程序的參數,注冊共享中斷時不能為NULL,因為卸載時需要這個做參數,避免卸載其它中斷服務函數

     (5)有些時候異步中斷產生的速度遠超cpu處理中斷的速度,導致中斷無法被及時響應,這時外部的中斷就只能被忽略不理會了?萬一重要的中斷被漏掉了怎么辦了?還是舉用戶鍵盤輸入的例子:當電腦變得卡頓的時候,只要沒死機,用戶還是可以在鍵盤輸入的,只不過暫時在頻幕上看不到輸入;過一段時間后用戶的輸入就會出現在頻幕上了,這是怎么做到的了?

  做服務器后台上層應用開發的時候,同樣會遇到生產者和消費者速度不匹配的時候,最常見的解決辦法就是加消息隊列來緩沖了,目前市面上最流行的消息緩沖隊列非kafka莫屬了;底層中斷場景遇到這種問題同樣可以用類似的思路解決:加個消息隊列緩沖,隊列的名字就叫kfifo(猜測全稱是kernel first in first out)!為了循環利用和節約空間,kfifo采用了環形隊列!實際使用時,linux也提供了kfifo_alloc、kfifo_free、kfifo_in、kfifo_out等接口供用戶直接調用!

  總結對比:為了不漏掉異步中斷,linux在兩方面做了改進(本質上是使用了兩個隊列記錄信息):

  •   執行中斷handler時分成了上、下;兩部分;上半部代碼很少,可以關閉中斷,一般執行很緊急的業務;下半部代碼集較多、處理時間較長,可以開中斷;用戶實際使用時,可以調用work_queue相關接口把下半部任務加入隊列
  •        響應異步中斷時,為了不漏掉中斷請求,也可以增加kfifo隊列!

  3、系統調用的核心意義:

  •   為什么要用系統調用了?
    •   每個3環app都需要底層的硬件交互,最常見的諸如在屏幕輸出字符、讀寫磁盤的文件、通過網卡收發數據等;和硬件交互,肯定要調用硬件自身的驅動,但是硬件的種類非常多,如果每個app都單獨調用硬件的驅動,會導致app開發的成本高昂!此時linux VFS的作用就凸顯了: VFS統一對接種類繁多的硬件驅動,起到了類似各種“中台”的作用;上層app僅需調用linux提供的api,底層不同硬件的驅動由linux操作系統去適配(這里就是VFS啦),app不需要自己挨個調用每個硬件的驅動了,極大降低的開發的難度和成本!這里再發一次之前VFS的圖示:

      

    •        同樣的硬件只有1個,多個進程或線程都要使用硬件,比如多個進程/線程都要讀寫磁盤、都要從網卡收發數據,肯定有個先后順序,這時也需要操作系統來協調; 
  •    linux提供的VFS解決了app適配不同硬件的老問題,但是新問題也來了:為了保護VFS和硬件的驅動不被app惡意篡改,VFS和驅動都在0環內核;但是app在3環啊,EIP直接從3環去取0環的指令是會出錯的(如果cpu硬件層面不報錯,內核代碼就毫無安全性可言了,早期的DOS操作系統就是這樣的,很容易被ap篡改代碼或數據搞崩!),所以cpu硬件層面誕生了軟中斷:通過int+中斷號的形式讓EIP順利進入內核執行代碼;而且3環的app只需要執行“int+中斷號”即可,完全看不見具體的VFS或驅動代碼是怎么寫的,極大的簡化了app調用api的方法,也保護了VFS或驅動的代碼安全(EIP從3環進入0環后,只能按照硬件廠家事先寫好的驅動代碼執行,沒法干其他任何事情了),一箭雙雕!
  •       因為int要檢查特權級別,還要出棧入棧保存上下文,比較耗時,cpu在硬件層面誕生了專門的系統調用指令:syscall/sysenter,但是核心功能和int是一樣的!

  4、 系統調用在逆向/安全防護的應用:以字節跳動HIDS為例

  早期在windows下無論是殺毒軟件,還是逆向破解的程序,都喜歡hook SSDT來監控3環的app在干啥,比如hook openProcess就知道3環有沒有app在調試自己;在linux平台上原理類似,也可以通過hook 系統調用來做防護,拿字節跳動的HIDS舉例(末尾參考5、6兩個鏈接):

  (1)mprotect 函數掛鈎:函數本是用來設置物理內存頁的rwx屬性的,利用這個功能可以用來調試和反調試

  •   調試: 先把關鍵的物理頁設置為不可寫,一旦有代碼試圖寫該頁就會產生page fault異常,由此可以定位關鍵的代碼,這就是傳說中的(硬件)內存讀寫斷點,一般用來定來定位關鍵的加密字段生成代碼,也可以用來注入自己的惡意代碼
  •        反調試:把物理內存頁面設置為不可寫,調試的時候由於需要插入int 3,遇到這種不可寫的內存是會報錯;大家用ida調試時經常遇到各種signal彈窗告警有一部分就是內存屬性不可寫導致的!

    (2)open函數掛鈎:函數本來是用來打開文件、獲取文件句柄的,利用這個可以用來:

  •   檢測自己的so是否被第三方調用:loadlibrary函數底層最終會調用linux系統提供的open函數打開so,然后才能加載so到內存執行代碼

    (3)prctl函數掛鈎:函數原本是用來設置進程屬性的,利用這個可以用來:

  •   逆向調試  
    •  設置PR_SET_PTRACER屬性用來把代碼注入到目標進程,frida底層貌似用的就是ptrace注入代碼;
    •    改進程/線程的名字躲避安全防護的檢測
  •        安全防護
    •    檢測自己的進程/線程名字是否被更改;
    •    檢測自己的進程/線程是否被設置PR_SET_PTRACER或PR_SET_MM屬性

      (4)ptrace函數掛鈎:這可能是逆向最有用的系統調用了,frida底層貌似就用了這個函數;HIDS hook這個函數記錄了關鍵信息有:

  •  POKETEXT/POKEDATA
  •    進程ID
  •    內存地址
  •    拷貝的數據
  •    執行程序
  •    進程樹

         這樣就很容易檢測自己的進程是不是正在被調試了!   

        還有很多重要的系統調用如execve、init_module等都被hook了,這里不再贅述!

  5、其實linux系統有現成的strace工具可以查看進程都觸發了哪些系統調用,以打開文件為例,命令很簡單:cat  1.txt;為了看清楚cat命令做了哪些系統調用,可以用strace來查看,完整的命令是:“strace cat 1.txt”,結果如下:

└─# strace cat 1.txt
execve("/usr/bin/cat", ["cat", "1.txt"], 0x7ffd36672918 /* 51 vars */) = 0
brk(NULL)                               = 0x55cbf772d000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=94692, ...}) = 0
mmap(NULL, 94692, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fc9c4d5e000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0@n\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1839792, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc9c4d5c000
mmap(NULL, 1852680, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fc9c4b97000
mprotect(0x7fc9c4bbc000, 1662976, PROT_NONE) = 0
mmap(0x7fc9c4bbc000, 1355776, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7fc9c4bbc000
mmap(0x7fc9c4d07000, 303104, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x170000) = 0x7fc9c4d07000
mmap(0x7fc9c4d52000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ba000) = 0x7fc9c4d52000
mmap(0x7fc9c4d58000, 13576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fc9c4d58000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7fc9c4d5d580) = 0
mprotect(0x7fc9c4d52000, 12288, PROT_READ) = 0
mprotect(0x55cbf6e3e000, 4096, PROT_READ) = 0
mprotect(0x7fc9c4da0000, 4096, PROT_READ) = 0
munmap(0x7fc9c4d5e000, 94692)           = 0
brk(NULL)                               = 0x55cbf772d000
brk(0x55cbf774e000)                     = 0x55cbf774e000
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=3041456, ...}) = 0
mmap(NULL, 3041456, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fc9c48b0000
close(3)                                = 0
fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(0x88, 0), ...}) = 0
openat(AT_FDCWD, "1.txt", O_RDONLY)     = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=5, ...}) = 0
fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0
mmap(NULL, 139264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc9c488e000
read(3, "11111", 131072)                = 5
write(1, "11111", 511111)                    = 5
read(3, "", 131072)                     = 0                                                                        
munmap(0x7fc9c488e000, 139264)          = 0
close(3)                                = 0
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

   從上面的系統調用來看,一個簡單的打開文件,盡然使用了這么多的系統調用,常見的execve、mmap、open、read、write、cloes等都用上了!每次系統調用從3環進出0環時都要保存上下文,效率很低,建議少用系統調用!

 

 

 

參考:

  1、https://www.cnblogs.com/theseventhson/p/13068709.html  實模式中斷原理

  2、https://www.cnblogs.com/jiading/p/12606978.html  linux中斷和系統調用解析

       3、https://www.cnblogs.com/LittleHann/p/4111692.html?utm_source=tuicool&utm_medium=referral     Linux Systemcall Int0x80方式、Sysenter/Sysexit Difference Comparation

       4、https://bbs.pediy.com/thread-226254.htm   syscall/sysenter具體過程

       5、https://mp.weixin.qq.com/s/rm_hXHb_YBWQqmifgAqfaw    最后防線:字節跳動HIDS分析

       6、https://github.com/EBWi11/AgentSmith-HIDS    https://github.com/bytedance/Elkeid/blob/main/README-zh_CN.md  

       7、https://blog.csdn.net/hunter___/article/details/83063131  prctl()函數詳解

       8、https://www.jianshu.com/p/b1f9d6911c90  ptrace使用介紹

       9、https://www.cnblogs.com/tolimit/p/4415348.html  linux中斷源碼分析-初始化

      10、https://www.cnblogs.com/vedic/p/11069249.html  linux workqueue講解

       11、https://blog.csdn.net/MyArrow/article/details/8090504  workqueue接口函數


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM