[自製操作系統] 第09回 載入內核

来源:https://www.cnblogs.com/Lizhixing/archive/2022/06/20/16393527.html
-Advertisement-
Play Games

目錄 一、前景回顧 二、用C語言編寫內核 三、載入內核 四、運行測試 一、前景回顧 本回開始,我們要開始編寫內核代碼了,在此之前,先梳理一下已經完成的工作。 藍色部分是目前已經完成的部分,黃色部分是本節將要實現的。 二、用C語言編寫內核 為什麼要用C語言來編寫內核呢,其實用彙編語言也可以實現,只是對 ...


目錄
一、前景回顧
二、用C語言編寫內核
三、載入內核
四、運行測試

 

一、前景回顧

  本回開始,我們要開始編寫內核代碼了,在此之前,先梳理一下已經完成的工作。
  
  藍色部分是目前已經完成的部分,黃色部分是本節將要實現的。

二、用C語言編寫內核

  為什麼要用C語言來編寫內核呢,其實用彙編語言也可以實現,只是對於我們來講,看C語言代碼肯定要比彙編語言更容易理解,看起來也沒那麼費勁。所以用C語言可以更加省事。

  先來看看我們內核代碼的最初形態,首先在項目路徑下新建一個project/kernel的目錄,以後我們內核相關的文件都存放於此,在該目錄下新建一個名為main.c的文件,在main.c中鍵入如下代碼:

1 int main(void)
2 {
3     while(1);
4     return 0;
5 }

  這就是我們的內核代碼,當然現在什麼都還沒有,就算內核成功載入進去也沒有什麼反應。這裡我們先實現一個自己的列印函數,在main函數中調用這個列印函數來列印出“HELLO KERNEL”的字元,這樣就能測試內核代碼運行是否成功。前面我們一直都是直接操作顯存段的記憶體來往屏幕上來列印字元,現在開始用C語言編程了,自然要封裝一個列印函數來列印字元。

  同樣,在項目路徑下新建另一個project/lib/kernel目錄,該目錄用來存放一些供內核使用的庫文件。在該目錄下新建名為print.S和print.h的文件,在此之前,我們在project/lib目錄下新建一個名為stdint.h的文件用來定義一些數據類型。代碼如下:

 1 #ifndef __LIB_STDINT_H__
 2 #define __LIB_STDINT_H__
 3 typedef signed char int8_t;
 4 typedef signed short int int16_t;
 5 typedef signed int int32_t;
 6 typedef signed long long int int64_t;
 7 typedef unsigned char uint8_t;
 8 typedef unsigned short int uint16_t;
 9 typedef unsigned int uint32_t;
10 typedef unsigned long long int uint64_t;
11 #endif
stdint.h
  1 TI_GDT         equ  0
  2 RPL0           equ  0
  3 SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0
  4 
  5 section .data
  6 put_int_buffer dq 0
  7 
  8 [bits 32]
  9 section .text
 10 ;-----------------------------------put_str--------------------------------------
 11 ;功能描述:put_str通過put_char來列印以0字元結尾的字元串
 12 ;----------------------------------------------------------------------------------
 13 global put_str
 14 put_str:
 15         push ebx
 16         push ecx
 17         xor ecx, ecx
 18         mov ebx, [esp + 12]
 19 .goon:
 20         mov cl, [ebx]
 21         cmp cl, 0
 22         jz .str_over
 23         push ecx
 24         call put_char
 25         add esp, 4
 26         inc ebx
 27         jmp .goon
 28 .str_over:
 29         pop ecx
 30         pop ebx
 31         ret
 32         
 33 ;--------------------------put_char-------------------------
 34 ;功能描述:把棧中的一個字元寫入到游標所在處
 35 ;---------------------------------------------------------------
 36 global put_char
 37 put_char:
 38         pushad                                         ;備份32位寄存器環境
 39         mov ax, SELECTOR_VIDEO  ;不能直接把立即數送入段寄存器中
 40         mov gs, ax
 41 
 42         ;----------------------獲取當前游標位置---------------------------------
 43         ;先獲取高8位
 44         mov dx, 0x03d4
 45         mov al, 0x0e
 46         out dx, al
 47         mov dx, 0x03d5
 48         in al, dx
 49         mov ah, al
 50 
 51         ;再獲取低8位
 52         mov dx, 0x03d4
 53         mov al, 0x0f
 54         out dx, al
 55         mov dx, 0x03d5
 56         in al, dx
 57 
 58         ;將游標位置存入bx
 59         mov bx, ax
 60 
 61         ;在棧中獲取待列印的字元
 62         mov ecx, [esp + 36]  ;pushad將8個32位寄存器都壓入棧中,再加上主調函數4位元組的返回地址,所以esp+36之後才是主調函數壓入的列印字元
 63         cmp cl, 0xd                 ;判斷該字元是否為CR(回車),CR的ASCII碼為0x0d
 64         jz .is_carriage_return
 65 
 66         cmp cl, 0xa                 ;判斷該字元是否為LF(換行),LF的ASCII碼為0x0a
 67         jz .is_line_feed
 68 
 69         cmp cl, 0x8                 ;判斷該字元是否為BS(空格),BS的ASCII碼為0x08
 70         jz .is_backspace
 71 
 72         jmp .put_other
 73 
 74 ;字元為BS(空格)的處理辦法
 75 .is_backspace:
 76         dec bx
 77         shl bx, 1
 78         mov byte [gs:bx], 0x20
 79         inc bx
 80         mov byte [gs:bx], 0x07
 81         shr bx, 1
 82         jmp set_cursor
 83 
 84 ;字元為CR(回車)以及LF(換行)的處理辦法
 85 .is_line_feed:
 86 .is_carriage_return:
 87         xor dx, dx
 88         mov ax, bx
 89         mov si, 80
 90         div si
 91         sub bx, dx
 92 
 93 ;CR(回車)符的處理結束
 94 .is_carriage_return_end:
 95         add bx, 80
 96         cmp bx, 2000
 97 ;LF(換行)符的處理結束
 98 .is_line_feed_end:
 99         jl set_cursor
100 
101 .put_other:
102         shl bx, 1
103         mov [gs:bx], cl
104         inc bx
105         mov byte [gs:bx], 0x07
106         shr bx, 1
107         inc bx
108         cmp bx, 2000
109         jl set_cursor
110 
111 .roll_screen:
112         cld
113         mov ecx, 960
114         mov esi, 0xc00b80a0
115         mov edi, 0xc00b8000
116         rep movsd
117         
118         mov ebx, 3840
119         mov ecx, 80
120 
121 .cls:
122         mov word [gs:ebx], 0x0720
123         add ebx, 2
124         loop .cls
125         mov bx, 1920
126 global set_cursor
127 set_cursor:
128         mov dx, 0x03d4
129         mov al, 0x0e
130         out dx, al
131         mov dx, 0x03d5
132         mov al, bh
133         out dx, al
134 
135         mov dx, 0x03d4
136         mov al, 0x0f
137         out dx, al
138         mov dx, 0x03d5
139         mov al, bl
140         out dx, al
141 .put_char_done:
142         popad
143         ret
144 ;-----------------------------------put_int--------------------------------------
145 ;功能描述:將小端位元組序的數字變成對應的ASCII後,倒置
146 ;輸入:棧中參數為待列印的數字
147 ;輸出:在屏幕中列印十六進位數字,並不會列印首碼0x
148 ;如列印十進位15時,只會列印f,而不是0xf
149 ;----------------------------------------------------------------------------------
150 global put_int
151 put_int:
152         pushad
153         mov ebp, esp
154         mov eax, [ebp + 36]
155         mov edx, eax
156         mov edi, 7
157         mov ecx, 8
158         mov ebx, put_int_buffer
159 
160 ;將32位數字按照16進位的形式從低位到高位逐個處理,共處理8個16進位數字
161 .16based_4bits:                   ; 每4位二進位是16進位數字的1位,遍歷每一位16進位數字
162         and edx, 0x0000000F               ; 解析16進位數字的每一位。and與操作後,edx只有低4位有效
163         cmp edx, 9                   ; 數字0~9和a~f需要分別處理成對應的字元
164         jg .is_A2F 
165         add edx, '0'                   ; ascii碼是8位大小。add求和操作後,edx低8位有效。
166         jmp .store
167 .is_A2F:
168         sub edx, 10                   ; A~F 減去10 所得到的差,再加上字元A的ascii碼,便是A~F對應的ascii碼
169         add edx, 'A'
170 
171 ;將每一位數字轉換成對應的字元後,按照類似“大端”的順序存儲到緩衝區put_int_buffer
172 ;高位字元放在低地址,低位字元要放在高地址,這樣和大端位元組序類似,只不過咱們這裡是字元序.
173 .store:
174 ; 此時dl中應該是數字對應的字元的ascii碼
175         mov [ebx+edi], dl               
176         dec edi
177         shr eax, 4
178         mov edx, eax 
179         loop .16based_4bits
180 
181 ;現在put_int_buffer中已全是字元,列印之前,
182 ;把高位連續的字元去掉,比如把字元000123變成123
183 .ready_to_print:
184         inc edi                   ; 此時edi退減為-1(0xffffffff),加1使其為0
185 .skip_prefix_0:  
186         cmp edi,8                   ; 若已經比較第9個字元了,表示待列印的字元串為全0 
187         je .full0 
188 ;找出連續的0字元, edi做為非0的最高位字元的偏移
189 .go_on_skip:   
190         mov cl, [put_int_buffer+edi]
191         inc edi
192         cmp cl, '0' 
193         je .skip_prefix_0               ; 繼續判斷下一位字元是否為字元0(不是數字0)
194         dec edi                   ;edi在上面的inc操作中指向了下一個字元,若當前字元不為'0',要恢復edi指向當前字元               
195         jmp .put_each_num
196 
197 .full0:
198         mov cl,'0'                   ; 輸入的數字為全0時,則只列印0
199 .put_each_num:
200         push ecx                   ; 此時cl中為可列印的字元
201         call put_char
202         add esp, 4
203         inc edi                   ; 使edi指向下一個字元
204         mov cl, [put_int_buffer+edi]           ; 獲取下一個字元到cl寄存器
205         cmp edi,8
206         jl .put_each_num
207         popad
208         ret
print.S
1 #ifndef  __LIB_KERNEL_PRINT_H
2 #define  __LIB_KERNEL_PRINT_H
3 #include "stdint.h"
4 void put_char(uint8_t char_asci);
5 void put_str(char *message);
6 void put_int(uint32_t num);
7 #endif
print.h

  最後輸入如下命令來編譯print.S:

nasm -f elf -o ./project/lib/kernel/print.o ./project/lib/kernel/print.S

  完善了列印函數後,我們現在可以在main函數中實現列印功能了,修改main.c文件:

1 #include "print.h"
2 int main(void)
3 {
4     put_str("HELLO KERNEL\n");
5     while(1);
6     return 0;
7 }

三、載入內核

  前面我們已經將內核代碼實現完成了,接下來按道理應該和前面一樣,將main.c文件編譯載入到硬碟中,隨後通過loader來讀取載入該文件,最終跳轉運行。的確也是如此,不過略有不同。請聽我慢慢講來。

  現在我們是main.c文件,不同於彙編代碼,我們接下來要使用gcc工具將main.c文件編譯成main.o文件:

gcc -m32 -I project/lib/kernel/ -c -fno-builtin project/kernel/main.c -o project/kernel/main.o

  它只是一個目標文件,也稱為重定位文件,重定位文件指的是文件裡面所用的符號還沒有安排地址,這些符號的地址將來是要與其他目標文件“組成”一個可執行文件時再重定位(編排地址),這裡的符號就是指的所調用的函數或使用的變數,看我們的main.c文件中,在main函數中調用了print.h中聲明的put_str函數,所以將來main.o文件需要和print.o文件一起組成可執行文件。

  如何“組成”呢?這裡的“組成”其實就是指的C語言程式變成可執行文件下的四步驟(預處理、編譯、彙編和鏈接)中的鏈接,Linux下使用的是ld命令來鏈接,我們是在Linux平臺下的,所以自然使用ld命令:

ld -m elf_i386 -Ttext 0xc0001500 -e main -o project/kernel/kernel.bin project/kernel/main.o project/lib/kernel/print.o

  最終生成可執行文件kernel.bin。它就是我們需要載入到硬碟里的那個文件。

  到這裡都和前面步驟一致,只是後面loader並不是單純的將kernel.bin文件拷貝到記憶體某處再跳轉執行。這是因為我們生成的kernel.bin文件的格式為elf,elf格式的文件,在文件最開始有一個名為elf格式頭的部分,該部分詳細包含了整個文件的信息,具體內容過多我這裡不再展開講,感興趣的朋友可以參考原書《操作系統真象還原》p213~222,或者百度。所以說如果我們只是單純地跳轉到該文件的載入處,那麼必定會出現問題,因為該文件的開始部分並不是可供CPU執行的程式,我們跳轉的地址應該是該文件的程式部分。這個地址在我們前面鏈接時已經指定為0xc0001500,因為我們前面已經開啟了分頁機制,所以實際上這個地址對應的是物理地址的0x1500處。

  接下來再修改loader.S文件,增加拷貝內核部分代碼以及拷貝函數代碼,為了便於閱讀,我將新代碼附在了之前的loader.S文件下,除此之外,boot.inc也有新增的內容。

  1 %include "boot.inc"
  2 section loader vstart=LOADER_BASE_ADDR
  3 LOADER_STACK_TOP equ LOADER_BASE_ADDR
  4 jmp loader_start
  5 
  6 ;構建gdt及其內部描述符
  7 GDT_BASE:        dd 0x00000000
  8                 dd 0x00000000
  9 CODE_DESC:       dd 0x0000FFFF
 10                 dd DESC_CODE_HIGH4
 11 DATA_STACK_DESC: dd 0x0000FFFF
 12                 dd DESC_DATA_HIGH4
 13 VIDEO_DESC:      dd 0x80000007
 14                 dd DESC_VIDEO_HIGH4
 15 
 16 GDT_SIZE  equ $-GDT_BASE
 17 GDT_LIMIT equ GDT_SIZE-1
 18 times 60 dq 0  ;此處預留60個描述符的空位
 19 
 20 SELECTOR_CODE  equ (0x0001<<3) + TI_GDT + RPL0
 21 SELECTOR_DATA  equ (0x0002<<3) + TI_GDT + RPL0
 22 SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0
 23 
 24 ;以下是gdt指針,前2個位元組是gdt界限,後4個位元組是gdt的起始地址
 25 gdt_ptr   dw GDT_LIMIT 
 26         dd GDT_BASE
 27 
 28 ;---------------------進入保護模式------------
 29 loader_start:
 30     ;一、打開A20地址線
 31     in al, 0x92
 32     or al, 0000_0010B
 33     out 0x92, al
 34     
 35     ;二、載入GDT
 36     lgdt [gdt_ptr]
 37 
 38     ;三、cr0第0位(pe)置1
 39     mov eax, cr0
 40     or eax, 0x00000001
 41     mov cr0, eax
 42     
 43     jmp dword SELECTOR_CODE:p_mode_start ;刷新流水線
 44 
 45     [bits 32]
 46     p_mode_start:
 47             mov ax, SELECTOR_DATA
 48             mov ds, ax
 49             mov es, ax
 50             mov ss, ax
 51             mov esp, LOADER_STACK_TOP
 52             mov ax, SELECTOR_VIDEO
 53             mov gs, ax
 54             
 55             mov byte [gs:160], 'p'
 56 
 57 ;------------------開啟分頁機制-----------------
 58     ;一、創建頁目錄表並初始化頁記憶體點陣圖
 59     call setup_page
 60 
 61     ;將描述符表地址及偏移量寫入記憶體gdt_ptr,一會兒用新地址重新載入
 62     sgdt [gdt_ptr]
 63     ;將gdt描述符中視頻段描述符中的段基址+0xc0000000
 64     mov ebx, [gdt_ptr + 2]
 65     or dword [ebx + 0x18 + 4], 0xc0000000
 66             
 67     ;將gdt的基址加上0xc0000000使其成為內核所在的高地址
 68     add dword [gdt_ptr + 2], 0xc0000000
 69 
 70     add esp, 0xc0000000  ;將棧指針同樣映射到內核地址
 71             
 72     ;二、將頁目錄表地址賦值給cr3
 73     mov eax, PAGE_DIR_TABLE_POS
 74     mov cr3, eax
 75             
 76     ;三、打開cr0的pg位
 77     mov eax, cr0
 78     or eax, 0x80000000
 79     mov cr0, eax
 80             
 81     ;在開啟分頁後,用gdt新的地址重新載入
 82     lgdt [gdt_ptr]
 83     mov byte [gs:160], 'H'
 84     mov byte [gs:162], 'E'
 85     mov byte [gs:164], 'L'
 86     mov byte [gs:166], 'L'
 87     mov byte [gs:168], 'O'
 88     mov byte [gs:170], ' '
 89     mov byte [gs:172], 'P'
 90     mov byte [gs:174], 'A'
 91     mov byte [gs:176], 'G'
 92     mov byte [gs:178], 'E'
 93 
 94 ;---------------------------------------------
 95 
 96 ;--------------------拷貝內核文件併進入kernel--------------------------
 97     mov eax, KERNEL_START_SECTOR              ;kernel.bin所在的扇區號 0x09
 98     mov ebx, KERNEL_BIN_BASE_ADDR             ;從磁碟讀出後,寫入到ebx指定的地址0x70000
 99     mov ecx, 200                              ;讀入的扇區數
100 
101     call rd_disk_m_32
102 
103     ;由於一直處在32位下,原則上不需要強制刷新,但是以防萬一還是加上
104     ;跳轉到kernel處
105     jmp SELECTOR_CODE:enter_kernel
106     
107     enter_kernel:
108         call kernel_init
109         mov esp, 0xc009f000               ;更新棧底指針
110         jmp KERNEL_ENTRY_POINT            ;內核地址0xc0001500
111         ;jmp $
112         ;---------------------將kernel.bin中的segment拷貝到指定的地址
113         kernel_init:
114             xor eax, eax
115             xor ebx, ebx   ;ebx記錄程式頭表地址
116             xor ecx, ecx    ;cx記錄程式頭表中的program header數量
117             xor edx, edx    ;dx記錄program header 尺寸,即e_phentsize
118 
119             ;偏移文件42位元組處的屬性是e_phentsize, 表示program header大小
120             mov dx, [KERNEL_BIN_BASE_ADDR + 42]
121             
122             ;偏移文件28位元組處的屬性是e_phoff
123             mov ebx, [KERNEL_BIN_BASE_ADDR + 28]
124 
125             add ebx, KERNEL_BIN_BASE_ADDR
126             mov cx, [KERNEL_BIN_BASE_ADDR + 44]
127     
128             .each_segment: 
129                     cmp byte [ebx + 0], PT_NULL
130                     je .PTNULL
131 
132             ;為函數memcpy壓入參數,參數是從右往左壓入
133             push dword [ebx + 16]
134             mov eax, [ebx + 4]
135             add eax, KERNEL_BIN_BASE_ADDR
136             push eax
137             push dword [ebx + 8]
138             call mem_cpy
139             add esp, 12
140 
141             .PTNULL:
142                     add ebx, edx
143                     loop .each_segment
144             ret
145 
146             ;-----------逐位元組拷貝mem_cpy(dst, src, size)
147             mem_cpy:
148                     cld
149                     push ebp
150                     mov ebp, esp
151                     push ecx
152                     mov edi, [ebp + 8]
153                     mov esi, [ebp + 12]
154                     mov ecx, [ebp + 16]
155                     rep movsb
156 
157                     pop ecx
158                     pop ebp
159                     ret 
160 ;---------------------------------------------------    
161 
162 ;--------------函數聲明------------------------
163     ;setup_page:(功能)設置分頁------------
164     setup_page:
165         ;先把頁目錄

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

-Advertisement-
Play Games
更多相關文章
  • 大佬理解->Java集合之ArrayList 1、ArrayList的特點 存放的元素有序 元素不唯一(可以重覆) 隨機訪問快 插入刪除元素慢 非線程安全 2、底層實現 底層初始化,使用一個Object類型的空對象數組,初始長度為0; 源碼 //Object類型對象數組引用 transient Ob ...
  • 大佬的理解->《Java IO(五) -- 字元流進階及BufferedWriter,BufferedReader》 1、BufferedReader BufferedReader高效字元流讀取文件基本用法,自帶緩衝區,讀取文件效率高,支持逐行讀取; 1.1 初始化 BufferedReader(R ...
  • 游戲的世界精彩紛呈,有動作類、策略類、角色扮演類等諸多類型,還有很多難以分類的小游戲,讓人玩起來往往愛不釋手 ...
  • 大佬的理解->《Java IO(四) -- 字元流》 FileReader字元流讀取文件,更適合用於讀取文件,可以讀取中文 1、FileReader 1.1 初始化 FileReader(File file) FileReader(String fileName) 1.2 讀取文件內容 read() ...
  • 項目全部代碼地址:https://github.com/Tom-shushu/work-study.git (mqtt-emqt 項目) 先看我們最後實現的一個效果 1.手機端向主題 topic111 發送消息,並接收。(手機測試工具名稱:MQTT調試器) 2.控制台列印 MQTT基本簡介 MQTT ...
  • 大佬的理解->《Java IO(三) -- 位元組流》 1、FileInputStream 1.1 初始化 FileInputStream(String name) FileInputStream(File file) //直接通過文件地址初始化 FileInputStream fis = new i ...
  • 第二回 巧習得元素分類 子不知懷璧其罪 雲溪父親見狀看了看雲溪,臉上滲出意思冷汗,但遲疑一下就立即退了出去,匆匆忙忙的往右邊廚房趕,只留下了雲溪和這位神秘的老爺子。 雲溪瞠目結舌的看著悠然自得的喝著老爹泡的茶的老爺子,下意識說了一句:“老先生你怎麼這麼快,還知道我要來這裡”。 “方向,你一直在繞巷子 ...
  • 一、Iproute2簡介 Iproute2是一個在Linux下的高級網路管理工具軟體。實際上,它是通過rtnetlink sockets方式動態配置內核的一些小工具組成的,從Linux2.2內核開始,Alexey Kuznetsov 實現了通過rtnetlink sockets用來配置網路協議棧,它 ...
一周排行
    -Advertisement-
    Play Games
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...