主頁 > 作業系統 > [自制作業系統] 第19回 實作用戶行程(下)

[自制作業系統] 第19回 實作用戶行程(下)

2022-09-05 06:26:46 作業系統

目錄
一、前景回顧
二、行程的創建與初始化
三、如何進行行程的切換
四、運行測驗
五、原書勘誤

 

一、前景回顧

  在上一回我們大概講述了任務切換的發展,并且知道Linux采用的是一個CPU使用一個TSS的方式,在最后我們成功實作了tss,現在萬事俱備,我們正式來實作用戶行程,

二、行程的創建與初始化

  行程的創建與執行緒的創建很相似,這里直接上圖來對比分析:

  
  我們使用process_execute函式來創建初始化行程,

 1 /*創建用戶行程*/
 2 void process_execute(void *filename, char *name)
 3 {
 4     /*pcb內核的資料結構,由內核來維護行程資訊,因此要在內核記憶體池中申請*/
 5     struct task_struct *thread = get_kernel_pages(1);
 6     init_thread(thread, name, 31);    
 7     thread_create(thread, start_process, filename);
 8     create_user_vaddr_bitmap(thread);    //創建虛擬地址的位圖
 9     thread->pgdir = create_page_dir();   //用戶行程的頁目錄表的物理地址,這里傳進來的是頁目錄表物理地址所對應的虛擬地址
10 
11     enum intr_status old_status = intr_disable();
12     ASSERT(!elem_find(&thread_ready_list, &thread->general_tag));
13     list_append(&thread_ready_list, &thread->general_tag);
14 
15     ASSERT(!elem_find(&thread_all_list, &thread->all_list_tag));
16     list_append(&thread_all_list, &thread->all_list_tag);
17     intr_set_status(old_status);
18 }

  在該函式中首先使用get_kernel_pages函式在內核物理空間中申請一頁物理記憶體來作為行程的PCB,因為最終調度是由內核來操控的,所以PCB統一都在內核物理空間中申請,隨后依舊呼叫init_thread()thread_create()函式來初始化行程的PCB,

  下面開始不一樣了,create_user_vaddr_bitmap()函式的作用是給行程創建初始化位圖,這里科普一下:我們都知道行程有4GB的虛擬空間,其中第1~3GB是分配給用戶空間,第4GB是分配給內核空間,這是Linux下的分配習慣,我們照搬,而用戶空間實際上只用上了0x08048000到0xc0000000這一部分,所以create_user_vaddr_bitmap()函式也就是將這一部分空間劃分到用戶的虛擬地址記憶體池中,

  再來看create_page_dir()函式,我們知道作業系統被所有用戶行程所共享,所以我們將用戶行程頁目錄表中的第768~1023個頁目錄項用內核頁目錄表的第768~1023個頁目錄項代替,其實就是將內核所在的頁目錄項復制到行程頁目錄表中同等位置,這樣就能讓用戶行程的高1GB空間指向內核,最后再將行程添加到全部佇列和就緒佇列中供調度,至此,用戶行程就算創建初始化完畢了,

  我們現在來看看行程的PCB的內容:

   

三、如何進行行程的切換

  因為我們之前一直都是處于內核態下,也就是0特權級下,現在要切換到用戶行程也就是用戶態,3特權級下運行,和之前的切換不太一樣,還是舉例來說明吧,

  假設當前內核執行緒A時間片用光了,在調度函式schedule()中會從就緒佇列中彈出下一個行程B的PCB,根據PCB我們就知道了行程B的所有資訊,不過接下來和之前執行緒的切換不一樣了,首先呼叫process_activate()函式激活下一個內核執行緒或者行程的頁表,對于內核執行緒來說,內核執行緒的頁目錄表在之前激活分頁機制的時候就已經設定好了,被存放在0x10000地址處,如果不是內核執行緒,那么就需要將行程B的頁目錄表地址賦給CR3暫存器,因為CPU尋址是基于CR3暫存器中保存的頁目錄表的地址來尋址的,切換到行程B后,需要將行程B的頁目錄表地址賦給了CR3暫存器,

 1 /*激活執行緒或行程的頁表,更新tss中的esp0為行程的特權級0的堆疊*/
 2 void process_activate(struct task_struct *p_thread)
 3 {
 4     ASSERT(p_thread != NULL);
 5     //激活該執行緒或者行程的頁表
 6     page_dir_activate(p_thread);
 7     
 8     if (p_thread->pgdir) {  //如果是行程那么需要在tss中填入0級特權堆疊的esp0
 9         update_tss_esp(p_thread);
10     }
11 }
process_activate

  除此之外,還要將tss中的esp0欄位更新為行程B的0級堆疊,前面已經說過,行程在由例如中斷等操作從3特權級進入0特權級后,也就是進入內核態,使用的會是0特權級下的堆疊,不再是3特權級的堆疊,因此在這個地方我們需要給行程B更新0特權級堆疊,方便以后行程B進入內核態,這里我們可以看到,行程B的0特權級的堆疊頂指標指向行程B的PCB最高處

1 /*更新tss中的esp0欄位的值為pthread的0級堆疊*/
2 void update_tss_esp(struct task_struct *pthread)
3 {
4     tss.esp0 = (uint32_t *)((uint32_t)pthread + PG_SIZE);
5 }
update_tss_esp

  這一系列操作完成后,我們又回到switch_to函式,和前面講執行緒切換也是一樣,首先通過一系列的push操作,將當前內核執行緒A的暫存器資訊壓入堆疊中以便下次又被調度上CPU后可以恢復環境,隨后從行程B的PCB中得到新的堆疊,此時行程B的堆疊的情況如下:
         

 1 switch_to:
 2     push esi            ;這里是根據ABI原則保護四個暫存器 放到堆疊里面
 3     push edi
 4     push ebx
 5     push ebp
 6     
 7     mov eax, [esp+20]    ;esp+20的位置是cur cur的pcb賦值給eax
 8     mov [eax], esp       ;[eax]為pcb的內核堆疊指標變數 把當前環境的esp值記錄下來
 9     
10     mov eax, [esp+24]
11     mov esp, [eax]       
12 
13     pop ebp
14     pop ebx
15     pop edi
16     pop esi
17     ret                 

  行程B的還是通過一系列POP操作,最終呼叫*eip所指向的函式kernel_thread,在該函式中又呼叫*function所指向的函式start_process(),該函式代碼如下:

 1 void start_process(void *filename)
 2 {
 3     void *function = filename;
 4     struct task_struct *cur = running_thread();
 5     cur->self_kstack += sizeof(struct thread_stack);
 6     struct intr_stack *proc_stack = (struct intr_stack *)cur->self_kstack;
 7     proc_stack->edi = proc_stack->esi = proc_stack->ebp = proc_stack->esp_dummy = 0;
 8     proc_stack->ebx = proc_stack->edx = proc_stack->ecx = proc_stack->eax = 0;
 9     proc_stack->gs = 0;
10     proc_stack->ds = proc_stack->es = proc_stack->fs = SELECTOR_U_DATA;  //資料段選擇子
11     proc_stack->eip = function; //函式地址 ip
12     proc_stack->cs = SELECTOR_U_CODE; //cs ip cs選擇子
13     proc_stack->eflags = (EFLAGS_IOPL_0 | EFLAGS_MBS | EFLAGS_IF_1); //不能夠關閉中斷 ELFAG_IF_1 不然會導致無法調度
14     proc_stack->esp = (void *)((uint32_t)get_a_page(PF_USER, USER_STACK3_VADDR) + PG_SIZE); //堆疊空間在0xc0000000以下一頁的地方 當然物理記憶體是作業系統來分配
15     proc_stack->ss = SELECTOR_U_DATA; //資料段選擇子
16     asm volatile ("movl %0, %%esp; jmp intr_exit" : : "g" (proc_stack) : "memory");
17 }

  來細品一下這個函式的內容,還記得前面的那個行程的PCB圖嗎?

   

  首先通過running_thread函式獲取到當前行程的PCB的地址,根據圖中我們可以知道self_kstack一開始是被賦值指向堆疊頂,也就是執行緒堆疊的開始位置,經過cur->self_kstack += sizeof(struct thread_stack)后,現在self_kstack指向中斷堆疊處了,如圖所示,然后定義一個pro_stack指標指向self_kstack,這個先記住,待會兒會用上,

  隨后便是對一系列暫存器的初始化,重點關注ds、es、fs、cs、ss和gs這幾個段暫存器的初始化,我們將它們初始化為用戶行程下的3特權級的段選擇子,因為在用戶態下,我們是不能訪問0特權級下的代碼段和資料段的,對于gs暫存器,這里其實不管是否設定為0都無所謂,因為用戶態下的程式是不能直接訪問顯存的,行程在從內核態進入用戶態時會進行特權檢查,如果gs段暫存器中的段選擇子的特權等級高于行程回傳后的特權等級,CPU就會自動將段暫存器gs給置0,如果用戶行程一旦訪問顯存,就會報錯,

  再往下就給esp賦值,這個地方是為了當回到用戶態空間后,給用戶程式指定一個堆疊頂指標,這里我們將用戶態的堆疊頂指標設定為用戶態空間下的0xc0000000處,

  最后通過行內匯編:

  asm volatile ("movl %0, %%esp; jmp intr_exit" : : "g" (proc_stack) : "memory");

  將proc_stack所指向的值賦給當前行程的esp,也就是堆疊頂指標,前面我們知道proc_stack已經被賦好了值,為self_kstack,最后便是跳轉到intr_exit處執行代碼,

  此時堆疊的情況如下:  
                                
  然后intr_exit的代碼如下所示:

1 intr_exit:
2     add esp, 4
3     popad
4     pop gs
5     pop fs
6     pop es
7     pop ds
8     add esp, 4
9     iretd

  看著代碼就很好理解了,首先add esp, 4跳過堆疊中的vec_no,隨后popad和pop操作彈出8個32位的通用暫存器和4個段暫存器,又是通過add esp, 4跳過堆疊中的err_code,最后執行iretd指令,將(*eip)、cs、eflags彈出,而我們事先已經將用戶行程要運行的函式地址存放在eip中,最后,由于我們跳轉后的用戶態,它的特權級不同于當前內核態的特權級,所以需要恢復舊堆疊,CPU自動將堆疊中的esp和ss彈出,這些值在我們前面的start_process()函式中已經初始化完畢,至此我們就已經完成了內核態到用戶態的轉換,

四、運行測驗

  這里我貼上本章所有相關代碼:

  1 #include "process.h"
  2 #include "thread.h"
  3 #include "global.h"
  4 #include "memory.h"
  5 #include "debug.h"
  6 #include "console.h"
  7 #include "interrupt.h"
  8 #include "tss.h"
  9 
 10 extern void intr_exit(void);
 11 extern struct list thread_ready_list;           //就緒佇列
 12 extern struct list thread_all_list;  
 13 
 14 void start_process(void *filename)
 15 {
 16     void *function = filename;
 17     struct task_struct *cur = running_thread();
 18     cur->self_kstack += sizeof(struct thread_stack);
 19     struct intr_stack *proc_stack = (struct intr_stack *)cur->self_kstack;
 20     proc_stack->edi = proc_stack->esi = proc_stack->ebp = proc_stack->esp_dummy = 0;
 21     proc_stack->ebx = proc_stack->edx = proc_stack->ecx = proc_stack->eax = 0;
 22     proc_stack->gs = 0;
 23     proc_stack->ds = proc_stack->es = proc_stack->fs = SELECTOR_U_DATA;            //資料段選擇子
 24     proc_stack->eip = function;                                //函式地址 ip
 25     proc_stack->cs = SELECTOR_U_CODE;                                //cs ip cs選擇子
 26     proc_stack->eflags = (EFLAGS_IOPL_0 | EFLAGS_MBS | EFLAGS_IF_1);                //不能夠關閉中斷 ELFAG_IF_1 不然會導致無法調度
 27     proc_stack->esp = (void *)((uint32_t)get_a_page(PF_USER, USER_STACK3_VADDR) + PG_SIZE);    //堆疊空間在0xc0000000以下一頁的地方 當然物理記憶體是作業系統來分配
 28     proc_stack->ss = SELECTOR_U_DATA;                                //資料段選擇子
 29     asm volatile ("movl %0, %%esp; jmp intr_exit" : : "g" (proc_stack) : "memory");
 30 }
 31 
 32 
 33 /*激活頁表*/
 34 void page_dir_activate(struct task_struct *p_thread)
 35 {
 36     //內核執行緒的頁目錄表的物理地址為0x100000
 37     uint32_t pagedir_phy_addr = 0x100000;
 38     if (p_thread->pgdir != NULL) { //說明下一個呼叫的是行程,否則是內核執行緒
 39         pagedir_phy_addr = addr_v2p((uint32_t)p_thread->pgdir);
 40     }
 41 
 42     /*更新頁目錄暫存器CR3,使新頁表生效*/
 43     asm volatile("movl %0, %%cr3" : : "r" (pagedir_phy_addr) : "memory");
 44 }
 45 
 46 /*激活執行緒或行程的頁表,更新tss中的esp0為行程的特權級0的堆疊*/
 47 void process_activate(struct task_struct *p_thread)
 48 {
 49     ASSERT(p_thread != NULL);
 50     //激活該執行緒或者行程的頁表
 51     page_dir_activate(p_thread);
 52     
 53     if (p_thread->pgdir) {  //如果是行程那么需要在tss中填入0級特權堆疊的esp0
 54         update_tss_esp(p_thread);
 55     }
 56 }
 57 
 58 uint32_t *create_page_dir(void)
 59 {
 60     //用戶行程的頁表不能讓用戶直接訪問到,所以在內核空間申請
 61     uint32_t *page_dir_vaddr = get_kernel_pages(1);                //得到記憶體
 62     if (page_dir_vaddr == NULL) {
 63         console_put_str("create_page_dir: get_kernel_page failed!\n");
 64         return NULL;
 65     }
 66     
 67     memcpy((uint32_t*)((uint32_t)page_dir_vaddr + 0x300 * 4), (uint32_t*)(0xfffff000 + 0x300 * 4), 1024); // 256項
 68     uint32_t new_page_dir_phy_addr = addr_v2p((uint32_t)page_dir_vaddr);                    
 69     page_dir_vaddr[1023] = new_page_dir_phy_addr | PG_US_U | PG_RW_W | PG_P_1;                    //最后一項是頁目錄項自己的地址
 70     
 71     return page_dir_vaddr;                                         
 72 }
 73 
 74 
 75 /*創建用戶行程虛擬地址位圖*/
 76 void create_user_vaddr_bitmap(struct task_struct *user_prog)
 77 {
 78     user_prog->userprog_vaddr.vaddr_start = USER_VADDR_START;
 79     
 80     //計算需要多少物理記憶體頁來記錄位圖 USER_VADDR_START為0x08048000
 81     uint32_t bitmap_pg_cnt = DIV_ROUND_UP((0xc0000000 - USER_VADDR_START) / PG_SIZE / 8, PG_SIZE); 
 82     user_prog->userprog_vaddr.vaddr_bitmap.bits = get_kernel_pages(bitmap_pg_cnt);
 83 
 84     user_prog->userprog_vaddr.vaddr_bitmap.btmp_bytes_len = (0xc0000000 - USER_VADDR_START) / PG_SIZE / 8;
 85     bitmap_init(&user_prog->userprog_vaddr.vaddr_bitmap);
 86 }
 87 
 88 /*創建用戶行程*/
 89 void process_execute(void *filename, char *name)
 90 {
 91     /*pcb內核的資料結構,由內核來維護行程資訊,因此要在內核記憶體池中申請*/
 92     struct task_struct *thread = get_kernel_pages(1);
 93     init_thread(thread, name, 31);    
 94     thread_create(thread, start_process, filename);
 95     create_user_vaddr_bitmap(thread);    //創建虛擬地址的位圖
 96     thread->pgdir = create_page_dir();   //用戶行程的頁目錄表的物理地址,這里傳進來的是頁目錄表物理地址所對應的虛擬地址
 97 
 98     enum intr_status old_status = intr_disable();
 99     ASSERT(!elem_find(&thread_ready_list, &thread->general_tag));
100     list_append(&thread_ready_list, &thread->general_tag);
101 
102     ASSERT(!elem_find(&thread_all_list, &thread->all_list_tag));
103     list_append(&thread_all_list, &thread->all_list_tag);
104     intr_set_status(old_status);
105 }
process.c
 1 #ifndef  __USERPROG_PROCESS_H
 2 #define  __USERPROG_PROCESS_H
 3 #include "stdint.h"
 4 #include "thread.h"
 5 
 6 #define USER_STACK3_VADDR (0xc0000000 - 0x1000)
 7 #define USER_VADDR_START 0x08048000
 8 
 9 
10 void process_execute(void *filename, char *name);
11 void create_user_vaddr_bitmap(struct task_struct *user_prog);
12 uint32_t *create_page_dir(void);
13 void process_activate(struct task_struct *p_thread);
14 void page_dir_activate(struct task_struct *p_thread);
15 void start_process(void *filename);
16 
17 #endif
process.h
  1 #include "memory.h"
  2 #include "print.h"
  3 #include "stdio.h"
  4 #include "debug.h"
  5 #include "string.h"
  6 #include "thread.h"
  7 #include "sync.h"
  8 
  9 #define PG_SIZE 4096     //頁大小
 10 
 11 /*0xc0000000是內核從虛擬地址3G起,
 12 * 0x100000意指低端記憶體1MB,為了使虛擬地址在邏輯上連續
 13 * 后面申請的虛擬地址都從0xc0100000開始
 14 */
 15 #define K_HEAP_START 0xc0100000 
 16 
 17 #define PDE_IDX(addr) ((addr & 0xffc00000) >> 22)
 18 #define PTE_IDX(addr) ((addr & 0x003ff000) >> 12)
 19 
 20 struct pool {
 21     struct bitmap pool_bitmap;     //本記憶體池用到的位圖結構
 22     uint32_t phy_addr_start;       //本記憶體池管理的物理記憶體的起始地址 
 23     uint32_t pool_size;            //記憶體池的容量
 24     struct lock lock;
 25 };
 26 
 27 struct pool kernel_pool, user_pool;  //生成內核記憶體池和用戶記憶體池
 28 struct virtual_addr kernel_vaddr;    //此結構用來給內核分配虛擬地址
 29 
 30 
 31 /*初始化記憶體池*/
 32 static void mem_pool_init(uint32_t all_mem) 
 33 {
 34     put_str("mem_pool_init start\n");
 35     /*目前頁表和頁目錄表的占用記憶體
 36     * 1頁頁目錄表 + 第0和第768個頁目錄項指向同一個頁表 + 第769~1022個頁目錄項共指向254個頁表 = 256個頁表
 37     */
 38     lock_init(&kernel_pool.lock);
 39     lock_init(&user_pool.lock);
 40 
 41     uint32_t page_table_size = PG_SIZE * 256;
 42     uint32_t used_mem = page_table_size + 0x100000;  //目前總共用掉的記憶體空間
 43     uint32_t free_mem = all_mem - used_mem;          //剩余記憶體為32MB-used_mem
 44     uint16_t all_free_pages = free_mem / PG_SIZE;    //將剩余記憶體劃分為頁,余數舍去,方便計算
 45     
 46     /*內核空間和用戶空間各自分配一半的記憶體頁*/
 47     uint16_t kernel_free_pages = all_free_pages / 2; 
 48     uint16_t user_free_pages = all_free_pages - kernel_free_pages; 
 49 
 50     /*為簡化位圖操作,余數不用做處理,壞處是這樣會丟記憶體,不過只要記憶體沒用到極限就不會出現問題*/
 51     uint32_t kbm_length = kernel_free_pages / 8; //位圖的長度單位是位元組
 52     uint32_t ubm_length = user_free_pages / 8;
 53 
 54     uint32_t kp_start = used_mem;                                 //內核記憶體池的起始物理地址
 55     uint32_t up_start = kp_start + kernel_free_pages * PG_SIZE;   //用戶記憶體池的起始物理地址
 56 
 57     /*初始化內核用戶池和用戶記憶體池*/
 58     kernel_pool.phy_addr_start = kp_start;
 59     user_pool.phy_addr_start = up_start;
 60 
 61     kernel_pool.pool_size = kernel_free_pages * PG_SIZE; 
 62     user_pool.pool_size = user_free_pages * PG_SIZE;
 63 
 64     kernel_pool.pool_bitmap.btmp_bytes_len = kbm_length;
 65     user_pool.pool_bitmap.btmp_bytes_len = ubm_length;
 66 
 67     /***********內核記憶體池和用戶記憶體池位圖************
 68     *內核的堆疊底是0xc009f00,減去4KB的PCB大小,便是0xc009e00
 69     *這里再分配4KB的空間用來存盤位圖,那么位圖的起始地址便是
 70     *0xc009a00,4KB的空間可以管理4*1024*8*4KB=512MB的物理記憶體
 71     *這對于我們的系統來說已經綽綽有余了,
 72     */
 73     /*內核記憶體池位圖地址*/
 74     kernel_pool.pool_bitmap.bits = (void *)MEM_BIT_BASE;  //MEM_BIT_BASE(0xc009a00)
 75     /*用戶記憶體池位圖地址緊跟其后*/
 76     user_pool.pool_bitmap.bits = (void *)(MEM_BIT_BASE + kbm_length);
 77 
 78     /*輸出記憶體池資訊*/
 79     put_str("kernel_pool_bitmap_start:");
 80     put_int((int)kernel_pool.pool_bitmap.bits);
 81     put_str("\n");
 82     put_str("kernel_pool.phy_addr_start:");
 83     put_int(kernel_pool.phy_addr_start);
 84     put_str("\n");
 85 
 86     put_str("user_pool_bitmap_start:");
 87     put_int((int)user_pool.pool_bitmap.bits);
 88     put_str("\n");
 89     put_str("user_pool.phy_addr_start:");
 90     put_int(user_pool.phy_addr_start);
 91     put_str("\n");
 92 
 93     /*將位圖置0*/
 94     bitmap_init(&kernel_pool.pool_bitmap);
 95     bitmap_init(&user_pool.pool_bitmap);
 96 
 97     /*初始化內核虛擬地址的位圖,按照實際物理記憶體大小生成陣列*/
 98     kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kbm_length;
 99     /*內核虛擬地址記憶體池位圖地址在用戶記憶體池位圖地址其后*/
100     kernel_vaddr.vaddr_bitmap.bits = (void *)(MEM_BIT_BASE + kbm_length + ubm_length);
101     /*內核虛擬地址記憶體池的地址以K_HEAP_START為起始地址*/
102     kernel_vaddr.vaddr_start = K_HEAP_START;
103     bitmap_init(&kernel_vaddr.vaddr_bitmap);
104 
105     put_str("mem_pool_init done\n");
106 }
107 
108 /*記憶體管理部分初始化入口*/
109 void mem_init(void)
110 {
111     put_str("mem_init start\n");
112     uint32_t mem_bytes_total = 33554432; //32MB記憶體 32*1024*1024=33554432
113     mem_pool_init(mem_bytes_total);
114     put_str("mem_init done\n");
115 }
116 
117 
118 /*在pf表示的虛擬記憶體池中申請pg_cnt個虛擬頁
119 * 成功則回傳虛擬地址的起始地址,失敗回傳NULL
120 */
121 static void *vaddr_get(enum pool_flags pf, uint32_t pg_cnt)
122 {
123     int vaddr_start = 0;
124     int bit_idx_start = -1;
125     uint32_t cnt = 0;
126     if (pf == PF_KERNEL) {
127         bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap, pg_cnt);
128         if (bit_idx_start == -1) {
129             return NULL;
130         }
131         /*在位圖中將申請到的虛擬記憶體頁所對應的位給置1*/
132         while (cnt < pg_cnt) {
133             bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 1);
134         }
135         vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start * PG_SIZE;
136             
137     } else {   //用戶記憶體池
138         struct task_struct *cur = running_thread();
139         bit_idx_start = bitmap_scan(&cur->userprog_vaddr.vaddr_bitmap, pg_cnt);
140         if (bit_idx_start == -1) {
141             return NULL;
142         }
143         while (cnt < pg_cnt) {
144             bitmap_set(&cur->userprog_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 1);
145         }
146         vaddr_start = cur->userprog_vaddr.vaddr_start + bit_idx_start * PG_SIZE;
147         /*0xc00000000 - PG_SIZE作為用戶3級堆疊已經在start_process被分配*/
148         ASSERT((uint32_t)vaddr_start < (0xc0000000 - PG_SIZE));
149     }
150     return (void *)vaddr_start;
151 }
152 
153 /*得到虛擬地址vaddr所對應的pte指標
154 * 這個指標也是一個虛擬地址,CPU通過這個虛擬地址去尋址會得到一個真實的物理地址
155 * 這個物理地址便是存放虛擬地址vaddr對應的普通物理頁的地址
156 * 假設我們已經知道虛擬地址vaddr對應的普通物理頁地址為0xa
157 * 那么便可以通過如下操作完成虛擬地址和普通物理頁地址的映射
158 * *pte = 0xa
159 */
160 uint32_t *pte_ptr(uint32_t vaddr) 
161 {
162     uint32_t *pte = (uint32_t *)(0xffc00000 + \
163             ((vaddr & 0xffc00000) >> 10) + \
164             PTE_IDX(vaddr) * 4);
165     return pte;
166 }
167 
168 /*得到虛擬地址vaddr所對應的pde指標
169 * 這個指標也是一個虛擬地址,CPU通過這個虛擬地址去尋址會得到一個真實的物理地址
170 * 這個物理地址便是存放虛擬地址vaddr對應的頁表的地址,使用方法同pte_ptr()一樣
171 */
172 uint32_t *pde_ptr(uint32_t vaddr) 
173 {
174     uint32_t *pde = (uint32_t *)(0xfffff000 + PDE_IDX(vaddr) * 4);
175     return pde;
176 }
177 
178 /*在m_pool指向的物理記憶體地址中分配一個物理頁
179 * 成功則回傳頁框的物理地址,失敗回傳NULL
180 */
181 static void *palloc(struct pool *m_pool)
182 {
183     int bit_idx = bitmap_scan(&m_pool->pool_bitmap, 1);
184     if (bit_idx == -1) {
185         return NULL;
186     }
187     /*在位圖中將申請到的物理記憶體頁所對應的位給置1*/
188     bitmap_set(&m_pool->pool_bitmap, bit_idx, 1);
189     /*得到申請的物理頁所在地址*/
190     uint32_t page_phyaddr = (m_pool->phy_addr_start + bit_idx * PG_SIZE);
191    
192     return (void *)page_phyaddr;
193 }
194 
195 /*在頁表中添加虛擬地址_vaddr與物理地址_page_phyaddr的映射*/
196 static void page_table_add(void *_vaddr, void *_page_phyaddr)
197 {
198     uint32_t vaddr = (uint32_t)_vaddr;
199     uint32_t page_phyaddr = (uint32_t)_page_phyaddr;
200     uint32_t *pde = pde_ptr(vaddr);
201     uint32_t *pte = pte_ptr(vaddr);
202     
203     //先判斷虛擬地址對應的pde是否存在
204     if (*pde & 0x00000001) {
205         ASSERT(!(*pte & 0x00000001));
206         *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
207     } else { //頁目錄項不存在,需要先創建頁目錄再創建頁表項
208         uint32_t pde_phyaddr = (uint32_t)palloc(&kernel_pool);
209         *pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
210         /* 將分配到的物理頁地址pde_phyaddr對應的物理記憶體清0
211         *  避免里面的陳舊資料變成頁表項
212         */
213         /* 這個地方不能這樣memset((void *)pde_phyaddr, 0, PG_SIZE);
214         * 因為現在我們所使用的所有地址都是虛擬地址,雖然我們知道pde_phyaddr是真實的物理地址
215         * 可是CPU是不知道的,CPU會把pde_phyaddr當作虛擬地址來使用,這樣就肯定無法清0了
216         * 所以解決問題的思路就是:如何得到pde_phyaddr所對應的虛擬地址,
217         */
218         //為什么不是memset((void *)((int)pde & 0xffc00000), 0, PG_SIZE);
219         //建議好好看看pde_ptr()和pte_ptr()函式的實作
220         memset((void *)((int)pte & 0xfffff000), 0, PG_SIZE);
221         ASSERT(!(*pte & 0x00000001));
222         *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
223     }
224 }
225 
226 /*分配pg_cnt個頁空間,成功則回傳起始虛擬地址,失敗回傳NULL*/
227 void *malloc_page(enum pool_flags pf, uint32_t pg_cnt)
228 {
229     ASSERT((pg_cnt > 0) && (pg_cnt < 3840));
230     void *vaddr_start = vaddr_get(pf, pg_cnt);
231     if (vaddr_start == NULL) {
232         return NULL;
233     }
234 
235     uint32_t vaddr = (uint32_t)vaddr_start;
236     uint32_t cnt = pg_cnt;
237 
238     struct pool *mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;
239 
240     /*因為虛擬地址連續,而物理地址不一定連續,所以逐個做映射*/
241     while (cnt-- > 0) {
242         void *page_phyaddr = palloc(mem_pool);
243         if (page_phyaddr == NULL) {
244             return NULL;
245         }
246         page_table_add((void *)vaddr, page_phyaddr);
247         vaddr += PG_SIZE;
248     }
249     return vaddr_start;
250 }
251 
252 /*從內核物理記憶體池中申請pg_cnt頁記憶體,成功回傳其虛擬地址,失敗回傳NULL*/
253 void *get_kernel_pages(uint32_t pg_cnt)
254 {
255     void *vaddr = malloc_page(PF_KERNEL, pg_cnt);
256     if (vaddr != NULL) {
257         memset(vaddr, 0, pg_cnt * PG_SIZE);
258     }
259     return vaddr;
260 }
261 
262 
263 /*在用戶空間中申請4K記憶體,并回傳其虛擬地址*/
264 void *get_user_pages(uint32_t pg_cnt)
265 {
266     lock_acquire(&user_pool.lock);
267     void *vaddr = malloc_page(PF_USER, pg_cnt);
268     memset(vaddr, 0, pg_cnt * PG_SIZE);
269     lock_release(&user_pool.lock);
270     return vaddr;
271 }
272 
273 /*將地址vaddr與pf池中的物理地址關聯起來,僅支持一頁記憶體空間分配*/
274 void *get_a_page(enum pool_flags pf, uint32_t vaddr)
275 {
276     struct pool *mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;
277     lock_acquire(&mem_pool->lock);
278 
279     struct task_struct* cur = running_thread();
280     int32_t bit_idx = -1;
281     
282     //虛擬地址位圖置1
283     if (cur->pgdir != NULL && pf == PF_USER) {
284         bit_idx = (vaddr - cur->userprog_vaddr.vaddr_start) / PG_SIZE;
285         ASSERT(bit_idx > 0);
286         bitmap_set(&cur->userprog_vaddr.vaddr_bitmap, bit_idx, 1);
287     } else if(cur->pgdir == NULL && pf == PF_KERNEL) {
288         bit_idx = (vaddr - kernel_vaddr.vaddr_start) / PG_SIZE;
289         ASSERT(bit_idx > 0);
290         bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx, 1);
291     } else {
292         PANIC("get_a_page:not allow kernel alloc userspace or user alloc kernelspace by get_a_page");
293     }
294     
295     void* page_phyaddr = palloc(mem_pool);
296     if (page_phyaddr == NULL)
297         return NULL;
298     page_table_add((void *)vaddr, page_phyaddr);
299     lock_release(&mem_pool->lock);
300     return (void *)vaddr;
301 }
302 
303 /*得到虛擬地址映射的物理地址*/
304 uint32_t addr_v2p(uint32_t vaddr)
305 {
306     uint32_t *pte = pte_ptr(vaddr);
307     return ((*pte & 0xfffff000) + (vaddr & 0x00000fff));
308 }
memory.c
 1 #ifndef  __KERNEL_MEMORY_H
 2 #define  __KERNEL_MEMORY_H
 3 #include "stdint.h"
 4 #include "bitmap.h"
 5 
 6 #define MEM_BIT_BASE 0xc009a000
 7 
 8 /*虛擬地址池,用于虛擬地址管理*/
 9 struct virtual_addr {
10     struct bitmap vaddr_bitmap;      //虛擬地址用到的位圖結構
11     uint32_t vaddr_start;            //虛擬地址起始地址
12 };
13 
14 /*記憶體池標記,用于判斷用哪個記憶體池*/
15 enum pool_flags {
16     PF_KERNEL = 1,
17     PF_USER = 2
18 };
19 
20 #define  PG_P_1    1   //頁表項或頁目錄項存在屬性位,存在
21 #define  PG_P_0    0   //頁表項或頁目錄項存在屬性位,不存在
22 #define  PG_RW_R   0   //R/W屬性位值,不可讀/不可寫
23 #define  PG_RW_W   2   //R/W屬性位值,可讀/可寫
24 #define  PG_US_S   0   //U/S屬性位值,系統級
25 #define  PG_US_U   4   //U/S屬性位值,用戶級
26 
27 void mem_init(void);
28 void *get_kernel_pages(uint32_t pg_cnt);
29 void *get_a_page(enum pool_flags pf, uint32_t vaddr);
30 void *get_user_pages(uint32_t pg_cnt);
31 uint32_t addr_v2p(uint32_t vaddr);
32 void *get_a_page(enum pool_flags pf, uint32_t vaddr);
33 
34 #endif
memory.h
  1 #include "thread.h"
  2 #include "string.h"
  3 #include "memory.h"
  4 #include "list.h"
  5 #include "interrupt.h"
  6 #include "debug.h"
  7 #include "print.h"
  8 #include "stddef.h"
  9 #include "process.h"
 10 
 11 struct task_struct *main_thread;         //主執行緒PCB
 12 struct list thread_ready_list;           //就緒佇列
 13 struct list thread_all_list;             //所有人物佇列
 14 static struct list_elem *thread_tag;     //用于保存佇列中的執行緒節點
 15 extern void switch_to(struct task_struct* cur, struct task_struct* next);
 16 
 17 
 18 /*獲取當前執行緒PCB指標*/
 19 struct task_struct *running_thread(void)
 20 {
 21     uint32_t esp;
 22     asm volatile ("mov %%esp, %0" : "=g" (esp));
 23 
 24     /*取esp整數部分,即PCB起始地址*/
 25     return (struct task_struct *)(esp & 0xfffff000);
 26 }
 27 
 28 /*由kernel_thread去執行function(func_arg)*/
 29 static void kernel_thread(thread_func *function, void *func_arg)
 30 {
 31     /*執行function前要開中斷,避免后面的時鐘中斷被屏蔽,而無法調度其他執行緒*/
 32     intr_enable();
 33     function(func_arg);
 34 }
 35 
 36 /*初始化執行緒PCB*/
 37 void init_thread(struct task_struct *pthread, char *name, int prio)
 38 {
 39     memset(pthread, 0, sizeof(*pthread));
 40     strcpy(pthread->name, name);
 41 
 42     /*由于main函式也封裝成了一個執行緒,并且他是一直在運行的,所以將其直接設定為TASK_RUNNING*/
 43     if (pthread == main_thread) {
 44         pthread->status = TASK_RUNNING;
 45     } else {
 46         pthread->status = TASK_READY;
 47     }
 48     //pthread->status = TASK_RUNNING;
 49     pthread->priority = prio;
 50     pthread->ticks = prio;
 51     pthread->elapsed_ticks = 0;
 52     pthread->pgdir = NULL;
 53     pthread->self_kstack = (uint32_t *)((uint32_t)pthread + PG_SIZE);
 54     pthread->stack_magic = 0x19870916;
 55 }
 56 
 57 void thread_create(struct task_struct *pthread, thread_func function, void *func_arg)
 58 {
 59     pthread->self_kstack -= sizeof(struct intr_stack);
 60     pthread->self_kstack -= sizeof(struct thread_stack);
 61 
 62     //初始化執行緒堆疊
 63     struct thread_stack *kthread_stack = (struct thread_stack *)pthread->self_kstack;
 64     kthread_stack->eip = kernel_thread;
 65     kthread_stack->function = function;
 66     kthread_stack->func_arg = func_arg;
 67     kthread_stack->ebp = kthread_stack->ebx = kthread_stack->edi = kthread_stack->esi = 0;
 68 }
 69 
 70 /*創建一個優先級為prio的執行緒,執行緒名字為name,執行緒所執行的函式為function(func_arg)*/
 71 struct task_struct *thread_start(char *name, int prio, thread_func function, void *func_arg)
 72 {
 73     /*創建執行緒的pcb,大小為4kb*/
 74     struct task_struct *thread = get_kernel_pages(1);
 75     init_thread(thread, name, prio);
 76     thread_create(thread, function, func_arg);
 77 
 78     /*確保之前不在佇列中*/
 79     ASSERT(!elem_find(&thread_ready_list, &thread->general_tag));
 80 
 81     /*加入就緒執行緒佇列*/
 82     list_append(&thread_ready_list, &thread->general_tag);
 83 
 84     /*確保之前不在佇列*/
 85     ASSERT(!elem_find(&thread_all_list, &thread->all_list_tag));
 86     
 87     /*加入全部執行緒佇列*/
 88     list_append(&thread_all_list, &thread->all_list_tag);
 89 
 90     return thread;
 91 }
 92 
 93 static void make_main_thread(void)
 94 {
 95     main_thread = running_thread();
 96     init_thread(main_thread, "main", 31);
 97 
 98     /*main函式是當前執行緒,當前執行緒不在thread_ready_list,所以只能將其加在thread_all_list*/
 99     ASSERT(!elem_find(&thread_all_list, &main_thread->all_list_tag));
100     list_append(&thread_all_list, &main_thread->all_list_tag);
101 }
102 
103 /*實作任務調度*/
104 void schedule(void)
105 {
106     ASSERT(intr_get_status() == INTR_OFF);
107     struct task_struct *cur = running_thread();
108     if (cur->status == TASK_RUNNING) {
109         ASSERT(!elem_find(&thread_ready_list, &cur->general_tag));
110         list_append(&thread_ready_list, &cur->general_tag);
111         cur->ticks = cur->priority;
112         cur->status = TASK_READY;
113     } else {
114         /*阻塞等其他情況*/
115     }
116 
117     ASSERT(!list_empty(&thread_ready_list));
118     thread_tag = NULL;
119     thread_tag = list_pop(&thread_ready_list);
120     
121     struct task_struct *next = elem2entry(struct task_struct, general_tag, thread_tag);
122     next->status = TASK_RUNNING;
123 
124     process_activate(next);
125     switch_to(cur, next);
126 }
127 
128 /*初始化執行緒環境*/
129 void thread_init(void)
130 {
131     put_str("thread_init start\n");
132     list_init(&thread_ready_list);
133     list_init(&thread_all_list);
134     /*將當前main函式創建為執行緒*/
135     make_main_thread();
136     put_str("thread_init done\n");
137 }
138 
139 /*當前執行緒將自己阻塞,標志其狀態為stat*/
140 void thread_block(enum task_status stat)
141 {
142     /*stat取值為TASK_BLOCKED、TASK_WAITING、TASK_HANGING
143     這三種狀態才不會被調度*/
144     ASSERT(((stat == TASK_BLOCKED) || (stat == TASK_WAITING) || (stat == TASK_HANGING)));
145     enum intr_status old_status = intr_disable();
146     struct task_struct *cur_thread = running_thread();
147     cur_thread->status = stat;
148     schedule();
149     intr_set_status(old_status);
150 }
151 
152 /*將執行緒thread解除阻塞*/
153 void thread_unblock(struct task_struct *thread)
154 {
155     enum intr_status old_status = intr_disable();
156     ASSERT(((thread->status == TASK_BLOCKED) || (thread->status == TASK_WAITING) || (thread->status == TASK_HANGING)));
157     if (thread->status != TASK_READY) {
158         ASSERT(!elem_find(&thread_ready_list, &thread->general_tag));
159         if (elem_find(&thread_ready_list, &thread->general_tag)) {
160             PANIC("thread_unblock: blocked thread in ready_list!\n");
161         }
162         list_push(&thread_ready_list, &thread->general_tag);
163         thread->status = TASK_READY;
164     }
165     intr_set_status(old_status);
166 }
thread.c
 1 #ifndef  __KERNEL_THREAD_H
 2 #define  __KERNEL_THREAD_H
 3 #include "stdint.h"
 4 #include "list.h"
 5 #include "memory.h"
 6 
 7 /*自定義通用函式型別,它將在很多執行緒函式中作為形參型別*/
 8 typedef void thread_func (void *);
 9 #define PG_SIZE 4096
10 /*行程或執行緒的狀態*/
11 enum task_status {
12     TASK_RUNNING,
13     TASK_READY,
14     TASK_BLOCKED,
15     TASK_WAITING,
16     TASK_HANGING,
17     TASK_DIED
18 };
19 
20 /****************中斷堆疊intr_stack****************/
21 struct intr_stack {
22     uint32_t vec_no;
23     uint32_t edi;
24     uint32_t esi;
25     uint32_t ebp;
26     uint32_t esp_dummy;
27     uint32_t ebx;
28     uint32_t edx;
29     uint32_t ecx;
30     uint32_t eax;
31     uint32_t gs;
32     uint32_t fs;
33     uint32_t es;
34     uint32_t ds;
35 
36 /*以下由cpu從低特權級進入高特權級時壓入*/
37     uint32_t err_code;
38     void (*eip)(void);
39     uint32_t cs;
40     uint32_t eflags;
41     void *esp;
42     uint32_t ss;
43 };
44 
45 /***************執行緒堆疊thread_stack**********/
46 struct thread_stack 
47 {
48     uint32_t ebp;
49     uint32_t ebx;
50     uint32_t edi;
51     uint32_t esi;
52 
53     void (*eip) (thread_func *func, void *func_arg);
54     void (*unused_retaddr);
55     thread_func *function;
56     void *func_arg;
57 };
58 
59 /************行程或者執行緒的pcb,程式控制塊**********/
60 struct task_struct
61 {
62     uint32_t *self_kstack;    //每個內核執行緒自己的內核堆疊
63     enum task_status status;
64     uint8_t priority;
65     
66     char name[16];
67     uint8_t ticks;            //每次在處理器上執行的時間滴答數
68 
69     /*此任務自從上CPU運行至今占用了多少滴答數,也就是這個任務執行了多久時間*/
70     uint32_t elapsed_ticks;
71 
72     /*general_tag的作用是用于執行緒在一般的佇列中的節點*/
73     struct list_elem general_tag;
74 
75     /*all_list_tag的作用是用于執行緒thread_all_list的節點*/
76     struct list_elem all_list_tag;
77 
78     uint32_t *pgdir;//行程自己頁表的虛擬地址
79 
80     struct virtual_addr userprog_vaddr;   //用戶行程的虛擬地址池
81 
82     uint32_t stack_magic;
83 };
84 
85 void schedule(void);
86 struct task_struct *running_thread(void);
87 static void kernel_thread(thread_func *function, void *func_arg);
88 void init_thread(struct task_struct *pthread, char *name, int prio);
89 void thread_create(struct task_struct *pthread, thread_func function, void *func_arg);
90 struct task_struct *thread_start(char *name, int prio, thread_func function, void *func_arg);
91 static void make_main_thread(void);
92 void thread_init(void);
93 void thread_block(enum task_status stat);
94 void thread_unblock(struct task_struct *thread);
95 
96 
97 #endif
thread.h

  修改main.c檔案,本來用戶行程在執行前,是由作業系統的程式加載起將用戶程式從檔案系統直接讀取到記憶體,再根據程式檔案的格式決議其內容,將程式中的段展開到相應的記憶體地址,程式格式會記錄程式的入口地址,CPU把CS:[E]IP指向它,該程式就被執行了,C語言雖然不能直接控制這兩個暫存器,但是函式呼叫其實就是改變這兩個暫存器的指向,故C語言撰寫的作業系統可以像呼叫函式那樣呼叫執行用戶程式,因此用戶行程被加載到記憶體中后如同函式一樣,僅僅是個指令區域,由于我們目前沒有實作檔案系統,前期我們用普通函式來代替用戶程式,所以在main函式中我們新建了兩個名為u_prog_a和u_prog_b的兩個函式來作為行程執行的用戶程式,在這兩個程式中分別對test_var_a和test_var_b變數進行加1操作,由于用戶態下的字串列印函式我們還沒實作,所以又新建兩個內核執行緒k_thread_a和k_thread_b來列印這兩個變數,

 1 #include "print.h"
 2 #include "debug.h"
 3 #include "init.h"
 4 #include "memory.h"
 5 #include "thread.h"
 6 #include "timer.h"
 7 #include "list.h"
 8 #include "interrupt.h"
 9 #include "console.h"
10 #include "keyboard.h"
11 #include "ioqueue.h"
12 #include "process.h"
13 
14 void k_thread_a(void *arg);
15 void k_thread_b(void *arg);
16 void u_prog_a(void);
17 void u_prog_b(void);
18 int test_var_a = 0, test_var_b = 0;
19 int main (void)
20 {
21     put_str("I am Kernel\n");
22     init_all();
23 
24     thread_start("k_thread_a", 31, k_thread_a, "argA ");
25     thread_start("k_thread_b", 31, k_thread_b, "argB ");
26     process_execute(u_prog_a, "user_prog_a");
27     process_execute(u_prog_b, "user_prog_b");
28     intr_enable();
29 
30     while (1);
31     return 0;  
32 }
33 
34 void u_prog_a(void)
35 {
36     while(1) {
37         test_var_a = *(int *)(0xc0006480);
38     }
39 }
40 
41 void u_prog_b(void)
42 {
43     while(1) {
44         test_var_b++;
45     }
46 }
47 
48 void k_thread_a(void *arg)
49 {
50     char *para = arg;
51     while (1) {
52         console_put_str("v_a:0x");
53         console_put_int(test_var_a);
54         console_put_str("\n");
55     }
56 }
57 
58 void k_thread_b(void *arg)
59 { 
60     char *para = arg;
61     while (1) {
62         console_put_str("v_b:0x");
63         console_put_int(test_var_b);
64         console_put_str("\n");
65     }
66 }
main.c

  運行測驗,可以看到基本正常,

  

五、原書勘誤
  這個地方我當初做到這里這一章節時,死活調不通,通過打斷點,可以看到進入行程后,中斷表有明顯的例外,
  
  在行程中,中斷表的位置位于0x000063c0處,當然每個人的實際情況可能不太一樣,總之明顯不對,因為我們只給行程的頁目錄表映射了內核部分,很明顯這個地址是沒有被添加到頁表中的,所以一旦發生了中斷,CPU拿著這個中斷表的地址去找中斷描述符時就會報錯,因為頁表中沒有記錄這個位置的映射關系,

  后面除錯的時候發現其實是在實作中斷代碼那一章時,書上給的代碼有誤,原書第330頁,如下:
  
  黃色部分的代碼是罪魁禍首,我測驗了一下,在我的系統中idt被存放在虛擬地址0xc00063c0處,對應到物理地址就是0x000063c0處,經過上圖這種移位操作后,最終得到的地址變成了虛擬地址0x000063c0,可以發現高16位被舍掉了,在我們還沒有實作行程的時候,在內核執行緒的頁表中0x000063c0和0xc00063c0這兩個虛擬地址都是映射到0x000063c0這個物理地址的,所以我們前面并不會報錯,但是到了行程,在我們行程的頁表中,只有0xc00063c0這個虛擬地址映射到0x000063c0這個物理地址,而0x000063c0這個虛擬地址是沒有被添加映射關系的,所以才會一執行就報錯,所以將代碼修改成如下就好了:

  uint64_t idt_operand = (sizeof(idt) - 1) | ((uint64_t)(uint32_t)idt << 16);

  好了,本回合就到此結束了,這一章知識量還是比較多的,代碼也是很長的,我也是回味了很久,預知后事如何,請看下回分解,

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/503577.html

標籤:嵌入式

上一篇:Mac本M1芯片安裝Homebrew

下一篇:win11常用快捷鍵

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more