深入 kernel panic 流程【轉】

来源:https://www.cnblogs.com/linhaostudy/archive/2018/08/06/9429511.html
-Advertisement-
Play Games

一、前言 我們在項目開發過程中,很多時候會出現由於某種原因經常會導致手機系統死機重啟的情況(重啟分Android重啟跟kernel重啟,而我們這裡只討論kernel重啟也就是 kernel panic 的情況),死機重啟基本算是影響最嚴重的系統問題了,有穩定復現的,也有概率出現的,解題難度也千差萬別 ...


一、前言

我們在項目開發過程中,很多時候會出現由於某種原因經常會導致手機系統死機重啟的情況(重啟分Android重啟跟kernel重啟,而我們這裡只討論kernel重啟也就是 kernel panic 的情況),死機重啟基本算是影響最嚴重的系統問題了,有穩定復現的,也有概率出現的,解題難度也千差萬別,出現問題後,通常我們會拿到類似這樣的kernel log信息(下麵log僅以調用BUG()為例,其它異常所致的死機log信息會有一些不同之處):

[    2.052157] <2>-(2)[1:swapper/0]------------[ cut here ]------------
[    2.052163] <2>-(2)[1:swapper/0]Kernel BUG at c04289dc [verbose debug info unavailable]
[    2.052169] <2>-(2)[1:swapper/0]Internal error: Oops - BUG: 0 [#1] PREEMPT SMP ARM
[    2.052178] <2>-(2)[1:swapper/0]disable aee kernel api[    3.052192] <2>-(2)[1:swapper/0]Non-crashing CPUs did not react to IPI
[    3.052204] <2>-(2)[1:swapper/0]CPU: 2 PID: 1 Comm: swapper/0 Tainted: G        W      3.18.35+ #3
[    3.052211] <2>-(2)[1:swapper/0]task: df060000 ti: df04a000 task.ti: df04a000
[    3.052227] <2>-(2)[1:swapper/0]PC is at ltr553_i2c_probe+0x94/0x9c
[    3.052233] <2>-(2)[1:swapper/0]LR is at 0x0
[    3.052242] <2>-(2)[1:swapper/0]pc : [<c04289dc>]    lr : [<00000000>]    psr: a0000113
[    3.052242] <2>sp : df04bd30  ip : 00000000  fp : df04bd4c
[    3.052249] <2>-(2)[1:swapper/0]r10: 00000003  r9 : de348fc0  r8 : c0428948
[    3.052255] <2>-(2)[1:swapper/0]r7 : dea1bc00  r6 : dea1bc04  r5 : dea1bc20  r4 : c0b53358
[    3.052262] <2>-(2)[1:swapper/0]r3 : c115ef4c  r2 : 00000000  r1 : 00000000  r0 : de366a00
[    4.354655] <2>-(2)[1:swapper/0] oops_end, 1, 11
[    4.354740] <2>-(2)[1:swapper/0]Kernel panic - not syncing: Fatal exception

這是linux 內核在死機之前輸出的相關重要信息,包括PC指針、調用棧等在內的非常重要的便於Debug的線索,比如我們可以藉助GUN tools(add2Line)工具結合內核符號映射表vmlinux來定位當前PC指針所在的代碼具體行數(定位到出錯代碼行並不意味著就找到了問題的根本原因跟修複異常,這個需要根據異常的複雜程度而論)。深入理解這些關鍵列印log信息的含義和機制非常有助於我們對於此類死機問題的定位和分析(對於記憶體被踩、硬體不穩定導致的一類問題分析有局限性),這也是我們需要深入學習內核異常流程的初衷。

這裡我們必須弄清楚幾個問題:

  • 這些死機前留下的關鍵register信息是怎麼來的,有什麼用,具體含義是什麼?
  • 如何利用這些遺留的線索找到出問題代碼具體在哪支文件,在哪一行?
  • 內核發生致命異常到死機的總流程是怎樣的,類似死機問題應該如何著手分析?

為此,本文就從最常見的主動觸發BUG()為例解析上面的疑問及分析整個kernel panic流程。

二、什麼是BUG() ?

有過驅動調試經驗的人肯定都知道這個東西,這裡的BUG跟我們一般認為的“軟體缺陷”可不是一回事,這裡說的BUG()其實是linux kernel中用於攔截內核程式超出預期的行為,屬於軟體主動彙報異常的一種機制。這裡有個疑問,就是什麼時候會用到呢?一般來說有兩種用到的情況,一是軟體開發過程中,若發現代碼邏輯出現致命fault後就可以調用BUG()讓kernel死掉(類似於assert),這樣方便於定位問題,從而修正代碼執行邏輯;另外一種情況就是,由於某種特殊原因(通常是為了debug而需抓ramdump),我們需要系統進入kernel panic的情況下使用.

  • 原形是什麼?

BUG()跟BUG_ON(1)其實本質是一回事,後者只是在前者的基礎上做了簡單的封裝而已,BUG()的實現 本質是埋入一條未定義指令:0xe7f001f2,觸發ARM發起Undefined Instruction異常(PS:ARM有分10種異常類型,詳細可以複習ARM異常模型章節).


<kernel-3.18/arch/arm/include/asm/bug.h>
#define BUG_INSTR_VALUE 0xe7f001f2
#define BUG_INSTR(__value) __inst_arm(__value)
#define BUG() _BUG(__FILE__, __LINE__, BUG_INSTR_VALUE)
#define _BUG(file, line, value) __BUG(file, line, value)
#define __BUG(__file, __line, __value)              \
do {                                \
    asm volatile(BUG_INSTR(__value) "\n");          \
    unreachable();                      \
} while (0)

三、BUG() 流程分析

  • BUG()到系統重啟的總流程圖

調用BUG()會向CPU下發一條未定義指令而觸發ARM發起未定義指令異常,隨後進入kernel異常處理流程歷經 Oops,die(),__die()等流程輸出用於調試分析的關鍵線索,最後進入panic()結束自己再獲得重生的過程,這個就是整個過程的基本流程,下麵先來看die()具體做了什麼呢?

四、die() 流程

源碼:

void die(const char *str, struct pt_regs *regs, int err)
{
    enum bug_trap_type bug_type = BUG_TRAP_TYPE_NONE;
    unsigned long flags = oops_begin();
    int sig = SIGSEGV;
 
    if (!user_mode(regs))
        bug_type = report_bug(regs->ARM_pc, regs);
    if (bug_type != BUG_TRAP_TYPE_NONE)
        str = "Oops - BUG";
    if (__die(str, err, regs))
        sig = 0;
    oops_end(flags, regs, sig);
}

總流程大致如下:

通常來說,代碼分析過程結合kernel log一起看會理解來得更加深刻,如果是BUG()/BUG_ON(1)導致的異常,那麼走到report_bug 就可以看到下麵標誌性 log:

enum bug_trap_type report_bug(unsigned long bugaddr, struct pt_regs *regs) 
{
...
    if (!is_valid_bugaddr(bugaddr))
        return BUG_TRAP_TYPE_NONE;
...
    printk(KERN_DEFAULT "------------[ cut here ]------------\n");
    if (file)
        pr_crit("kernel BUG at %s:%u!\n", file, line);
    else
        pr_crit("Kernel BUG at %p [verbose debug info unavailable]\n",
            (void *)bugaddr);
 
===>
[    2.052157] <2>-(2)[1:swapper/0]------------[ cut here ]------------
[    2.052163] <2>-(2)[1:swapper/0]Kernel BUG at c04289dc [verbose debug info unavailable]
[    2.052169] <2>-(2)[1:swapper/0]Internal error: Oops - BUG: 0 [#1] PREEMPT SMP ARM

所以如果在log中看到了這個 “[ cut here ]” 的信息就推斷是軟體發生致命fault而主動call了BUG()所致的系統重啟了,就可以根據相關信息嘗試定位分析修複異常了.**這裡要註意的是還有另外一種__WARN()的情況也會列印出 “[ cut here ]” 的標誌性log但是內核並不會掛掉,容易造成誤導:**

#define __WARN()    warn_slowpath_null(__FILE__, __LINE__)
#define __WARN_printf(arg...)   warn_slowpath_fmt(__FILE__, __LINE__, arg)
void warn_slowpath_fmt(const char *file, int line, const char *fmt, ...)
{
...
    warn_slowpath_common(file, line, __builtin_return_address(0),
                 TAINT_WARN, &args);
static void warn_slowpath_common(const char *file, int line, void *caller,
                 unsigned taint, struct slowpath_args *args)
{
...
    pr_warn("------------[ cut here ]------------\n");
    pr_warn("WARNING: CPU: %d PID: %d at %s:%d %pS()\n",
        raw_smp_processor_id(), current->pid, file, line, caller);
 
===>
[    1.106219] <2>-(2)[1:swapper/0]------------[ cut here ]------------
[    1.107018] <2>-(2)[1:swapper/0]WARNING: CPU: 2 PID: 1 at /home/android/work/prj/n-6580/kernel-3.18/kernel/irq/manage.c:454 __enable_irq+0x50/0x8c()

所以其實從顯現上很好區分兩種情況,如果是BUG()/BUG_ON(1)那麼內核一定會掛掉重啟,而__WARN()只會dump_stack()而不會死機, 從源碼跟log信息也可以容易區分兩種情況,如果是BUG()/BUG_ON(1)的話一定有類似下麵的log輸出,只要搜索關鍵字:“Internal error: Oops” 即可.

[    2.052163] <2>-(2)[1:swapper/0]Kernel BUG at c04289dc [verbose debug info unavailable]
[    2.052169] <2>-(2)[1:swapper/0]Internal error: Oops - BUG: 0 [#1] PREEMPT SMP ARM

五、__die() 流程分析

從上面輸出的log信息還不足以定位具體出問題的代碼位置,包括定位異常所需要的最關鍵的 PC指針、調用棧等這些對於調試來說至關重要的線索信息都是在__die()中輸出.

流程圖:

列印出標誌性log信息:

static int __die(const char *str, int err, struct pt_regs *regs)
{
...
    printk(KERN_EMERG "Internal error: %s: %x [#%d]" S_PREEMPT S_SMP
           S_ISA "\n", str, err, ++die_counter);
===>
[    2.052169] <2>-(2)[1:swapper/0]Internal error: Oops - BUG: 0 [#1] PREEMPT SMP ARM

log 顯示異常str是Oops - BUG,error-code 為0,die計數器次數:1

Oops 的本意為 “哎呀” 的一個俚語,這裡意形象的意指kernel出現了一件意外而不知道該如何處理的事件.

notify_die() 會通知對Oops感興趣的模塊執行相關回調,比如mtk的aee異常引擎模塊就是通過註冊到die_chain通知鏈上的.

int notrace notify_die(enum die_val val, const char *str,
           struct pt_regs *regs, long err, int trap, int sig)
{
    struct die_args args = {
        .regs   = regs,
        .str    = str,
        .err    = err,
        .trapnr = trap,
        .signr  = sig,
    };
    return atomic_notifier_call_chain(&die_chain, val, &args);
}

mtk的aee異常引擎在kernel初始化的時候會去註冊到die_chain通知鏈,而且我們可以看到其實還註冊了panic通知鏈.

int __init aee_ipanic_init(void)
{
    spin_lock_init(&ipanic_lock);
    mrdump_init();
    atomic_notifier_chain_register(&panic_notifier_list, &panic_blk);
    register_die_notifier(&die_blk);
    register_ipanic_ops(&ipanic_oops_ops);
    ipanic_log_temp_init();
    ipanic_msdc_init();
    LOGI("ipanic: startup, partition assgined %s\n", AEE_IPANIC_PLABEL);
    return 0;
}

而對我們調試追蹤有用的關鍵信息是在 __show_regs() 裡面列印的:

void __show_regs(struct pt_regs *regs)
{
    unsigned long flags;
    char buf[64];
 
    show_regs_print_info(KERN_DEFAULT);
    print_symbol("PC is at %s\n", instruction_pointer(regs));
    print_symbol("LR is at %s\n", regs->ARM_lr);
    printk("pc : [<%08lx>]    lr : [<%08lx>]    psr: %08lx\n"
           "sp : %08lx  ip : %08lx  fp : %08lx\n",
        regs->ARM_pc, regs->ARM_lr, regs->ARM_cpsr,
        regs->ARM_sp, regs->ARM_ip, regs->ARM_fp);
    printk("r10: %08lx  r9 : %08lx  r8 : %08lx\n",
        regs->ARM_r10, regs->ARM_r9,
        regs->ARM_r8);
    printk("r7 : %08lx  r6 : %08lx  r5 : %08lx  r4 : %08lx\n",
        regs->ARM_r7, regs->ARM_r6,
        regs->ARM_r5, regs->ARM_r4);
    printk("r3 : %08lx  r2 : %08lx  r1 : %08lx  r0 : %08lx\n",
        regs->ARM_r3, regs->ARM_r2,
        regs->ARM_r1, regs->ARM_r0);
    flags = regs->ARM_cpsr;
    buf[0] = flags & PSR_N_BIT ? 'N' : 'n';
    buf[1] = flags & PSR_Z_BIT ? 'Z' : 'z';
    buf[2] = flags & PSR_C_BIT ? 'C' : 'c';
    buf[3] = flags & PSR_V_BIT ? 'V' : 'v';
    buf[4] = '\0';
    printk("Flags: %s  IRQs o%s  FIQs o%s  Mode %s  ISA %s  Segment %s\n",
        buf, interrupts_enabled(regs) ? "n" : "ff",
        fast_interrupts_enabled(regs) ? "n" : "ff",
        processor_modes[processor_mode(regs)],
        isa_modes[isa_mode(regs)],
        get_fs() == get_ds() ? "kernel" : "user");
    show_extra_register_data(regs, 128);
}
void dump_stack_print_info(const char *log_lvl)
{
    printk("%sCPU: %d PID: %d Comm: %.20s %s %s %.*s\n",
           log_lvl, raw_smp_processor_id(), current->pid, current->comm,
           print_tainted(), init_utsname()->release,
           (int)strcspn(init_utsname()->version, " "),
           init_utsname()->version);
...

這裡列印出了重要的pc停下的位置、相關寄存器信息,發生的是user還是kernel的異常、發生異常的cpu、進程pid等信息.

===>

接下來 dump_mem() 用於dump出當前線程的記憶體信息:

dump_mem(KERN_EMERG, "Stack: ", regs->ARM_sp,
    THREAD_SIZE + (unsigned long)task_stack_page(tsk));

使用 dump_backtrace(regs, tsk) 列印出調試最直觀的調用棧信息:
===>

[    3.056363] <2>-(2)[1:swapper/0]Backtrace: -(2)[1:swapper/0]
[    3.056386] <2>-(2)[1:swapper/0][<c010badc>] (dump_backtrace) from [<c010bc7c>] (show_stack+0x18/0x1c)
[    3.056393] <2>-(2)[1:swapper/0] r6:c103d790-(2)[1:swapper/0] r5:ffffffff-(2)[1:swapper/0] r4:00000000-(2)[1:swapper/0] r3:00000000-(2)[1:swapper/0]
[    3.056426] <2>-(2)[1:swapper/0][<c010bc64>] (show_stack) from [<c0a91e64>] (dump_stack+0x90/0xa4)
[    3.056439] <2>-(2)[1:swapper/0][<c0a91dd4>] (dump_stack) from [<c072d264>] (ipan6503] <2>-(2)[1:swapper/0][<c013e6bc>] (notifier_call_chain) from [<c013eb84>] (atomic_notifier_call_chain+0x3c/0x50)
[    3.056509] <2>-(2)[1:swapper/0] r10:c10efec4-(2)[1:swapper/0] r9:df060000-(2)[1:swapper/0] r8:df04a020-(2)[1:swapper/0] r7:c0caaaf0-(2)[1:swapper/0] r6:c10f0c88-(2)[1:swapper/0] r5:00000001
[    3.056539] <2>-(2)[1:swapper/0] r4:df04bb74-(2)[1:swapper/0]
[    3.056554] <2>-(2)[1:swapper/0][<c013eb48>] (atomic_notifier_call_chain) from [<c013f244>] (notify_die+0x44/0x4c)
[    3.056560] <2>-(2)[1:swapper/0] r6:df04bce8-(2)[1:swapper/0] r5:00000000-(2)[1:swapper/0] r4:00000001-(2)[1:swapper/0]
[    3.056585] <2>-(2)[1:swapper/0][<c013f200>] (notify_die) from [<c010bd94>] (die+0x114/0x41c)
[    3.056590] <2>-(2)[1:swapper/0] r4:c102826c-(2)[1:swapper/0]
[    3.056607] <2>-(2)[1:swapper/0][<c010bc80>] (die) from [<c010c0c0>] (arm_notify_die+0x24/0x5c)
[    3.056612] <2>-(2)[1:swapper/0] r10:df04a000-(2)[1:swapper/0] r9:00000000-(2)[1:swapper/0] r8:df04bce8-(2)[1:swapper/0] r7:e7f001f2-(2)[1:swapper/0] r6:df04a000-(2)[1:swapper/0] r5:c04289dc
[    3.056642] <2>-(2)[1:swapper/0] r4:00000004-(2)[1:swapper/0]
[    3.056658] <2>-(2)[1:swapper/0][<c010c09c>] (arm_notify_die) from [<c01001cc>] (do_undefinstr+0x1a4/0x1ec)
[    3.056670] <2>-(2)[1:swapper/0][<c0100028>] (do_undefinstr) from [<c010c98c>] (__und_svc_finish+0x0/0x34)
[    3.056676] <2>-(2)[1:swapper/0]Exception stack(0xdf04bce8 to 0xdf04bd30)
[    3.056687] <2>-(2)[1:swapper/0]bce0:                   de366a00 00000000 00000000 c115ef4c c0b53358 dea1bc20
[    3.056698] <2>-(2)[1:swapper/0]bd00: dea1bc04 dea1bc00 c0428948 de348fc0 00000003 df04bd4c 00000000 df04bd30
[    3.056706] <2>-(2)[1:swapper/0]bd20: 00000000 c04289dc a0000113 ffffffff
[    3.056711] <2>-(2)[1:swapper/0] r9:c010c98c-(2)[1:swapper/0] r8:e7100000-(2)[1:swapper/0] r7:00000000-(2)[1:swapper/0] r6:c010cd98-(2)[1:swapper/0] r5:00000000-(2)[1:swapper/0] r4:c04289e0
[    3.056750] <2>-(2)[1:swapper/0][<c0428948>] (ltr553_i2c_probe) from [<c07d88d8>] (i2c_device_probe+0xd0/0x12c)
[    3.056756] <2>-(2)[1:swapper/0] r5:dea1bc20-(2)[1:swapper/0] r4:c0b53358-(2)[1:swapper/0]
[    3.056778] <2>-(2)[1:swapper/0][<c07d8808>] (i2c_device_probe) from [<c03c298c>] (driver_probe_device+0x160/0x43c)

通過上面的調用棧信息結合GUN Tools(add2Line)基本就可以定位發生異常的具體代碼位置了.

最後會通過dump_instr(KERN_EMERG, regs) 列印出pc指針和前4條指令:

static void dump_instr(const char *lvl, struct pt_regs *regs)
{
...
    for (i = -4; i < 1 + !!thumb; i++) {
        unsigned int val, bad;
        if (thumb)
            bad = __get_user(val, &((u16 *)addr)[i]);
        else
            bad = __get_user(val, &((u32 *)addr)[i]);
        if (!bad)
            p += sprintf(p, i == 0 ? "(%0*x) " : "%0*x ", width, val);
        else {
            p += sprintf(p, "bad PC value");
            break;
        }
    }
    printk("%sCode: %s\n", lvl, str);
..
===>
[    3.226706] <2>-(2)[1:swapper/0][<c0a8ae74>] (/0]Code: e89da830 e30e3f4c e34c3115 e5830000 (e7f001f2)

看到這個 e7f001f2 了吧,是不是很眼熟?這個就是BUG()中埋入的未定義指令!

到這一步,大部分關鍵信息都已經輸出了,可以通過add2Line工具定位出具體死在的代碼行號,大致看看發生了什麼,如果是BUG()導致的異常,那麼就可以考慮分析和修複異常了,因為BUG()屬於主動彙報異常,一般來說debug難度會相對其它的被動上報方式容易得多.

例如:

從上面log知PC死在的地址<c04289dc>,通過add2Line工具結合內核符號映射表 vmlinux 就可以定位出具體代碼所在文件行號:

arm-linux-androideabi-addr2line -e out/target/product/$project/obj/KERNEL_OBJ/vmlinux -f -C c04289dc
ltr553_i2c_probe
/aosp/kernel-3.18/drivers/misc/mediatek/alsps/ltr553/ltr553.c:3278

定位到了具體代碼行號就可以進一步分析代碼log找出問題原因修複異常了(一般來說BUG()導致的異常比較好解,其它的情況難度就是天差地別了..)。 那麼接下來kernel要乾什麼呢?重要信息都輸出完了接下來就直接走 kernel panic 流程了.

六、panic 流程

panic 本意是“恐慌”的意思,這裡意旨kernel發生了致命錯誤導致無法繼續運行下去的情況.

流程圖:

相關重要的debug信息已經在之前的__die()流程輸出完成了,panic()其實要乾的主要事情就是讓系統先死掉再重生,kernel panic有標誌性的log列印,可以作為是否發生panic的搜索關鍵字.

[    4.354740] <2>-(2)[1:swapper/0]Kernel panic - not syncing: Fatal exception

雖然主要的工作就是讓系統複位,但在去的路上還是會做一些事情,儘可能的不遺餘力給事後分析提供線索,比如atomic_notifier_call_chain()會去遍歷panic_notifier_list鏈表,依次通知對panic感興趣的模塊(比如mtk的aee機制在這裡會把異常的一小塊記憶體給dump處理放到expdb分區,不過後面新的機制是改在reboot後再來做了)做一些事情,如果打開了ramdump支持就直接陷入download模式,抓取ramdump供離線分析用,單從這塊來講MTK/QCOM平臺流程差不多.

[    9.772431] Rebooting in 1 seconds..

另外還要說的一點是,以上所有的分析都是基於log信息的分析,簡單易行,這是系統異常調試中最基本也是最重要的分析手段,對於BUG()導致的問題通常可以比較順利的分析解決,但是也有其局限性,比如記憶體被踩、硬體不穩定導致的概率死機等類型問題分析起來就往往很吃力,而這就需要藉助ramdump分析手段才能進一步比較順利的分析解題.

最後附上總時序圖


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Apache 1. 基本操作 | 解釋 | 命令 | | | | | 安裝 | yum install httpd | | 啟動 | service httpd start | | 停止 | service httpd stop | 2. 啟動完成後 查看進程是否存在: 此時在本機Win地址欄輸入 ...
  • 操作系統通過系統調用為運行於其上的進程提供服務。 當用戶態進程發起一個系統調用, CPU 將切換到 內核態 並開始執行一個 內核函數 。 內核函數負責響應應用程式的要求,例如操作文件、進行網路通訊或者申請記憶體資源等。 原文地址: "https://learn linux.readthedocs.io ...
  • 1 編譯安裝QEMU 首先下載qemu for mini2440,直接打包下載 http://repo.or.cz/w/qemu/mini2440.git/snapshot/HEAD.tar.gz 解壓後,進入源代碼目錄中: sudo apt-get install zlib1g-dev sudo ...
  • scp 幫助命令: man scp scp功能: 下載遠程文件或者目錄到本地, 如果想上傳或者想下載目錄,最好的辦法是採用tar壓縮一下,是最明智的選擇. 從遠程主機 下載東西到 本地電腦 拷貝文件命令 scp user@host:/path/file /localpath 即 scp 用戶名@IP ...
  • 日常運維中,我們會存在每日備份的現象,針對這一種情況可能會要求監控文件是否存在。比較笨拙的方法就是登錄上伺服器到某個路徑下查看文件是否存在,除此之外,我們可以利用shell或者python來編寫監控文件是否存在的腳本來減少工作量。當然,作為一個同樣是小白的我,編寫的腳本可以略顯簡單,希望大神們可以指 ...
  • 對於任何一個進程,在啟動時,都會打開三個流:stdin(標準輸入), stdout(標準輸出), stderr(標準錯誤輸出)。Stdout,stderr是process與Display之間,stdin是process與keyboard之間。也就是說系統的標準輸入輸出,是進程與設備間交流的橋梁。 但 ...
  • 亂碼產生的原因: 電腦中儲存的信息都是用二進位數表示的;而我們在屏幕上看到的英文、漢字等字元是二進位數轉換之後的結果。通俗的說,按照何種規則將字元存儲在電腦中,如'a'用什麼表示,稱為"編碼";反之,將存儲在電腦中的二進位數解析顯示出來,稱為"解碼",如同密碼學中的加密和解密。在解碼過程中,如 ...
  • Iptables防火牆規則使用梳理 iptables是組成Linux平臺下的包過濾防火牆,與大多數的Linux軟體一樣,這個包過濾防火牆是免費的,它可以代替昂貴的商業防火牆解決方案,完成封包過濾、封包重定向和網路地址轉換(NAT)等功能。在日常Linux運維工作中,經常會設置iptables防火牆規 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...