《操作系统真象还原》第七章——改进中断

2024-06-19 13:36

本文主要是介绍《操作系统真象还原》第七章——改进中断,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

在上一节《操作系统真象还原》第七章——启用中断-CSDN博客,我们在kernel.S中定义了33个中断处理程序,并简单的对其进行了实现(打印字符串),但很显然,使用这种方式定义中断处理程序是很不方便的,一方面我们的中断处理程序都是用汇编语言写的,这增加了我们编写代码的难度,因为后期我们要对每个中断处理程序都要单独定义。另一方面,我们将中断处理程序的声明和定义放在了同一个文件中,不符合代码的高耦合低内聚原则

改进

主要改进如下:

将中断处理程序的定义放在c语言中实现,而调用仍然放在kernel.S中实现

以下是中断处理程序的实现,在这里我们仍旧统一将其定义为一个函数,后期再调整修改为对应的中断实现

kernel/interrupt.c

static void general_intr_handler(uint8_t vec_nr)
{//伪中断向量,无需处理if(vec_nr==0x27 || vec_nr==0x2f){return;}put_str("int vector:0x");put_int(vec_nr);put_char('\n');
}

改进的kernel.S

kernel/kernel.S

%macro VECTOR 2                         ;定义VECTOR宏,该宏用于定义中断处理程序
section .text                           ;中断处理程序的代码段
intr%1entry:                            ;这是一个标号,用于表示中断程序的入口地址,即中断程序的函数名%2                                  ;压入中断错误码(如果有)push ds                             ; 以下是保存上下文环境push espush fspush gspushad; 如果是从片上进入的中断,除了往从片上发送EOI外,还要往主片上发送EOI mov al,0x20                         ; 中断结束命令EOIout 0xa0,al                         ;向主片发送OCW2,其中EOI位为1,告知结束中断,详见书p317out 0x20,al                         ;向从片发送OCW2,其中EOI位为1,告知结束中断push %1			                    ; 不管idt_table中的目标程序是否需要参数,都一律压入中断向量号,调试时很方便call [idt_table + %1*4]             ; 调用idt_table中的C版本中断处理函数jmp intr_exitsection .datadd intr%1entry                      ;存储各个中断入口程序的地址,形成intr_entry_table数组,定义的地址是4字节,32位
%endmacrosection .text
global intr_exit
intr_exit:; 以下是恢复上下文环境add esp, 4			                ; 跳过中断号popadpop gspop fspop espop dsadd esp, 4			                 ;对于会压入错误码的中断会抛弃错误码(这个错误码是执行中断处理函数之前CPU自动压入的),对于不会压入错误码的中断,就会抛弃上面push的0                    iretd				                 ; 从中断返回,32位下iret等同指令iretd

完整代码

kernel/interrupt.c

#include "interrupt.h"      //里面定义了intr_handler类型
#include "stdint.h"         //各种uint_t类型
#include "global.h"         //里面定义了选择子
#include "io.h"             
#include "print.h"#define PIC_M_CTRL 0x20	       // 这里用的可编程中断控制器是8259A,主片的控制端口是0x20
#define PIC_M_DATA 0x21	       // 主片的数据端口是0x21
#define PIC_S_CTRL 0xa0	       // 从片的控制端口是0xa0
#define PIC_S_DATA 0xa1	       // 从片的数据端口是0xa1#define IDT_DESC_CNT 0x21	   //支持的中断描述符个数33//定义中断描述符结构体
struct gate_desc {uint16_t    func_offset_low_word;        //函数地址低字uint16_t    selector;                    //选择子字段uint8_t     dcount;                      //此项为双字计数字段,是门描述符中的第4字节。这个字段无用uint8_t     attribute;                   //属性字段uint16_t    func_offset_high_word;       //函数地址高字
};//定义中断门描述符结构体数组,形成中断描述符表idt,该数组中的元素是中断描述符
static struct gate_desc idt[IDT_DESC_CNT];
//静态函数声明,该函数用于构建中断描述符,将上述定义的中断描述符结构体按照字段说明进行填充即可
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function);//intr_handler为void*类型,intr_entry_table为中断处理程序数组,其内的元素是中断处理程序入口地址
extern intr_handler intr_entry_table[IDT_DESC_CNT];//存储中断/异常的名字
char* intr_name[IDT_DESC_CNT];    
//定义中断处理程序数组,在kernel.S中定义的intrXXentry只是中断处理程序的入口,最终调用的是ide_table中的处理程序           
intr_handler idt_table[IDT_DESC_CNT];/*初始化可编程中断控制器8259A*/
static void pic_init()
{/* 初始化主片 */outb (PIC_M_CTRL, 0x11);   // ICW1: 边沿触发,级联8259, 需要ICW4.outb (PIC_M_DATA, 0x20);   // ICW2: 起始中断向量号为0x20,也就是IR[0-7] 为 0x20 ~ 0x27.outb (PIC_M_DATA, 0x04);   // ICW3: IR2接从片. outb (PIC_M_DATA, 0x01);   // ICW4: 8086模式, 正常EOI/* 初始化从片 */outb (PIC_S_CTRL, 0x11);	// ICW1: 边沿触发,级联8259, 需要ICW4.outb (PIC_S_DATA, 0x28);	// ICW2: 起始中断向量号为0x28,也就是IR[8-15] 为 0x28 ~ 0x2F.outb (PIC_S_DATA, 0x02);	// ICW3: 设置从片连接到主片的IR2引脚outb (PIC_S_DATA, 0x01);	// ICW4: 8086模式, 正常EOI/* 打开主片上IR0,也就是目前只接受时钟产生的中断 */outb (PIC_M_DATA, 0xfe);outb (PIC_S_DATA, 0xff);put_str("pic_init done\n");
}/*
函数功能:构建中断描述符
函数实现:按照中断描述符结构体定义填充字段
参数:中断门描述符地址,属性,中断处理函数
*/
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function) 
{ p_gdesc->func_offset_low_word = (uint32_t)function & 0x0000FFFF;p_gdesc->selector = SELECTOR_K_CODE;p_gdesc->dcount = 0;p_gdesc->attribute = attr;p_gdesc->func_offset_high_word = ((uint32_t)function & 0xFFFF0000) >> 16;
}/*
函数功能:中断描述符表idt
函数实现:循环调用make_idt_desc构建中断描述符,形成中断描述符表idt
参数:中断描述符表中的某个中断描述符地址,属性字段,中断处理函数地址
*/
static void idt_desc_init(void) {int i;for (i = 0; i < IDT_DESC_CNT; i++) {make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]); }put_str("idt_desc_init done\n");
}#if 1
/*用c语言定义中断处理函数的具体实现,不再用汇编语言在kernel.S中定义中断处理函数的实现*/
//参数:中断向量号
static void general_intr_handler(uint8_t vec_nr)
{//伪中断向量,无需处理if(vec_nr==0x27 || vec_nr==0x2f){return;}put_str("int vector:0x");put_int(vec_nr);put_char('\n');
}
#endif/* 完成一般中断处理函数注册及异常名称注册 */
static void exception_init(void) {			   // 完成一般中断处理函数注册及异常名称注册int i;for (i = 0; i < IDT_DESC_CNT; i++) {/* idt_table数组中的函数是在进入中断后根据中断向量号调用的,* 见kernel/kernel.S的call [idt_table + %1*4] */idt_table[i] = general_intr_handler;   // 默认为general_intr_handler。// 以后会由register_handler来注册具体处理函数。intr_name[i] = "unknown";				   // 先统一赋值为unknown }intr_name[0] = "#DE Divide Error";intr_name[1] = "#DB Debug Exception";intr_name[2] = "NMI Interrupt";intr_name[3] = "#BP Breakpoint Exception";intr_name[4] = "#OF Overflow Exception";intr_name[5] = "#BR BOUND Range Exceeded Exception";intr_name[6] = "#UD Invalid Opcode Exception";intr_name[7] = "#NM Device Not Available Exception";intr_name[8] = "#DF Double Fault Exception";intr_name[9] = "Coprocessor Segment Overrun";intr_name[10] = "#TS Invalid TSS Exception";intr_name[11] = "#NP Segment Not Present";intr_name[12] = "#SS Stack Fault Exception";intr_name[13] = "#GP General Protection Exception";intr_name[14] = "#PF Page-Fault Exception";// intr_name[15] 第15项是intel保留项,未使用intr_name[16] = "#MF x87 FPU Floating-Point Error";intr_name[17] = "#AC Alignment Check Exception";intr_name[18] = "#MC Machine-Check Exception";intr_name[19] = "#XF SIMD Floating-Point Exception";}/*完成有关中断的所有初始化工作*/
void idt_init() {put_str("idt_init start\n");idt_desc_init();	                        //完成中段描述符表的构建pic_init();		                           //设定化中断控制器,只接受来自时钟中断的信号exception_init();/* 加载idt */uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)(uint32_t)idt << 16));    //定义要加载到IDTR寄存器中的值asm volatile("lidt %0" : : "m" (idt_operand));put_str("idt_init done\n");
}

kernel/kernel.S

[bits 32]
%define ERROR_CODE nop
%define ZERO push 0extern idt_tablesection .data                           ;定义数据段global intr_entry_table                 ;定义中断处理程序数据,数组的元素值是中断处理程序,共33个
intr_entry_table:;宏定义的语法格式为:
;       %macro 宏名 参数个数
;           宏定义
;       %endmacro
%macro VECTOR 2                         ;定义VECTOR宏,该宏用于定义中断处理程序
section .text                           ;中断处理程序的代码段
intr%1entry:                            ;这是一个标号,用于表示中断程序的入口地址,即中断程序的函数名%2                                  ;压入中断错误码(如果有)push ds                             ; 以下是保存上下文环境push espush fspush gspushad; 如果是从片上进入的中断,除了往从片上发送EOI外,还要往主片上发送EOI mov al,0x20                         ; 中断结束命令EOIout 0xa0,al                         ;向主片发送OCW2,其中EOI位为1,告知结束中断,详见书p317out 0x20,al                         ;向从片发送OCW2,其中EOI位为1,告知结束中断push %1			                    ; 不管idt_table中的目标程序是否需要参数,都一律压入中断向量号,调试时很方便call [idt_table + %1*4]             ; 调用idt_table中的C版本中断处理函数jmp intr_exitsection .datadd intr%1entry                      ;存储各个中断入口程序的地址,形成intr_entry_table数组,定义的地址是4字节,32位
%endmacrosection .text
global intr_exit
intr_exit:; 以下是恢复上下文环境add esp, 4			                ; 跳过中断号popadpop gspop fspop espop dsadd esp, 4			                 ;对于会压入错误码的中断会抛弃错误码(这个错误码是执行中断处理函数之前CPU自动压入的),对于不会压入错误码的中断,就会抛弃上面push的0                    iretd				                 ; 从中断返回,32位下iret等同指令iretdVECTOR 0x00,ZERO                         ;调用之前写好的宏来批量生成中断处理函数,传入参数是中断号码与上面中断宏的%2步骤,这个步骤是什么都不做,还是压入0看p303
VECTOR 0x01,ZERO
VECTOR 0x02,ZERO
VECTOR 0x03,ZERO 
VECTOR 0x04,ZERO
VECTOR 0x05,ZERO
VECTOR 0x06,ZERO
VECTOR 0x07,ZERO 
VECTOR 0x08,ERROR_CODE
VECTOR 0x09,ZERO
VECTOR 0x0a,ERROR_CODE
VECTOR 0x0b,ERROR_CODE 
VECTOR 0x0c,ZERO
VECTOR 0x0d,ERROR_CODE
VECTOR 0x0e,ERROR_CODE
VECTOR 0x0f,ZERO 
VECTOR 0x10,ZERO
VECTOR 0x11,ERROR_CODE
VECTOR 0x12,ZERO
VECTOR 0x13,ZERO 
VECTOR 0x14,ZERO
VECTOR 0x15,ZERO
VECTOR 0x16,ZERO
VECTOR 0x17,ZERO 
VECTOR 0x18,ERROR_CODE
VECTOR 0x19,ZERO
VECTOR 0x1a,ERROR_CODE
VECTOR 0x1b,ERROR_CODE 
VECTOR 0x1c,ZERO
VECTOR 0x1d,ERROR_CODE
VECTOR 0x1e,ERROR_CODE
VECTOR 0x1f,ZERO 
VECTOR 0x20,ZERO

lib/kernel/print.S

TI_GDT equ 0
RPL0 equ 0
SELECTOR_VIDEO equ (0x0003<<3)+TI_GDT+RPL0;定义显存段选择子section .data
put_int_buffer dq 0                                         ; 定义8字节缓冲区用于数字到字符的转换;--------------------   字符串打印函数   -----------------------
[bits 32]
section .text
global put_strput_str:push ebxpush ecxxor ecx,ecxmov ebx,[esp+12]                ;ebx存放字符串首地址
.goon:mov cl,[ebx]                    ;取出字符串的第一个字符,存放到cx寄存器中cmp cl,0                        ;判断是否到了字符串结尾,字符串结尾标志符'\0'即0jz .str_overpush ecx                        ;压入put_char参数,调用put_char函数call put_charadd esp,4                       ;回收栈空间inc ebx                         ;指向字符串的下一个字符jmp .goon
.str_over:pop ecx                         ;回收栈空间pop ebx                         ;回收栈空间ret;--------------------   单字符打印函数   -----------------------
[bits 32]
section .text
global put_char                     ;通过global关键字将put_char函数定义为全局符号;使其对外部文件可见
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
put_char:pushad                          ;push all double,压入所有双字长的寄存器;入栈顺序为eax->ecx->edx->ebx->esp->ebp->esi->edimov ax,SELECTOR_VIDEOmov gs,ax                       ;为gs寄存器赋予显存段的选择子;以下代码用于获取光标的坐标位置,光标的坐标位置存放在光标坐标寄存器中
;其中索引为0eh的寄存器和索引为0fh的寄存器分别存放光标高8位和低8位
;访问CRT controller寄存器组的寄存器,需要先往端口地址为0x03d4的寄存器写入索引
;从端口地址为0x03d5的数据寄存器中读写数据mov dx,0x03d4                   ;将0x03d4的端口写入dx寄存器中mov al,0x0e                     ;将需要的索引值写入al寄存器中out dx,al                       ;向0x03d4端口写入0x0e索引mov dx,0x03d5                   in al,dx                        ;从0x03d5端口处获取光标高8位mov ah,al                       ;ax寄存器用于存放光标坐标,;因此将光标坐标的高8位数据存放到ah中;同上,以下代码获取光标坐标的低8位mov dx,0x03d4mov al,0x0fout dx,almov dx,0x03d5in al,dx                        ;此时ax中就存放着读取到的光标坐标值mov bx,ax                       ;bx寄存器不仅是光标坐标值,同时也是下一个可打印字符的位置;而我们习惯于bx作为基址寄存器,以后的处理都要基于bx寄存器;因此才将获取到的光标坐标值赋值为bxmov ecx,[esp+36]                ;前边已经压入了8个双字(2个字节)的寄存器,;加上put_char函数的4字节返回地址;所以待打印的字符在栈顶偏移36字节的位置cmp cl,0xd                      ;回车符处理jz .is_carriage_returncmp cl,0xa                      ;换行符处理jz .is_line_feedcmp cl,0x8                      ;退格键处理jz .is_backspace                jmp .put_other                  ;正常字符处理;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;退格键处理;处理思路:;1.将光标位置减一;2.将待删除的字符使用空格字符(ASCII:0x20)代替
.is_backspace:dec bx                          ;bx中存储光标的坐标位置,将光标坐标位置减去一,即模拟退格shl bx,1                        ;由于文本模式下一个字符占用两个字节(第一个字节表示字符的ASCII码,第二个字节表示字符的属性),;故光标位置乘以2就是光标处字符的第一个字节的偏移量mov byte[gs:bx],0x20            ;将空格键存入待删除字符处inc bx                          ;此时bx中存储的是字待删除字符的第一个字节位置,;使用inc指令将bx加1后就是该字符的第二个字节的位置mov byte[gs:bx],0x07            ;将黑底白字(0x07)属性加入到该字符处shr bx,1                        ;bx除以2,恢复光标坐标位置jmp .set_cursor                 ;去设置光标位置, 这样光标位置才能真正在视觉上更新;将cx指向的字符放入到光标处
.put_other:shl bx,1                        ;将光标坐标转换为内存偏移量mov byte[gs:bx],cl              ;将cx指向的字符放入到光标处inc bx                          ;bx指向当前字符的下一个字节处,存放当前字符属性mov byte[gs:bx],0x07            ;存放字符属性shr bx,1                        ;将内存偏移量恢复为光标坐标值inc bx                          ;bx指向下一个待写入字符位置cmp bx,2000                     ;80*25=2000,判断是否字符已经写满屏了jl .set_cursor                  ;更新光标坐标值;换行处理;思路:首先将光标移动到本行行首,之后再将光标移动到下一行行首
.is_line_feed:
.is_carriage_return:xor dx,dx;将光标移动到本行行首mov ax,bxmov si,80div si                          ;除法操作,ax/si,结果ax存储商,dx存储余数sub bx,dx.is_carriage_return_end:add bx,80                   ;将光标移动到下一行行首cmp bx,2000.is_line_feed_end:jl .set_cursor;滚屏处理;思路:屏幕行范围是0~24,滚屏的原理是将屏幕的1~24行搬运到0~23行,再将第24行用空格填充
.roll_screen:cld                             ;清除方向标志位,字符串的内存移动地址从低地址向高地址移动;若方向标志位被设置,则字符串的内存移动地址从高地址向低地址移动mov ecx,960                     ;共移动2000-80=192个字符,每个字符占2个字节,故共需移动1920*2=3840个字节;movsd指令每次移动4个字节,故共需执行该指令3840/4=960次数mov esi,0xb80a0                 ;第一行行首地址,要复制的起始地址mov edi,0xb8000                 ;第0行行首地址,要复制的目的地址rep movsd                       ;rep(repeat)指令,重复执行movsd指令,执行的次数在ecx寄存器中;将最后一行填充为空白mov ebx,3840mov ecx,80.cls:mov word[gs:ebx],0x0720add ebx,2loop .clsmov bx,1920                 ;将光标值重置为1920,最后一行的首字符..set_cursor:;将光标设为bx值;;;;;;; 1 先设置高8位 ;;;;;;;;mov dx, 0x03d4			                                ;索引寄存器mov al, 0x0e				                            ;用于提供光标位置的高8位out dx, almov dx, 0x03d5			                                ;通过读写数据端口0x3d5来获得或设置光标位置 mov al, bhout dx, al;;;;;;; 2 再设置低8位 ;;;;;;;;;mov dx, 0x03d4mov al, 0x0fout dx, almov dx, 0x03d5 mov al, blout dx, al.put_char_done:popadret;--------------------   将小端字节序的数字变成对应的ascii后,倒置   -----------------------
;输入:栈中参数为待打印的数字
;输出:在屏幕上打印16进制数字,并不会打印前缀0x,如打印10进制15时,只会直接打印f,不会是0xf
;------------------------------------------------------------------------------------------
global put_int
put_int:pushadmov ebp, espmov eax, [ebp+4*9]		                                ; call的返回地址占4字节+pushad的8个4字节,现在eax中就是要显示的32位数值mov edx, eax                                             ;ebx中现在是要显示的32位数值mov edi, 7                                               ; 指定在put_int_buffer中初始的偏移量,也就是把栈中第一个字节取出放入buffer最后一个位置,第二个字节放入buff倒数第二个位置mov ecx, 8			                                    ; 32位数字中,16进制数字的位数是8个mov ebx, put_int_buffer                                  ;ebx现在存储的是buffer的起始地址;将32位数字按照16进制的形式从低位到高位逐个处理,共处理8个16进制数字
.16based_4bits:			                                    ; 每4位二进制是16进制数字的1位,遍历每一位16进制数字and edx, 0x0000000F		                                ; 解析16进制数字的每一位。and与操作后,edx只有低4位有效cmp edx, 9			                                    ; 数字0~9和a~f需要分别处理成对应的字符jg .is_A2F add edx, '0'			                                    ; ascii码是8位大小。add求和操作后,edx低8位有效。jmp .store
.is_A2F:sub edx, 10			                                    ; A~F 减去10 所得到的差,再加上字符A的ascii码,便是A~F对应的ascii码add edx, 'A';将每一位数字转换成对应的字符后,按照类似“大端”的顺序存储到缓冲区put_int_buffer;高位字符放在低地址,低位字符要放在高地址,这样和大端字节序类似,只不过咱们这里是字符序.
.store:mov [ebx+edi], dl		                                ; 此时dl中是数字对应的字符的ascii码dec edi                                                  ;edi是表示在buffer中存储的偏移,现在向前移动shr eax, 4                                               ;eax中是完整存储了这个32位数值,现在右移4位,处理下一个4位二进制表示的16进制数字mov edx, eax                                             ;把eax中的值送入edx,让ebx去处理loop .16based_4bits;现在put_int_buffer中已全是字符,打印之前,;把高位连续的字符去掉,比如把字符00000123变成123
.ready_to_print:inc edi			                                        ; 此时edi退减为-1(0xffffffff),加1使其为0
.skip_prefix_0:                                             ;跳过前缀的连续多个0cmp edi,8			                                    ; 若已经比较第9个字符了,表示待打印的字符串为全0 je .full0 ;找出连续的0字符, edi做为非0的最高位字符的偏移
.go_on_skip:   mov cl, [put_int_buffer+edi]inc edicmp cl, '0' je .skip_prefix_0		                                ; 继续判断下一位字符是否为字符0(不是数字0)dec edi			                                        ;edi在上面的inc操作中指向了下一个字符,若当前字符不为'0',要恢复edi指向当前字符		       jmp .put_each_num.full0:mov cl,'0'			                                    ; 输入的数字为全0时,则只打印0
.put_each_num:push ecx			                                        ; 此时cl中为可打印的字符call put_charadd esp, 4inc edi			                                        ; 使edi指向下一个字符mov cl, [put_int_buffer+edi]	                            ; 获取下一个字符到cl寄存器cmp edi,8                                                ;当edi=8时,虽然不会去打印,但是实际上已经越界访问缓冲区了jl .put_each_numpopadret

编译脚本

#编译mbr
nasm ./boot/mbr.S -o ./build/mbr -I ./boot/include/ 
dd if=./build/mbr of=~/bochs/hd60M.img bs=512 count=1 conv=notrunc#编译loader
nasm ./boot/loader.S -o ./build/loader -I ./boot/include/
dd if=./build/loader of=~/bochs/hd60M.img bs=512 count=4 seek=2 conv=notrunc#编译print函数
nasm ./lib/kernel/print.S -f elf -o ./build/print.o # 编译kernel
nasm ./kernel/kernel.S -f elf -o ./build/kernel.o #sudo apt-get install libc6-dev-i386#编译main函数
gcc-4.4 ./kernel/main.c -o build/main.o -c  -fno-builtin -m32 -I ./lib/kernel/ -I ./lib/ -I ./kernel/# 编译interrupt
gcc-4.4 ./kernel/interrupt.c -o ./build/interrupt.o -c -fno-builtin -m32 -I ./lib/kernel/ -I ./lib/ -I ./kernel/ # 编译init
gcc-4.4 ./kernel/init.c -o ./build/init.o -c -fno-builtin -m32 -I ./lib/kernel/ -I ./lib/ -I ./kernel/ #链接成内核
ld build/main.o  build/init.o build/interrupt.o build/kernel.o build/print.o -o build/kernel.bin  -m elf_i386 -Ttext 0xc0001500 -e main #将内核文件写入磁盘,loader程序会将其加载到内存运行
dd if=./build/kernel.bin of=~/bochs/hd60M.img bs=512 count=200 conv=notrunc seek=9#rm -rf build/*

实现结果

./bin/bochs -f boot.disk

这篇关于《操作系统真象还原》第七章——改进中断的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1075172

相关文章

【Linux进阶】UNIX体系结构分解——操作系统,内核,shell

1.什么是操作系统? 从严格意义上说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境。我们通常将这种软件称为内核(kerel),因为它相对较小,而且位于环境的核心。  从广义上说,操作系统包括了内核和一些其他软件,这些软件使得计算机能够发挥作用,并使计算机具有自己的特生。这里所说的其他软件包括系统实用程序(system utility)、应用程序、shell以及公用函数库等

【操作系统】信号Signal超详解|捕捉函数

🔥博客主页: 我要成为C++领域大神🎥系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞👍收藏⭐评论✍️ 本博客致力于知识分享,与更多的人进行学习交流 ​ 如何触发信号 信号是Linux下的经典技术,一般操作系统利用信号杀死违规进程,典型进程干预手段,信号除了杀死进程外也可以挂起进程 kill -l 查看系统支持的信号

(超详细)YOLOV7改进-Soft-NMS(支持多种IoU变种选择)

1.在until/general.py文件最后加上下面代码 2.在general.py里面找到这代码,修改这两个地方 3.之后直接运行即可

YOLOv8改进 | SPPF | 具有多尺度带孔卷积层的ASPP【CVPR2018】

💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡 专栏目录 :《YOLOv8改进有效涨点》专栏介绍 & 专栏目录 | 目前已有40+篇内容,内含各种Head检测头、损失函数Loss、Backbone、Neck、NMS等创新点改进——点击即可跳转 Atrous Spatial Pyramid Pooling (ASPP) 是一种在深度学习框架中用于语义分割的网络结构,它旨

操作系统实训复习笔记(1)

目录 Linux vi/vim编辑器(简单) (1)vi/vim基本用法。 (2)vi/vim基础操作。 进程基础操作(简单) (1)fork()函数。 写文件系统函数(中等) ​编辑 (1)C语言读取文件。 (2)C语言写入文件。 1、write()函数。  读文件系统函数(简单) (1)read()函数。 作者本人的操作系统实训复习笔记 Linux

SQL Server中,用Restore DataBase把数据库还原到指定的路径

restore database 数据库名 from disk='备份文件路径' with move '数据库文件名' to '数据库文件放置路径', move '日志文件名' to '日志文件存放置路径' Go 如: restore database EaseWe from disk='H:\EaseWe.bak' with move 'Ease

HarmonyOS NEXT:华为开启全新操作系统时代

在全球科技浪潮的汹涌澎湃中,华为再次以创新者的姿态,引领了一场关于操作系统的革命。HarmonyOS NEXT,这一由华为倾力打造的分布式操作系统,不仅是对现有技术的一次大胆突破,更是对未来智能生活的一次深邃展望。 HarmonyOS NEXT并非简单的迭代升级,而是在华为多年技术积淀的基础上,对操作系统的一次彻底重构。它采用微内核架构,摒弃了传统的宏内核模式,实现了模块化和组件化的设计理念

Linux操作系统段式存储管理、 段页式存储管理

1、段式存储管理 1.1分段 进程的地址空间:按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名(在低级语言中,程序员使用段名来编程),每段从0开始编址。内存分配规则:以段为单位进行分配,每个段在内存中占连续空间,但各段之间可以不相邻。 分段系统的逻辑地址结构由段号(段名)和段内地址(段内偏移量)所组成。 1.2段表 每一个程序设置一个段表,放在内存,属于进程的现场信息

【智能优化算法改进策略之局部搜索算子(五)—自适应Rosenbrock坐标轮换法】

1、原理介绍 作为一种有效的直接搜索技术,Rosenbrock坐标轮换法[1,2]是根据Rosenbrock著名的“香蕉函数”的特点量身定制的,该函数的最小值位于曲线狭窄的山谷中。此外,该方法是一种典型的基于自适应搜索方向集的无导数局部搜索技术。此法于1960年由Rosenbrock提出,它与Hooke-Jeeves模式搜索法有些类似,但比模式搜索更为有效。每次迭代运算分为两部分[3]: 1)

智能优化算法改进策略之局部搜索算子(六)--进化梯度搜索

1、原理介绍     进化梯度搜索(Evolutionary Gradient Search, EGS)[1]是兼顾进化计算与梯度搜索的一种混合算法,具有较强的局部搜索能力。在每次迭代过程中,EGS方法首先用受进化启发的形式估计梯度方向,然后以最陡下降的方式执行实际的迭代步骤,其中还包括步长的自适应,这一过程的总体方案如下图所示:     文献[1]