【xv6操作系統】系統調用與traps機制解析及實驗設計

【xv6操作系統】系統調用與traps機制解析及實驗設計

  • 系統調用
    • 相關理論
    • 系統調用追溯
    • 系統調用實驗設計
    • Sysinfo
    • 🚩系統調用總結(結合trap機制)
  • trap
    • trap機制
    • trap代碼流程
    • Backtrace實驗
    • alarm實驗

系統調用

相關理論

??隔離性(isolation): 類似的,操作系統某種程度上為所有的應用程序服務。當你的應用程序出現問題時,你會希望操作系統不會因此而崩潰。比如說你向操作系統傳遞了一些奇怪的參數,你會希望操作系統仍然能夠很好的處理它們(能較好的處理異常情況)。所以,你也需要在應用程序和操作系統之間有強隔離性。

使用操作系統的一個原因,甚至可以說是主要原因就是為了實現multiplexing(CPU在多進程同分時復用)和內存隔離。如果你不使用操作系統,并且應用程序直接與硬件交互,就很難實現這兩點。

  • 應用程序不能直接與CPU交互,只能與進程交互
  • 操作系統內核會完成不同進程在CPU上的切換
  • 操作系統不是直接將CPU提供給應用程序,而是向應用程序提供“進程”,進程抽象了CPU,這樣操作系統才能在多個應用程序之間復用一個或者多個CPU。

硬件對于強隔離的支持

??當用戶程序執行系統調用,會通過ECALL(詳情結合下章trap)觸發一個軟中斷(software interrupt),軟中斷會查詢操作系統預先設定的中斷向量表,并執行中斷向量表中包含的中斷處理程序。中斷處理程序在內核中,這樣就完成了user mode到kernel mode的切換,并執行用戶程序想要執行的特殊權限指令。

??每一個進程都會有自己獨立的page table,這樣的話,每一個進程只能訪問出現在自己page table中的物理內存。操作系統會設置page table,使得每一個進程都有不重合的物理內存,這樣一個進程就不能訪問其他進程的物理內存,因為其他進程的物理內存都不在它的page table中。

編譯運行kernel:
??Makefile會為所有內核文件做相同的操作,比如說pipe.c,會按照同樣的套路,先經過gcc編譯成pipe.s,再通過匯編解釋器生成pipe.o。之后,系統加載器(Loader)會收集所有的.o文件,將它們鏈接在一起,并生成內核文件。

?

User/Kernel mode切換:

  • 用戶的應用程序執行系統調用的唯一方法就是通過這里的ECALL指令。

  • 調用ECALL指令,并將fork對應的數字作為參數傳給ECALL

  • 這里的數字參數代表了應用程序想要調用的System Call。

  • 可以通過系統調用或者說ECALL指令,將控制權從應用程序轉到操作系統中

進程虛擬地址空間

  • 使用符號 p->xxx 來指代 proc 結構中的元素;struct proc 在 kernel/proc.h 文件第 86 行定義。

  • 每個進程有兩個棧:一個用戶棧 user stack 和一個內核棧 kernel stack ( p->kstack )

    enum procstate { UNUSED, USED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE };
    // Per-process state
    struct proc {struct spinlock lock;			// 鎖// p->lock must be held when using these:enum procstate state;        // Process statevoid *chan;                  // If non-zero, sleeping on chanint killed;                  // If non-zero, have been killedint xstate;                  // Exit status to be returned to parent's waitint pid;                     // Process ID// wait_lock must be held when using this:struct proc *parent;         // Parent process// these are private to the process, so p->lock need not be held.uint64 kstack;               // Virtual address of kernel stackuint64 sz;                   // Size of process memory (bytes)pagetable_t pagetable;       // User page tablestruct trapframe *trapframe; // data page for trampoline.Sstruct context context;      // swtch() here to run processstruct file *ofile[NOFILE];  // Open filesstruct inode *cwd;           // Current directorychar name[16];               // Process name (debugging)
    };
    

在內核態中如何獲取用戶參數,如sleep(10),涉及一個拷貝

系統調用追溯

1. 實驗要求: 添加一個trace system call調用,可以實現跟蹤system call。此函數入參為一個數字,可以控制跟蹤哪些system call。

如:

  • trace(1<<SYS_fork),trace(10b),trace(2)表示跟蹤fork調用;
  • trace(1<<SYS_read),trace(10 0000b),trace(32),表示跟蹤read調用;

2. 一些理論基礎

?

?

  • initcode.S 將 exec 的參數放置在寄存器 a0 和 a1,系統調用編號放在 a7 中.

    # exec(init, argv)
    .globl start
    start:la a0, initla a1, argvli a7, SYS_exececall
    
  • sys_call返回值:sys_exec 返回時,系統調用會將其返回值記錄在 p->trapframe->a0 中。如果系統調用編號無效,系統調用將打印錯誤并返回 ?1

system call調用鏈路

1)在user/user.h做函數聲明
2)Makefile調用usys.pl(perl腳本)生成usys.S,里面寫了具體實現,通過ecall進入kernel,通過設置寄存器a7的值,表明調用哪個system call
3)ecall表示一種特殊的trap,轉到kernel/syscall.c:syscall執行
4)syscall.c中有個函數指針數組,即一個數組中存放了所有指向system call實現函數的指針,通過寄存器a7的值定位到某個函數指針,通過函數指針調用函數

系統調用實驗設計

核心1:內核函數調用過程

entry進入內核,調用syscall函數,通過a7獲取函數指針數組的數值(即sys_call的入口地址)

?
核心2:內核獲取用戶參數

調用 argintargaddr函數,本質是從進程中獲取

?
實驗設計:

(1)鏈路配置:

  • user.h聲明 trace函數

    int trace(int);
    
  • user.pl加入跳板函數

    entry("trace");
    
  • 在 syscalls 函數指針數組添加 sys_trace 數組,并在syscall.h中宏定義 SYS_trace

    // syscall.c
    [SYS_trace]   sys_trace,// syscall.h
    #define SYS_trace  22
    
  • 在syscall中外部函數聲明,并在sys_call在sysproc.c中定義函數原型 sys_trace

    //sys_call.c
    extern uint64 sys_trace(void);//sysproc.c
    //Add a sys_trace() function 
    uint64 sys_trace(void)
    

(2)函數實現: 根據實驗要求設計

  • 在 sys_trace 中獲取用戶系統調用參數 mask
    uint64 
    sys_trace(void)
    {int mask;if(argint(0, &mask) < 0)return -1;
    }
    
  • 通過在 proc 結構的新變量中記住其參數來實現新的系統調用,即 proc 結構體(proc.h)中添加新的變量 mask,以便子進程繼承
    //Add a sys_trace() function 
    uint64 
    sys_trace(void)
    {int mask;if(argint(0, &mask) < 0)return -1;//獲取當前進程struct proc *p = myproc();//需要在proc.h結構體中添加新的成員變量 maskp->trace_mask = mask;		return 0;
    }
    
  • 修改 fork() (kernel/proc.c) 以將跟蹤掩碼從父進程復制到子進程
    //copy the trace mask from the parent to the child process.
    np->trace_mask = p->trace_mask;
    
  • 修改 kernel/syscall.c 中的 syscall()函數,以打印跟蹤輸出

Sysinfo

  • 個人認為本題重點在于如何實現用戶態與內核態的信息交互
    (調用、參數、返回值等)

添加一個sysinfo system call調用,可以實現打印可用空間(字節)、可用進程數

首先閱讀測試案例:user/sysinfotest.c:main,需結合程序

  • testcall:測試 sysinfo調用失敗,用戶傳遞非法內存地址,因為Risc-v只支持39位 在下一章頁表中涉及
  • testmem:使用sbrk分配物理內存頁,測試剩余分配量,釋放測試
  • testproc:測試有多少進程未使用,測試fork,使用 + 1,未使用 -1

實驗設計: 構建 sysinfo 系統調用鏈路,略,參考System call tracing 中的實驗設計的第一步:鏈路配置

程序設計:
1. 獲取用戶sysinfo參數

uint64 addr;             // user pointer to struct stat
// step1: copy a struct sysinfo back to user space
if(argaddr(0, &addr) < 0)return -1;

2. 獲取未使用的空間和進程數存入 struct sysinfo 結構體

struct sysinfo info;        // sysinfo struct// step2 get freemen 
info.freemem = acquire_freemen();	//kalloc.c
// step3 get unused number of processes
info.nproc = acquire_npro();		//proc.c

3. 功能函數實現
(1) 獲取未使用的空間,參考 kalloc.c: kalloc,遍歷空頁表,計算頁數,返回 頁數 * 每一頁的字節數,這里涉及到一點下一章的頁表知識

// 參考 kalloc 
uint64 acquire_freemen(void)
{struct run *r;        //listuint64 cnt = 0;acquire(&kmem.lock);  r = kmem.freelist;//遍歷鏈表求長度 頁表while(r){r = r->next;cnt++;}release(&kmem.lock);return cnt * PGSIZE;    // 頁表頁數 * 每一頁的字節數
}

(2) 獲取未使用的進程數,遍歷進程,判斷進程狀態

uint64 acquire_npro(void)
{struct proc *p;   // 進程指針int cnt = 0;// 遍歷所有進程for(p = proc; p < &proc[NPROC]; p++) {acquire(&p->lock);          // get lock//進程狀態為使用if(p->state == UNUSED) {cnt++;} release(&p->lock);        // release lcok  }return cnt;
}

4. 內核數據拷貝給用戶,利用 copyout 函數將內核地址的數據拷貝給用戶態
函數定義:

int copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)

數據拷貝實現

struct proc *p = myproc();  // process p
copyout(p->pagetable, addr, (char *)&info, sizeof(info));

5. 完整 sys_sysinfo 函數實現:

//Add a sys_sysinfo() function 
uint64 
sys_sysinfo(void)
{struct sysinfo info;        // sysinfo structuint64 addr;                // user pointer to struct statstruct proc *p = myproc();  // process p// step2 get parainfo.freemem = acquire_freemen();// step3 get number of processesinfo.nproc = acquire_npro();// step1: copy a struct sysinfo back to user spaceif(argaddr(0, &addr) < 0)return -1;if(copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)return -1;//printf("call sys_sysinfo Hi\n");return 0;
}

🚩系統調用總結(結合trap機制)

  • 這里在學習頁表、trap后總結

?


trap

??大佬認為: 本部分主要內容其實都在lecture里(lecture 5、lecture 6),實驗不是非常復雜但是以理解概念為重,trap機制、trampoline作用、函數calling convention、調用棧、特權模式、riscv匯編,這些即使都不知道可能依然能完成 lab。但是不代表這些不重要,相反這些才是主要內容,

參考大佬的博文:[mit6.s081] 筆記 Lab4: Traps | 中斷陷阱

trap機制

參考博文: Mit6.S081學習筆記

  • 程序執行系統調用
  • 程序出現了類似page fault、運算時除以0的錯誤
  • 一個設備觸發了中斷使得當前程序運行需要響應內核設備驅動

都會發生用戶空間和內核空間的切換,這種切換通常被稱為trap

?
對比 (二)ARM寄存器組織與異常處理 中的異常處理學習,這是ARM寄存器組織異常處理過程:

  • 拷貝之前的模式及狀態
  • 切換模式 用戶模式 -> 異常模式
    • 禁止相應的中斷
    • 修改模式位
    • 修改狀態位
  • 保存返回地址
  • 設置 PC 為相應的中斷異常向量表的值

?
先認識一些內核模式下的寄存器,留個印象:

  • scause: trap類型(sys_call, page fault)
  • sststus: 保存狀態模式(User / supervisor mode)
  • scratch: trapframe地址
  • sepc: 保存當前PC的值
  • stvec: trap handler地址
  • csr: 控制狀態寄存器,csrr,csrw,csrrw指令

用戶下的內存塊:

  • trampoline:存放切換代碼的地方 (內核下也有)
  • trapframe:類似于上下文保存的結構 (內核下沒有)

??需要清楚如何讓程序的運行,從只擁有user權限并且位于用戶空間的程序,切換到擁有supervisor權限的內核。在這個過程中,硬件的狀態將會非常重要,因為很多的工作都是將硬件從適合運行用戶應用程序的狀態,改變到適合運行內核代碼的狀態。

用戶應用程序可以使用全部的32個寄存器,很多寄存器都有特殊的作用。其中:

  • 程序計數寄存器PC(Program Counter Register)
  • 表明當前mode的標志位,這個標志位表明了當前是 supervisor mode 還是 user mode
  • 還有一堆控制CPU工作方式的寄存器,比如 SATP(Supervisor Address Translation andProtection)寄存器,包含了指向page table的物理內存地址
  • STVEC(Supervisor Trap Vector Base Address Register)寄存器,指向了內核中處理trap的指令的起始地址
  • SEPC(Supervisor Exception Program Counter)寄存器,在trap的過程中保存程序計數器的值
  • SSCRATCH(Supervisor Scratch Register)寄存器,這也是個非常重要的寄存器

這些寄存器表明了執行系統調用時計算機的狀態。


trap代碼流程

用戶程序執行系統調用函數(實際上通過執行ECALL指令來執行系統調用)

??用戶程序→ ECALL→uservec(在trampoline中)→usertrap(在trap.c中)→ syscall → sys xxx(對應的系統調用)一執行結果返回給syscall→usertrapret(在trap.c中)→userret(在trampoline中)→系統調用完成,返回到用戶空間,恢復ECALL之后的用戶程序的執行

?

一、當一個trap來臨((ecall指令)RISC-V硬件做了什么

  1. 如果是設備中斷,并且狀態SIE位(sststus中的標志位)被清空,不執行以下操作
  2. 清除SIE以禁用中斷(disable interrupts by clearing SlE)(ECALL禁用,防止切換到其他進程,并重新trap,覆蓋sepc
  3. 保存pc 的值到sepc(epc = PC)
  4. 保存當前所處模式到SPP位(sststus中的標志位) (user mode -> supervisor mode
  5. 設置scause表明 trap類型(Set scause to reflect the trap’s cause)
  6. 切換到supervisor模式(Set the mode to supervisor)
  7. stvec寄存器里面的值復制到pc(Copy the stvec to the pc)
  8. pc上取值執行(Start executing at the new pc)

所以現在,ecall在硬件上幫我們做了一點點工作,但是實際上我們離執行內核中的C代碼還差的很遠。接下來:

  • 我們需要保存32個用戶寄存器的內容,這樣當我們想要恢復用戶代碼執行時,我們才能恢復這些寄存器的內容。(保存現場)
  • 因為現在我們還在user page table,我們需要切換到kernel page table
  • 我們需要創建或者找到一個kernel stack,并將Stack Pointer寄存器的內容指向那個kernel stack。這樣才能給C代碼提供棧
  • 我們還需要跳轉到內核中C代碼的某些合理的位置。

ecall并不會為我們做這里的任何一件事。

GDB過程:

?

二、uservec函數

  • 保存現場(32個通用寄存器)
  • 把內核的page table,內核的stack、當前執行該進程的CPU號裝載到寄存器里
  • 跳轉到usertrap繼續執行
?

三、usertrap函數

  • 分情況,執行系統調用/中斷/異常的處理邏輯

    • scause == 8 -> syscall -> num = p->trapframe->a7 -> syscalls表驅動 -> sys_write(16)
  • 修改了stvec的值,還可能會修改sepc的值

    void usertrap(void)
    {int which_dev = 0;if((r_sstatus() & SSTATUS_SPP) != 0)panic("usertrap: not from user mode");// send interrupts and exceptions to kerneltrap(),// since we're now in the kernel.w_stvec((uint64)kernelvec);// save user program counter.p->trapframe->epc = r_sepc();if(r_scause() == 8) {// system call// sepc points to the ecall instruction,// but we want to return to the next instruction.p->trapframe->epc += 4;// an interrupt will change sstatus &c registers,// so don't enable until done with those registers.intr_on();syscall();} // give up the CPU if this is a timer interrupt.if(which_dev == 2) {yield();}usertrapret();
    }
    

四、usertrapret函數

  • 填入了trapframe的內容,這樣下一次從用戶空間轉換到內核空間時可以用到這些數據
    • 存儲 kernel page table 的指針
    • 存儲當前用戶進程的 kernel stack
    • 存儲 usertrap 函數的指針,這樣trampoline代碼才能跳轉到這個函數
    • 從tp寄存器中讀取當前的CPU核編號,并存儲在trapframe中
    • 恢復stvec、sepc的值(supervisor mode register)

五、userret函數

  • 恢復現場

  • 把用戶空間的 page table、用戶空間的 stack 裝載到寄存器里

  • 執行sret指令(相對于ecall指令

六、sret指令

  • 程序會切換回user mode
  • SEPC寄存器的數值會被拷貝到PC寄存器(程序計數器)
  • 重新打開中斷

Backtrace實驗

添加 backtrace 功能,打印出調用棧,用于調試

實驗步驟:

  • 在 defs.h 中添加聲明 backtrace,并在 print 中定義

?

  • 在 riscv.h 中添加獲取當前 fp(frame pointer)寄存器的方法:

    static inline uint64
    r_fp()
    {uint64 x;asm volatile("mv %0, s0" : "=r" (x) );return x;
    }
    
  • 獲取當前棧幀指針

    void backtrace(void)
    {//并在 Backtrace 中調用r_fp以讀取當前幀指針//此函數使用內聯匯編來讀取 s0。uint64 fp = r_fp();       // 當前棧幀指針
    
  • lecture notes have a picture of the layout of stack frames. The return address lives at a fixed offset (-8) from the frame pointer of a stackframe, and that the saved frame pointer lives at fixed offset (-16) from the frame pointer.

  • Xv6 allocates one page for each stack in the xv6 kernel at PAGE-aligned address. You can compute the top and bottom address of the stack page by using PGROUNDDOWN(fp) and PGROUNDUP(fp)

  • 程序設計

    void backtrace(void)
    {//并在 Backtrace 中調用r_fp以讀取當前幀指針//此函數使用內聯匯編來讀取 s0。uint64 fp = r_fp();       // 當前棧幀指針uint64 return_address;    // 函數返回地址 printf("backtrace:\n");// 判斷是否已經到達棧底while (fp != PGROUNDUP(fp)){// 獲取每個棧的返回地址return_address = *(uint64*)(fp - 8);// 更新 fp 獲取上個棧的棧幀地址fp = *(uint64*)(fp - 16);printf("%p:\n",return_address);}
    }
    

函數調用棧(Stack)

  • 棧由高地址往低地址增長
  • 在xv6里,有一頁大小(4KB)
  • 棧指針(stack pointer)保存在 sp 寄存器里

棧幀(Stack Frame)

  • 當前棧幀的地址保存在 s0/fp 寄存器里

  • 當前棧幀的地址也叫棧幀的指針(frame pointer, fp),指向該棧幀的最高處

  • 棧幀指針往下偏移8個字節是函數返回地址 return address

  • 往下偏移16個字節是上一個棧幀的棧幀指針 previous frame pointer

?
  • fp 指向當前棧幀的開始地址,sp 指向當前棧幀的結束地址。
  • 棧從高地址往低地址生長,所以 fp 雖然是幀開始地址,但是地址比 sp 高
  • 棧幀中從高到低第一個 8 字節 fp-8 是 return address,也就是當前調用層應該返回到的地址。
  • 棧幀中從高到低第二個 8 字節 fp-16 是 previous address,指向上一層棧幀的 fp 開始地址。
  • 剩下的為保存的寄存器、局部變量等。一個棧幀的大小不固定,但是至少 16 字節。
  • 在 xv6 中,使用一個頁來存儲棧,如果 fp 已經到達棧頁的上界,則說明已經到達棧底

查看 call.asm,可以看到,一個函數的函數體最開始首先會擴充一個棧幀給該層調用使用,在函數執行完畢后再回收,例子:

int g(int x) {0:	1141                  addi  sp,sp,-16  // 擴張調用棧,得到一個 16 字節的棧幀2:	e422                  sd    s0,8(sp)   // 將返回地址存到棧幀的第一個 8 字節中4:	0800                  addi  s0,sp,16return x+3;
}6:	250d                  addiw a0,a0,38:	6422                  ld    s0,8(sp)   // 從棧幀讀出返回地址a:	0141                  addi  sp,sp,16   // 回收棧幀c:	8082                  ret              // 返回

注意棧的生長方向是從高地址到低地址,所以擴張是 -16,而回收是 +16。

alarm實驗

??該實驗需要實現 sigalarm和 sigreturn 兩個系統調用,為用戶進程添加定期通知功能,使得進程在一段時間內使用 CPU 后,會被定期“提醒”,類似于一種用戶態的中斷處理,用來模擬用戶級的異常處理。

作用:

  • 對于希望限制其占用CPU時間的計算密集型進程,或者希望進行計算但也希望采取一些定期行動的進程可能很有用。
  • 更一般來說,你將實現一種原始的用戶級中斷/故障處理程序;例如,你可以使用類似的機制來處理應用程序中的頁面錯誤

實驗要求與實現:

0. 核心參數設置:

struct proc{...// alarm test0int ticks;                    // 報警間隔 interval for the alarmuint64 handler;               // call functionint ticks_count;              // how many ticks right now//test1 test2 struct trapframe *save_trap_frame;  // 保存現場int is_handling;                    // 是否正在中斷 防止二次打斷...
}

1. 添加新的系統調用:

  1. user.h 聲明系統調用
  2. usys.pl添加入口
  3. syscall.h函數編號
  4. syscall.c更加系統調用,做函數表映射

2. 保存 sigalarm 的報警間隔與 handler 指針保存在 struct proc 中新的字段,并在proc.c中allocproc初始化字段

  • 核心代碼:

    sys_sigalarm:
    獲取用戶參數
    argint(0, &ticks);
    argaddr(1, &handler);
    // 保存參數
    p->ticks = ticks;
    p->handler = handler;
    p->ticks_count = 0;sys_sigreturn:
    p->is_handling = 0;      	清空中斷標記位  
    memmove(p->trapframe, p->save_trap_frame, PGSIZE);	中斷返回 保存現場的地址 
    
  • 完整代碼

  • 系統調用函數實現:
    (1)sys_sigalarm 獲取用戶數據(間隔時間與處理函數handler)并保存到進程中

    uint64 
    sys_sigalarm(void)
    {int ticks;uint64 handler;// 進入中斷// 獲取進程struct proc *p = myproc();// 獲取用戶參數argint(0, &ticks);argaddr(1, &handler);// 保存參數p->ticks = ticks;p->handler = handler;p->ticks_count = 0;return 0;
    }

    (2)sys_sigreturn 中斷返回保存現場,清空中斷標志位

    uint64 sys_sigreturn(void){  //恢復現場// 獲取進程struct proc *p = myproc();// 清空異常標志p->is_handling = 0;        // 中斷返回 保存現場的地址memmove(p->trapframe, p->save_trap_frame, PGSIZE);        return 0;}
    
  • proc/allocproc函數初始化成員變量

      // init p->ticks = 0;p->ticks_count = 0;p->handler = 0;
    

3. 在kernel/trap.c中的實現該時鐘中斷的代碼usertrap

  • 核心代碼:

    保存中斷現場 
    memmove(p->save_trap_frame, p->trapframe, PGSIZE);
    handler存入epc寄存器,破壞了現場,因此這兩行代碼不可先后更換
    p->trapframe->epc = p->handler;   
    
  • 完整代碼:

      // give up the CPU if this is a timer interrupt.if(which_dev == 2){if(p->ticks > 0){p->ticks_count++;//時間到 并且無其他中斷if(p->ticks_count > p->ticks && p->is_handling == 0){p->ticks_count = 0;//保存中斷現場 memmove(p->save_trap_frame, p->trapframe, PGSIZE);//執行函數的地址入口p->trapframe->epc = p->handler;   // handler存入epc寄存器p->is_handling = 1;               // 標記正在中斷}}yield();}
    

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/73314.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/73314.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/73314.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Docker文件夾上傳秘籍Windows下的高效傳輸之道

哈嘍,大家好,我是木頭左! 一、理解Docker容器與Windows文件系統的差異 在深入探討如何從 Windows 系統將文件夾及遞歸文件夾和文件上傳到 Docker 容器之前,有必要先明晰 Docker 容器與 Windows 文件系統之間存在的本質差異。 (一)Docker 容器的文件系統特性 Docker 容…

08 | 實現版本號打印功能

提示&#xff1a; 所有體系課見專欄&#xff1a;Go 項目開發極速入門實戰課&#xff1b;歡迎加入 云原生 AI 實戰 星球&#xff0c;12 高質量體系課、20 高質量實戰項目助你在 AI 時代建立技術競爭力&#xff08;聚焦于 Go、云原生、AI Infra&#xff09;&#xff1b;本節課最終…

在微信小程序或前端開發中,picker 和 select 都是用戶交互中用于選擇的組件,但它們在功能、設計和使用場景上有一定的區別

在微信小程序或前端開發中&#xff0c;picker 和 select 都是用戶交互中用于選擇的組件&#xff0c;但它們在功能、設計和使用場景上有一定的區別。 1. picker 的特點 描述&#xff1a; picker 是微信小程序中的原生組件&#xff0c;通常用于選擇單項或多項值&#xff0c;如時…

PMP 證書的含金量怎么樣?

pmp含金量&#xff0c;這是一個很有爭議的話題&#xff0c;我根據我以往的面試跟工作經歷對 PMP 也有幾點看法&#xff0c;想跟大家聊一聊。 一、如果真心想做項目管理&#xff0c;PMP 一定要去考一個 現在的早已不是憑經驗做項目的時代了&#xff0c;各大企業都追求專業式的…

Springboot連接neo4j

?一、Spring Data Neo4j 核心知識體系 ?1. 核心概念 ?圖數據庫特性&#xff1a; 數據以 ?節點&#xff08;Node&#xff09;? 和 ?關系&#xff08;Relationship&#xff09;? 形式存儲&#xff0c;支持屬性&#xff08;Property&#xff09;。查詢語言&#xff1a;Cyp…

我與DeepSeek讀《大型網站技術架構》- 大型網站架構技術一覽與Web開發技術發展歷程

文章目錄 大型網站架構技術一覽1. 前端架構2. 應用層架構3. 服務層架構4. 存儲層架構5. 后臺架構6. 數據采集與監控7. 安全架構8. 數據中心機房架構 Web開發技術發展歷程一、靜態HTML階段二、CGI腳本模式階段三、服務器頁面模式階段 大型網站架構技術一覽 1. 前端架構 瀏覽器…

Python數據類型進階——詳解

—— 小 峰 編 程 目錄 1.整型 1.1 定義 1.2 獨有功能 1.3 公共功能 1.4 轉換 1.5 其他 1.5.1 長整型 1.5.2 地板除(除法&#xff09; 2. 布爾類型 2.1 定義 2.2 獨有功能 2.3 公共功能 2.4 轉換 2.5 其他 做條件自動轉換 3.字符串類型 3.1 定義 3.2 獨有功能…

GNU Binutils 全工具指南:從編譯到逆向的完整生態

1. GNU Binutils 全工具指南&#xff1a;從編譯到逆向的完整生態 1. GNU Binutils 全工具指南&#xff1a;從編譯到逆向的完整生態 1.1. 引言1.2. 工具分類速查表1.3. 核心工具詳解 1.3.1. 編譯與匯編工具 1.3.1.1. as&#xff08;匯編器&#xff09;1.3.1.2. gcc&#xff08;…

docker python:latest鏡像 允許ssh遠程

跳轉到家目錄 cd創建pythonsshdockerfile mkdir pythonsshdockerfile跳轉pythonsshdockerfile cd pythonsshdockerfile創建Dockerfile文件 vim Dockerfile將Dockerfile的指令復制到文件中 # 使用 python:latest 作為基礎鏡像 # 如果我的鏡像列表中沒有python:latest鏡像&…

c++的基礎排序算法

一、快速排序 1. 選擇基準值&#xff08;Pivot&#xff09; 作用 &#xff1a;從數組中選擇一個元素作為基準&#xff08;Pivot&#xff09;&#xff0c;用于劃分數組。常見選擇方式 &#xff1a; 固定選擇最后一個元素&#xff08;如示例代碼&#xff09;。隨機選擇&#xf…

焊接機器人與線激光視覺系統搭配的詳細教程

以下是關于焊接機器人與線激光視覺系統搭配的詳細教程&#xff0c;包含核心程序框架、調參方法及源碼實現思路。本文綜合了多個技術文檔與專利內容&#xff0c;結合工業應用場景進行系統化總結。 一、系統硬件配置與視覺系統搭建 1. 硬件組成 焊接機器人系統通常由以下模塊構…

jmeter分布式原理及實例

一、執行原理 二、相關注意事項 關閉防火墻所有上網控制機、代理機、服務器都在同一個網絡上所有機器的jmeter和java版本必須一致關閉RMI.SSL開關 三、配置和執行 配置&#xff1a; 修改bin/jmeter.properties文件&#xff1a; 代理機&#xff1a; 修改服務端口&#xff1…

LinuX---Shell腳本創建和執行

概述&#xff1a; 它是一個命令行解釋器&#xff0c;接收應用程序/用戶命令&#xff0c;然后調用操作系統內核。 Shell還是一個功能強大的編程語言&#xff0c;易編寫、易調試、靈活性強。 Linux提供的Shell解析器有 atguiguubuntu:~$ cat /etc/shells # /etc/shells: valid …

FPGA中級項目1——IP核(ROM 與 RAM)

FPGA中級項目1——IP核&#xff08;ROM 與 RAM&#xff09; IP核簡介 在 FPGA&#xff08;現場可編程門陣列&#xff09;設計中&#xff0c;IP 核&#xff08;Intellectual Property Core&#xff0c;知識產權核&#xff09;是預先設計好的、可重用的電路模塊&#xff0c;用于實…

PCL 點云OBB包圍盒(二)

文章目錄 一、簡介二、實現步驟二、實現代碼三、實現效果參考資料一、簡介 包圍盒是一種求解離散點集最優包圍空間的算法,基本思想是用體積稍大且特性簡單的幾何體(稱為包圍盒)來近似地代替復雜的幾何對象。(來源于百度)常用的求解包圍盒的算法主要有AABB和OOB算法,但AAB…

第九節:哈希表(初階)

1. 哈希表的核心概念 哈希表&#xff08;Hash Table&#xff09;是一種通過哈希函數將鍵&#xff08;Key&#xff09;映射到存儲桶&#xff08;Bucket&#xff09;的數據結構&#xff0c;核心目標是實現快速查找、插入和刪除操作。其核心特點如下&#xff1a; ?哈希函數&…

【Visio使用教程】

Visio使用教程 1. Visio 的基本介紹1.1 Visio 是什么&#xff1f;核心特點&#xff1a; 1.2 主要功能與應用場景典型用途&#xff1a;行業應用&#xff1a; 1.3 版本與兼容性1.4 Visio下載1.5 安裝 2. Visio 的界面與基礎操作2.1 界面布局詳解2.2 創建新文檔與模板選擇2.3 形狀…

緩存使用的具體場景有哪些?緩存的一致性問題如何解決?緩存使用常見問題有哪些?

緩存使用場景、一致性及常見問題解析 一、緩存的核心使用場景 1. 高頻讀、低頻寫場景 典型場景&#xff1a;商品詳情頁、新聞資訊、用戶基本信息。特點&#xff1a;數據更新頻率低&#xff0c;但訪問量極高。策略&#xff1a; Cache-Aside&#xff08;旁路緩存&#xff09;&a…

谷歌 Gemini 2.0 Flash實測:1條指令自動出圖+配故事!

今天看到很多人夸Gemini 2.0 Flash的能力很強。 強大的P圖能力&#xff0c;改背景、換衣服、調整姿態、表情控制等等 其中最讓人眼前一亮的是圖文功能。 它不僅是理解圖文&#xff0c;而是能根據文字描述創作出一整個的故事、步驟圖文。 我上手試了一下&#xff0c;感覺效果…

雷電模擬器連接Android Studio步驟

打開雷電模擬器&#xff0c;點擊桌面系統應用—>打開設置—>關于平板電腦→連續點擊5次版本號&#xff0c;會出現開發者選項—->進入開發者選項—->勾選打開usb調試。 命令行提示符&#xff0c;進入雷電模擬器安裝目錄。然后執行 Plain Text adb.exe connect 127.0…