Linux進程啟動過程分析do_execve(可執行程式的載入和運行)---Linux進程的管理與調度(十一)

来源:https://www.cnblogs.com/linhaostudy/archive/2018/09/15/9650228.html
-Advertisement-
Play Games

execve系統調用 execve系統調用 我們前面提到了, fork, vfork等複製出來的進程是父進程的一個副本, 那麼如何我們想載入新的程式, 可以通過execve來載入和啟動新的程式。 x86架構下, 其實還實現了一個新的exec的系統調用叫做execveat(自linux 3.19後進入 ...


execve系統調用

execve系統調用

我們前面提到了, fork, vfork等複製出來的進程是父進程的一個副本, 那麼如何我們想載入新的程式, 可以通過execve來載入和啟動新的程式。

x86架構下, 其實還實現了一個新的exec的系統調用叫做execveat(自linux-3.19後進入內核)

syscalls,x86: Add execveat() system call

exec()函數族

exec函數一共有六個,其中execve為內核級系統調用,其他(execl,execle,execlp,execv,execvp)都是調用execve的庫函數。

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,
                  ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

ELF文件格式以及可執行程式的表示

ELF可執行文件格式

Linux下標準的可執行文件格式是ELF.ELF(Executable and Linking Format)是一種對象文件的格式,用於定義不同類型的對象文件(Object files)中都放了什麼東西、以及都以什麼樣的格式去放這些東西。它自最早在 System V 系統上出現後,被 UNIX 世界所廣泛接受,作為預設的二進位文件格式來使用。

但是linux也支持其他不同的可執行程式格式, 各個可執行程式的執行方式不盡相同, 因此linux內核每種被註冊的可執行程式格式都用linux_bin_fmt來存儲, 其中記錄了可執行程式的載入和執行函數

同時我們需要一種方法來保存可執行程式的信息, 比如可執行文件的路徑, 運行的參數和環境變數等信息,即linux_bin_prm結構

struct linux_bin_prm結構描述一個可執行程式

linux_binprm是定義在include/linux/binfmts.h中, 用來保存要要執行的文件相關的信息, 包括可執行程式的路徑, 參數和環境變數的信息

/*
* This structure is used to hold the arguments that are used when loading binaries.
*/
struct linux_binprm {
    char buf[BINPRM_BUF_SIZE];  // 保存可執行文件的頭128位元組
#ifdef CONFIG_MMU
    struct vm_area_struct *vma;
    unsigned long vma_pages;
#else
# define MAX_ARG_PAGES  32
    struct page *page[MAX_ARG_PAGES];
#endif
    struct mm_struct *mm;
    unsigned long p; /* current top of mem , 當前記憶體頁最高地址*/
    unsigned int
            cred_prepared:1,/* true if creds already prepared (multiple
                             * preps happen for interpreters) */
            cap_effective:1;/* true if has elevated effective capabilities,
                             * false if not; except for init which inherits
                             * its parent's caps anyway */
#ifdef __alpha__
    unsigned int taso:1;
#endif
    unsigned int recursion_depth; /* only for search_binary_handler() */
    struct file * file;         /*  要執行的文件  */
    struct cred *cred;      /* new credentials */
    int unsafe;             /* how unsafe this exec is (mask of LSM_UNSAFE_*) */
    unsigned int per_clear; /* bits to clear in current->personality */
    int argc, envc;     /*  命令行參數和環境變數數目  */
    const char * filename;  /* Name of binary as seen by procps, 要執行的文件的名稱  */
    const char * interp;    /* Name of the binary really executed. Most
                               of the time same as filename, but could be
                               different for binfmt_{misc,script} 要執行的文件的真實名稱,通常和filename相同  */
    unsigned interp_flags;
    unsigned interp_data;
    unsigned long loader, exec;
};

struct linux_binfmt可執行程式的結構

linux支持其他不同格式的可執行程式, 在這種方式下, linux能運行其他操作系統所編譯的程式, 如MS-DOS程式, 活BSD Unix的COFF可執行格式, 因此linux內核用struct linux_binfmt來描述各種可執行程式。

linux內核對所支持的每種可執行的程式類型都有個struct linux_binfmt的數據結構,定義如下

linux_binfmt定義在include/linux/binfmts.h

/*
  * This structure defines the functions that are used to load the binary formats that
  * linux accepts.
  */
struct linux_binfmt {
    struct list_head lh;
    struct module *module;
    int (*load_binary)(struct linux_binprm *);
    int (*load_shlib)(struct file *);
    int (*core_dump)(struct coredump_params *cprm);
    unsigned long min_coredump;     /* minimal dump size */
 };

其提供了3種方法來載入和執行可執行程式

  • load_binary

通過讀存放在可執行文件中的信息為當前進程建立一個新的執行環境

  • load_shlib

用於動態的把一個共用庫捆綁到一個已經在運行的進程, 這是由uselib()系統調用激活的

  • core_dump

在名為core的文件中, 存放當前進程的執行上下文. 這個文件通常是在進程接收到一個預設操作為”dump”的信號時被創建的, 其格式取決於被執行程式的可執行類型

當我們執行一個可執行程式的時候, 內核會list_for_each_entry遍歷所有註冊的linux_binfmt對象, 對其調用load_binrary方法來嘗試載入, 直到載入成功為止.

execve載入可執行程式的過程

內核中實際執行execv()或execve()系統調用的程式是do_execve(),這個函數先打開目標映像文件,並從目標文件的頭部(第一個位元組開始)讀入若幹(當前Linux內核中是128)位元組(實際上就是填充ELF文件頭,下麵的分析可以看到),然後調用另一個函數search_binary_handler(),在此函數裡面,它會搜索我們上面提到的Linux支持的可執行文件類型隊列,讓各種可執行程式的處理程式前來認領和處理。如果類型匹配,則調用load_binary函數指針所指向的處理函數來處理目標映像文件。在ELF文件格式中,處理函數是load_elf_binary函數,下麵主要就是分析load_elf_binary函數的執行過程(說明:因為內核中實際的載入需要涉及到很多東西,這裡只關註跟ELF文件的處理相關的代碼):

sys_execve() > do_execve() > do_execveat_common > search_binary_handler() > load_elf_binary()

execve的入口函數sys_execve

描述 定義 鏈接
系統調用號(體繫結構相關) 類似與如下的形式
#define __NR_execve
117__SYSCALL(117, sys_execve, 3)
arch/對應體繫結構/include/uapi/asm/unistd.h, line 265
入口函數聲明 asmlinkage long sys_execve(const char __user *filename,
const char __user *const __user *argv,
const char __user *const __user *envp);
include/linux/syscalls.h, line 843
系統調用實現 asmlinkage long sys_execve(const char __user *filename, const char __user *const __user *argv, const char __user *const __user *envp); fs/exec.v 1710

execve系統調用的的入口點是體繫結構相關的sys_execve, 該函數很快將工作委托給系統無關的do_execve函數

SYSCALL_DEFINE3(execve,
                const char __user *, filename,
                const char __user *const __user *, argv,
                const char __user *const __user *, envp)
{
    return do_execve(getname(filename), argv, envp);
}

通過參數傳遞了寄存集合和可執行文件的名稱(filename), 而且還傳遞了指向了程式的參數argv和環境變數envp的指針

參數 描述
filename 可執行程式的名稱
argv 程式的參數
envp 環境變數

指向程式參數argv和環境變數envp兩個數組的指針以及數組中所有的指針都位於虛擬地址空間的用戶空間部分。因此內核在當問用戶空間記憶體時, 需要多加小心, 而__user註釋則允許自動化工具來檢測時候所有相關事宜都處理得當

do_execve函數

do_execve的定義在fs/exec.c中,參見 http://lxr.free-electrons.com/source/fs/exec.c?v=4.5#L1628

更早期實現linux-2.4 linux-3.18引入execveat之前do_execve實現 linux-3.19~至今引入execveat之後do_execve實現 do_execveat
代碼過長, 沒有經過do_execve_common的封裝 int do_execve(struct filename *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp)
{
struct user_arg_ptr argv = { .ptr.native = __argv };
struct user_arg_ptr envp = { .ptr.native = __envp };
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}
int do_execve(struct filename *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp)
{
struct user_arg_ptr argv = { .ptr.native = __argv };
struct user_arg_ptr envp = { .ptr.native = __envp };
return do_execveat_common(AT_FDCWD, filename, argv, envp, 0);
}
int do_execveat(int fd, struct filename *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp,
int flags)
{
struct user_arg_ptr argv = { .ptr.native = __argv };
struct user_arg_ptr envp = { .ptr.native = __envp };
return do_execveat_common(fd, filename, argv, envp, flags);
}

早期的do_execve流程如下, 基本無差別, 可以作為參考

程式的載入do_execve_common和do_execveat_common

早期linux-2.4中直接由do_execve實現程式的載入和運行

linux-3.18引入execveat之前do_execve調用do_execve_common來完成程式的載入和運行

linux-3.19~至今引入execveat之後do_execve調用do_execveat_common來完成程式的載入和運行

在Linux中提供了一系列的函數,這些函數能用可執行文件所描述的新上下文代替進程的上下文。這樣的函數名以首碼exec開始。所有的exec函數都是調用了execve()系統調用。

sys_execve接受參數:1.可執行文件的路徑 2.命令行參數字元串 3.環境變數字元串

sys_execve是調用do_execve實現的。do_execve則是調用do_execveat_common實現的,依次執行以下操作:

  1. 調用unshare_files()為進程複製一份文件表
  2. 調用kzalloc()分配一份struct linux_binprm結構體
  3. 調用open_exec()查找並打開二進位文件
  4. 調用sched_exec()找到最小負載的CPU,用來執行該二進位文件
  5. 根據獲取的信息,填充struct linux_binprm結構體中的file、filename、interp成員
  6. 調用bprm_mm_init()創建進程的記憶體地址空間,為新程式初始化記憶體管理.並調用init_new_context()檢查當前進程是否使用自定義的局部描述符表;如果是,那麼分配和準備一個新的LDT
  7. 填充struct linux_binprm結構體中的argc、envc成員
  8. 調用prepare_binprm()檢查該二進位文件的可執行許可權;最後,kernel_read()讀取二進位文件的頭128位元組(這些位元組用於識別二進位文件的格式及其他信息,後續會使用到)
  9. 調用copy_strings_kernel()從內核空間獲取二進位文件的路徑名稱
  10. 調用copy_string()從用戶空間拷貝環境變數和命令行參數
  11. 至此,二進位文件已經被打開,struct linux_binprm結構體中也記錄了重要信息, 內核開始調用exec_binprm執行可執行程式
  12. 釋放linux_binprm數據結構,返回從該文件可執行格式的load_binary中獲得的代碼

定義在fs/exec.c

/*
 * sys_execve() executes a new program.
 */
static int do_execveat_common(int fd, struct filename *filename,
                          struct user_arg_ptr argv,
                          struct user_arg_ptr envp,
                          int flags)
{
    char *pathbuf = NULL;
    struct linux_binprm *bprm;  /* 這個結構當然是非常重要的,下文,列出了這個結構體以便查詢各個成員變數的意義   */
    struct file *file;
    struct files_struct *displaced;
    int retval;

    if (IS_ERR(filename))
            return PTR_ERR(filename);

    /*
     * We move the actual failure in case of RLIMIT_NPROC excess from
     * set*uid() to execve() because too many poorly written programs
     * don't check setuid() return code.  Here we additionally recheck
     * whether NPROC limit is still exceeded.
     */
    if ((current->flags & PF_NPROC_EXCEEDED) &&
        atomic_read(&current_user()->processes) > rlimit(RLIMIT_NPROC)) {
            retval = -EAGAIN;
            goto out_ret;
    }

    /* We're below the limit (still or again), so we don't want to make
     * further execve() calls fail. */
    current->flags &= ~PF_NPROC_EXCEEDED;

    //  1.  調用unshare_files()為進程複製一份文件表;
    retval = unshare_files(&displaced);
    if (retval)
            goto out_ret;

    retval = -ENOMEM;

    //  2、調用kzalloc()在堆上分配一份structlinux_binprm結構體;
    bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
    if (!bprm)
            goto out_files;

    retval = prepare_bprm_creds(bprm);
    if (retval)
            goto out_free;

    check_unsafe_exec(bprm);
    current->in_execve = 1;

    //  3、調用open_exec()查找並打開二進位文件;
    file = do_open_execat(fd, filename, flags);
    retval = PTR_ERR(file);
    if (IS_ERR(file))
            goto out_unmark;

    //  4、調用sched_exec()找到最小負載的CPU,用來執行該二進位文件;
    sched_exec();

    //  5、根據獲取的信息,填充structlinux_binprm結構體中的file、filename、interp成員;
    bprm->file = file;
    if (fd == AT_FDCWD || filename->name[0] == '/') {
            bprm->filename = filename->name;
    } else {
            if (filename->name[0] == '\0')
                    pathbuf = kasprintf(GFP_TEMPORARY, "/dev/fd/%d", fd);
            else
                    pathbuf = kasprintf(GFP_TEMPORARY, "/dev/fd/%d/%s",
                                        fd, filename->name);
            if (!pathbuf) {
                    retval = -ENOMEM;
                    goto out_unmark;
            }
            /*
             * Record that a name derived from an O_CLOEXEC fd will be
             * inaccessible after exec. Relies on having exclusive access to
             * current->files (due to unshare_files above).
             */
            if (close_on_exec(fd, rcu_dereference_raw(current->files->fdt)))
                    bprm->interp_flags |= BINPRM_FLAGS_PATH_INACCESSIBLE;
            bprm->filename = pathbuf;
    }
    bprm->interp = bprm->filename;

    //  6、調用bprm_mm_init()創建進程的記憶體地址空間,並調用init_new_context()檢查當前進程是否使用自定義的局部描述符表;如果是,那麼分配和準備一個新的LDT;
    retval = bprm_mm_init(bprm);
    if (retval)
            goto out_unmark;

    //  7、填充structlinux_binprm結構體中的命令行參數argv,環境變數envp
    bprm->argc = count(argv, MAX_ARG_STRINGS);
    if ((retval = bprm->argc) < 0)
            goto out;

    bprm->envc = count(envp, MAX_ARG_STRINGS);
    if ((retval = bprm->envc) < 0)
            goto out;

    //  8、調用prepare_binprm()檢查該二進位文件的可執行許可權;最後,kernel_read()讀取二進位文件的頭128位元組(這些位元組用於識別二進位文件的格式及其他信息,後續會使用到);
    retval = prepare_binprm(bprm);
    if (retval < 0)
            goto out;

    //  9、調用copy_strings_kernel()從內核空間獲取二進位文件的路徑名稱;
    retval = copy_strings_kernel(1, &bprm->filename, bprm);
    if (retval < 0)
            goto out;

    bprm->exec = bprm->p;

    //  10.1、調用copy_string()從用戶空間拷貝環境變數
    retval = copy_strings(bprm->envc, envp, bprm);
    if (retval < 0)
            goto out;

    //  10.2、調用copy_string()從用戶空間拷貝命令行參數;
    retval = copy_strings(bprm->argc, argv, bprm);
    if (retval < 0)
            goto out;
    /*
        至此,二進位文件已經被打開,struct linux_binprm結構體中也記錄了重要信息;

        下麵需要識別該二進位文件的格式並最終運行該文件
    */
    retval = exec_binprm(bprm);
    if (retval < 0)
            goto out;

    /* execve succeeded */
    current->fs->in_exec = 0;
    current->in_execve = 0;
    acct_update_integrals(current);
    task_numa_free(current);
    free_bprm(bprm);
    kfree(pathbuf);
    putname(filename);
    if (displaced)
            put_files_struct(displaced);
    return retval;

out:
    if (bprm->mm) {
            acct_arg_size(bprm, 0);
            mmput(bprm->mm);
    }

out_unmark:
    current->fs->in_exec = 0;
    current->in_execve = 0;

out_free:
    free_bprm(bprm);
    kfree(pathbuf);

out_files:
    if (displaced)
            reset_files_struct(displaced);
out_ret:
    putname(filename);
    return retval;
}

exec_binprm識別並載入二進程程式

每種格式的二進位文件對應一個struct linux_binprm結構體,load_binary成員負責識別該二進位文件的格式;

內核使用鏈表組織這些struct linux_binfmt結構體,鏈表頭是formats。

接著do_execveat_common()繼續往下看:

調用search_binary_handler()函數對linux_binprm的formats鏈表進行掃描,並嘗試每個load_binary函數,如果成功載入了文件的執行格式,對formats的掃描終止。

static int exec_binprm(struct linux_binprm *bprm)
{
    pid_t old_pid, old_vpid;
    int ret;

    /* Need to fetch pid before load_binary changes it */
    old_pid = current->pid;
    rcu_read_lock();
    old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent));
    rcu_read_unlock();

    ret = search_binary_handler(bprm);
    if (ret >= 0) {
            audit_bprm(bprm);
            trace_sched_process_exec(current, old_pid, bprm);
            ptrace_event(PTRACE_EVENT_EXEC, old_vpid);
            proc_exec_connector(current);
    }

    return ret;
}

search_binary_handler識別二進程程式

這裡需要說明的是,這裡的fmt變數的類型是struct linux_binfmt *, 但是這一個類型與之前在do_execveat_common()中的bprm是不一樣的,

定義在fs/exec.c

/* 
* cycle the list of binary formats handler, until one recognizes the image 
*/ 
int search_binary_handler(struct linux_binprm *bprm) 
{ 
bool need_retry = IS_ENABLED(CONFIG_MODULES); 
struct linux_binfmt *fmt; 
int retval;
/* This allows 4 levels of binfmt rewrites before failing hard. */
if (bprm->recursion_depth > 5)
        return -ELOOP;

retval = security_bprm_check(bprm);
if (retval)
        return retval;

retval = -ENOENT;

retry: 
read_lock(&binfmt_lock);
//  遍歷formats鏈表
list_for_each_entry(fmt, &formats, lh) {
        if (!try_module_get(fmt->module))
                continue;
        read_unlock(&binfmt_lock);
        bprm->recursion_depth++;

        // 遍歷formats鏈表
        retval = fmt->load_binary(bprm);
        read_lock(&binfmt_lock);
        put_binfmt(fmt);
        bprm->recursion_depth--;
        if (retval < 0 && !bprm->mm) {
                /* we got to flush_old_exec() and failed after it */
                read_unlock(&binfmt_lock);
                force_sigsegv(SIGSEGV, current);
                return retval;
        }
        if (retval != -ENOEXEC || !bprm->file) {
                read_unlock(&binfmt_lock);
                return retval;
        }
}
read_unlock(&binfmt_lock);

if (need_retry) {
        if (printable(bprm->buf[0]) && printable(bprm->buf[1]) &&
            printable(bprm->buf[2]) && printable(bprm->buf[3]))
                return retval;
        if (request_module("binfmt-%04x", *(ushort *)(bprm->buf + 2)) < 0)
                return retval;
        need_retry = false;
        goto retry;
}

return retval;
}

load_binary載入可執行程式

我們前面提到了,linux內核支持多種可執行程式格式, 每種格式都被註冊為一個linux_binfmt結構, 其中存儲了對應可執行程式格式載入函數等

格式 linux_binfmt定義 load_binary load_shlib core_dump
a.out aout_format load_aout_binary load_aout_library aout_core_dump
flat style executables flat_format load_flat_binary load_flat_shared_library flat_core_dump
script腳本 script_format load_script
misc_format misc_format load_misc_binary
em86 em86_format load_format
elf_fdpic elf_fdpic_format load_elf_fdpic_binary elf_fdpic_core_dump
elf elf_format load_elf_binary load_elf_binary elf_core_dump

參考

linux可執行文件的載入和運行(轉)

linux上應用程式的執行機制

linux 可執行文件創建 學習筆記


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

-Advertisement-
Play Games
更多相關文章
  • 環境: 環境: 環境: 環境: 本機是window7系統,安裝VMware虛擬機,在VMware安裝了Rdhat系統,想上網,在網上搜索了不少的配置方法,這篇文章介紹的比較全面,感謝分享,摘抄在這裡讓更多的愛好者學習。我自己的情況和這個是一樣的,已經配置成功了。 首先在安裝完虛擬後,要查看本機網路連 ...
  • 本文介紹如何在不使用U盤的情況下使用wubi.exe程式在Win7上安裝ubuntu-14.04.3版系統。 花了一天的時間終於安裝上了Ubuntu14.04,過程坎坷,是血淚史,開始報“cannot download the metalink and therefore the ISO”錯誤,解決 ...
  • 2018-09-15 17:36:42 1. Etcher 官網:https://etcher.io/ 資料來源:https://linuxmint-installation-guide.readthedocs.io/zh_CN/latest/burn.html 步驟:下載、安裝、運行,界面如下圖所 ...
  • Busybox簡介 • 製作文件系統我們需要使用到Busybox 工具 – 版本為busybox-1.21.1.tar.bz2 – 開源網址是http://www.busybox.net/ – BusyBox 是一個集成了一百多個最常用linux 命令和工具的軟體,包含常用的shell命令 配置Bu ...
  • sysstat Sysstat的工具集sar、 iostat、mpstat、sadf、sar、sadc * iostat 工具提供CPU使用率及硬碟吞吐效率的數據; * mpstat 工具提供單個處理器或多個處理器相關數據; * sar 工具負責收集、報告並存儲系統活躍的信息; * sa1 工具負責 ...
  • 根據本周的Linux學習進度,整理了部分Linux知識及常用命令,待完善…… 1、顯示預設啟動方式(預設啟動目標): systemctl get-default 2、設置預設啟動方式(預設啟動目標):設置預設啟動目標為命令行界面:systemctl set-default multi-user.ta ...
  • 一. 內部命令 Linux命令有內部命令(內建命令)和外部命令之分,內部命令和外部命令功能基本相同,但也有些細微差別。內部命令不需要使用子進程來執行,它們已經和shell編譯成一體,作為shell工具的組成部分存在。不需要藉助外部程式文件來運行。它們是一些比較簡單的linux系統命令,如exit,h ...
  • 上一篇先是介紹了UDP的埠掃描,又談了TCP的不完全連接埠掃描 https://www.cnblogs.com/xuyiqing/p/9389276.html 接下來我們看看TCP的全連接埠掃描: SYN掃描在網路環境非常複雜的情況下,無法正常工作,於是我們可以使用全連接掃描 即完整地建立三次 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...