第十章-输入输出系统

news2024/9/22 23:20:31

Ⅰ.锁

本质是互斥操作

原因:针对公共资源访问时,临界区若不加以互斥限制,可能导致执行过程中突然的中断导致出现异常。

1.互斥过程

设定互斥量M为二值信号量,0/1,P-,V+,现有两个进程A、B共同访问公共资源R,则有

1.线程A访问R,P-,M=0

2.线程B访问R,P-,M<0,阻塞线程B,P+

3.线程A访问R结束,V+,唤醒线程B

4.线程B开始执行

阻塞功能 :阻塞是线程主动的行为 。己阻塞的线程是由别人来唤醒的,通常是锁的持有者。不让线程在就绪队列中出现就行了,这样线程便没有机会运行。

线程自主阻塞之后,保存当前的寄存器,栈信息,当唤醒后,并不会从头开始执行,而是从线程阻塞的部分开始重新执行,并且时间片依旧是阻塞前的数值。

(1)实现线程阻塞和唤醒
/*	阻塞过程:
* 1.关中断
* 2.更改当前线程状态为阻塞态
* 3.调用schedule(),从就绪队列中取出下一个就绪线程列执行
* 4.开中断
*/
void thread_blocked(enum task_status state){
    // 原子操作,必须关中断
    enum intr_status old_status = intr_distable();
    // 只有是传入下面三种状态才可以被阻塞
    ASSERT((state == TASK_BLOCKED) || (state == TASK_HANGING) || (state == TASK_WAITING));
    struct task_struct * cur_thread = running_thread();
    cur_thread->status = state;
    schedule();
    // list_pop(&thread_ready_list, &cur_thread->general_tag);
    intr_set_status(old_status);
}
/* 唤醒线程
* 1.关中断
* 2.判断当前线程是否在就绪队列中
* 3.不在,则添加;在,则PANIC
* 4.当前线程加入就绪队列
* 5.修改状态为TASK_READY
* 6.开中断
*/
// 被阻塞的进程由于没有执行,因此不可以调度schedule()函数
void thread_unblocked(struct task_struct* pthread){
    enum intr_status old_status = intr_disable();
    ASSERT((pthread->status == TASK_BLOCKED) || (pthread->status == TASK_HANGING) || (pthread->status == TASK_WAITING));
    if(pthread->status != TASK_READY){
        ASSERT(!elem_find(&thread_ready_list, &pthread->general_tag));
        if(elem_find(&thread_ready_list, &pthread->general_tag)){
            PANIC("thread_unblock:blocked thread in ready_list");
        }
        list_push(&thread_ready_list, &pthread->general_tag);
        pthread->status = TASK_RUNNING;
    }
    intr_set_status(old_status);
}
(2)实现PV操作
/* P操作,存在线程阻塞
* 1.关中断
* 2.判断当前semophore==0
* 3.为0,循环等待,并将当前线程加入信号量等待队列,并阻塞当前线程
* 4.不为0,semophore-1
* 5.开中断
*/
void sema_down(struct semaphore* psema) {
    emum intr_status old_status = intr_disable();
    while(psema->value == 0){
        ASSERT(!elem_find(&psema->waiters, &running_thread()->general_tag));
        if(elem_find(&psema->waiters, &running_thread()->general_tag)){
            PANIC("sema_down:P op failed");            
        }
        list_append(&psema->waiters, &running_thread()->general_tag);
        thread_blocked(TASK_BLOCKED);
    }
    psema->value--;
    ASSERT(psema->value == 0);
    intr_set_status(old_status);    
}
/* V操作,存在线程唤醒
* 1.关中断
* 2.判断当前信号量等待队列不为空,根据elem2entry找到队头线程的PCB地址,并唤醒
* 3.为空,semophore++
* 4.开中断
*/
void sema_up(struct semaphore* psema) {
    emum intr_status old_status = intr_disable();
    ASSERT(psema->value == 0);
    if(!list_empty(&psema->waiters)){
        struct task_struct* wait_thread = elem2entry(struct task_struct, general_tag, list_pop(&psema->waiters));
        thread_unblocked(wait_thread);
    }
    psema->value++;
    ASSERT(psema->value == 1);
    intr_set_status(old_status);    
}
(3)实现获得锁、释放锁操作
/* 获得锁
* 1.先判断自己是不是锁的持有者
* 2.不是,P操作,持有锁,持有次数+1
* 3.反之,持有次数+1
*/
void lock_acquire (struct lock* plock) {
   /*	排除曾经自己已经持有锁但还未将其释放的情况	*/
    if(plock->holder != running_thread()){
        sema_down(&plock->semaphore);
        plock->holder = running_thread();
        ASSERT(plock->holder_repeat_nr == 0);
        plock->holder_repeat_nr = 1;
    }else{
        plock->holder_repeat_nr++;
    }
}
/* 释放锁
* 1.ASSERT()判断是否拥有锁
* 2.判断持有锁的次数,防止进程需要多次访问资源。
* 3.清空锁
* 3.V操作
*/
void lock_release (struct lock* plock) {
    ASSERT(plock->holder == running_thread());

    if(plock->holder_repeat_nr > 1){
        plock->holder_repeat_nr--;
        return;
    }
    ASSERT(plock->holder_repeat_nr == 1);
    plock->holder = NULL;
    plock->holder_repeat_nr = 0;
    sema_up(&plock->semaphore);
}

Ⅱ.从键盘获取输入

键的扫描码:一个键的状态要么是按下,要么是弹起,因此一个键便有两个编码,按键被按下时的编码叫通码,也就是表示按键上的触点接通了内部电路,使硬件产生了一个码,故通码也称为 makecode。按键在被按住不松手时会持续产生相同的码,直到按键被松开时才终止,因此按键被松开弹起时产生的编码叫断码,也就是电路被断开了,不再持续产生码了,故断码也称为 breakcode。一个键的扫描码是由通码和断码组成的。

键盘编码器:键盘是个独立的设备,在它内部有个叫作键盘编码器的芯片,通常是 Intel 8048 或兼容芯片,它的作用是:每当键盘上发生按键操作,它就向键盘控制器报告哪个键被按下,以及键的扫描码。

键盘控制器:在主机内部的主板上,通常是 Intel 8042 或兼容芯片,接收来自键盘编码器的按键信息,将其解码后保存,然后向中断代理发中断,之后处理器执行相应的中断处理程序读入 8042 处理保存过的按键信息。

1.从键盘读取输入的过程

• 扫描码有 3 套,现在一般键盘中的 8048 芯片支持的是第二套扫描码 。 因此每当有击键发生时, 8048发给 8042 的都是第二套键盘扫描码。
• 8042 为了兼容性,将接收到的第二套键盘扫描码转换成第一套扫描码。 8042 是按字节来处理的,每处理一个字节的扫描码后,将其存储到自己的输出缓冲区寄存器 。
• 然后向中断代理 8259A 发中断信号,这样我们的键盘中断处理程序通过读取 8042 的输出缓冲区寄存器,会获得第一套键盘扫描码。

2.键盘扫描码

根据键盘的更新迭代,出现了3种常用的键盘扫描码,为了兼容第一套键盘扫描码对应的中断处理程序,不管键盘用的是何种键盘扫描码,当键盘将扫描码发送到 8042 后,都由 8042 转换成第一套扫描码。

在这里插入图片描述

8042介于8048与处理器中间,担任中间人的职责,完成处理器对8048的设置,以及8048的扫描码输入给处理器。因此8042作为输入输出缓存区,包括输入、输出缓冲寄存器,以及状态、控制寄存器。

在这里插入图片描述

在这里插入图片描述

Ⅲ.键盘驱动程序

对于键盘驱动程序而言,每次都需要读取输出寄存器,取出键盘的扫描码并完成转为ASCII码的工作。

键盘驱动程序,初始化(注册键盘中断处理程序)

#define KEY_BUF_PORT 0x60  // 键盘 buffer 寄存器端口号为 0x60
/* 键盘中断处理程序 */
static void intr_keyboard_handler(void) {
/* 必须要读取输出缓冲区寄存器,否则8042不再继续响应键盘中断 */
   uint8_t scancode = inb(KBD_BUF_PORT);
   put_int(scancode);
   return;
}

/* 键盘初始化,注册键盘中断处理程序 */
void keyboard_init() {
   put_str("keyboard init start\n");
   register_handler(0x21, intr_keyboard_handler);
   put_str("keyboard init done\n");
}
1.C语言中有3中转义字符

(1) 一般转义字符,‘\+单个字母’ 的形式。
(2)八进制转义字符,‘\0+三位八进制数字表示的 ASCII 码’ 的形式 。
(3)十六进制转义字符,‘\x+两位十六进制数字表示的 ASCII 码’ 的形式 。

2.处理扫描码

键盘驱动程序需要完成 键盘扫描码->按键的ASCII码 的映射过程

(1)设计思路:
  1. 如果是一些用于操作方面的控制键,简称操作控制键,如<shift>、<ctrl>、<caps lock>,它通常是组合键,需要与其他键一起考虑,然后做出具体的行为展现,在键盘驱动中完成处理。
  2. 如果是一些用于字符方面的键,无论是可见字符,或是字符方面的控制键(简称字符控制键),如<backspace>,统统交给字符处理程序完成。

对于第一阶段,它与字符无直接的关系,因此咱们就在键盘驱动中处理。
对于第二阶段,咱们得知道用户按下的是什么宇符,不能把操作控制键当成字符传给字符处理程序,比如把 shift键的扫描码传给 put_char,这不就乱了吗?因此,咱们得把按键的扫描码转换成对应的字符,也就是将通码转换为字符的 ASCII 码,这就是前面所说的源到目标的映射关系。

**简言之:需要区分什么按键是需要显示的,什么按键是负责控制的。**这就需要建立扫描码和ASCII码的映射表,对键盘输入加以判定。

(2)建立键盘字符与通码映射关系

keymap[][0]表示未与shift组合的按键值,keymap[][1]表示与shift组合的按键值

/* 以通码make_code为索引的二维数组 */
static char keymap[][2] = {
/* 扫描码   未与shift组合  与shift组合*/
/* ---------------------------------- */
/* 0x00 */	{0,	0},		
/* 0x01 */	{esc,	esc},		
/* 0x02 */	{'1',	'!'},		
/* 0x03 */	{'2',	'@'},		
/* 0x04 */	{'3',	'#'},		
/* 0x05 */	{'4',	'$'},		
/* 0x06 */	{'5',	'%'},		
/* 0x07 */	{'6',	'^'},		
/* 0x08 */	{'7',	'&'},		
/* 0x09 */	{'8',	'*'},		
/* 0x0A */	{'9',	'('},		
/* 0x0B */	{'0',	')'},		
/* 0x0C */	{'-',	'_'},		
/* 0x0D */	{'=',	'+'},		
/* 0x0E */	{backspace, backspace},	
/* 0x0F */	{tab,	tab},		
/* 0x10 */	{'q',	'Q'},		
/* 0x11 */	{'w',	'W'},		
/* 0x12 */	{'e',	'E'},		
/* 0x13 */	{'r',	'R'},		
/* 0x14 */	{'t',	'T'},		
/* 0x15 */	{'y',	'Y'},		
/* 0x16 */	{'u',	'U'},		
/* 0x17 */	{'i',	'I'},		
/* 0x18 */	{'o',	'O'},		
/* 0x19 */	{'p',	'P'},		
/* 0x1A */	{'[',	'{'},		
/* 0x1B */	{']',	'}'},		
/* 0x1C */	{enter,  enter},
/* 0x1D */	{ctrl_l_char, ctrl_l_char},
/* 0x1E */	{'a',	'A'},		
/* 0x1F */	{'s',	'S'},		
/* 0x20 */	{'d',	'D'},		
/* 0x21 */	{'f',	'F'},		
/* 0x22 */	{'g',	'G'},		
/* 0x23 */	{'h',	'H'},		
/* 0x24 */	{'j',	'J'},		
/* 0x25 */	{'k',	'K'},		
/* 0x26 */	{'l',	'L'},		
/* 0x27 */	{';',	':'},		
/* 0x28 */	{'\'',	'"'},		
/* 0x29 */	{'`',	'~'},		
/* 0x2A */	{shift_l_char, shift_l_char},	
/* 0x2B */	{'\\',	'|'},		
/* 0x2C */	{'z',	'Z'},		
/* 0x2D */	{'x',	'X'},		
/* 0x2E */	{'c',	'C'},		
/* 0x2F */	{'v',	'V'},		
/* 0x30 */	{'b',	'B'},		
/* 0x31 */	{'n',	'N'},		
/* 0x32 */	{'m',	'M'},		
/* 0x33 */	{',',	'<'},		
/* 0x34 */	{'.',	'>'},		
/* 0x35 */	{'/',	'?'},
/* 0x36	*/	{shift_r_char, shift_r_char},	
/* 0x37 */	{'*',	'*'},    	
/* 0x38 */	{alt_l_char, alt_l_char},
/* 0x39 */	{' ',	' '},		
/* 0x3A */	{caps_lock_char, caps_lock_char}
/*其它按键暂不处理*/
};
(3)更新键盘驱动程序
(3.1)读取扫描码
static void intr_keyboard_handler(void) {
    ……
   bool break_code;
   uint16_t scancode = inb(KBD_BUF_PORT);

/* 若扫描码是e0开头的,表示此键的按下将产生多字节的扫描码,如shift、alt、ctrl、caps_lock等扫描码为2字节
 * 所以马上结束此次中断处理函数,等待下一个扫描码进来*/ 
   if (scancode == 0xe0) { 
      ext_scancode = true;    // 打开e0标记
      return;
   }

/* 如果上次是以0xe0开头,将扫描码合并 */
   if (ext_scancode) {
      scancode = ((0xe000) | scancode);
      ext_scancode = false;   // 关闭e0标记
   }   

   break_code = ((scancode & 0x0080) != 0);   // 获取break_code
    ……
}
(3.2)需要判断当前键盘是处于断码/通码

通码的扫描码scancode第8位为0,断码为1。

若为断码,需要将操作方面的控制键状态改为false,如shift、alt。将他们的断码的第8位改为0,作为通码访问keyb_map获取具体是哪个控制键,然后更改状态即可。

/*---------------续上----------------*/

   if (break_code) {   // 若是断码break_code(按键弹起时产生的扫描码)
   /* 由于ctrl_r 和alt_r的make_code和break_code都是两字节,
   所以可用下面的方法取make_code,多字节的扫描码暂不处理 */
      uint16_t make_code = (scancode &= 0xff7f);   // 得到其make_code(按键按下时产生的扫描码)
   /* 若是任意以下三个键弹起了,将状态置为false */
      if (make_code == ctrl_l_make || make_code == ctrl_r_make) {
	 ctrl_status = false;
      } else if (make_code == shift_l_make || make_code == shift_r_make) {
	 shift_status = false;
      } else if (make_code == alt_l_make || make_code == alt_r_make) {
	 alt_status = false;
      } /* 由于caps_lock不是弹起后关闭,所以需要单独处理 */
      return;   // 直接返回结束此次中断处理程序
   } 

若为通码,分为操作键和字符键两类,先要读取操作键,如shift、ctrl、alt、caps_lock,然后修改操作键的状态,根据操作键的状态确定映射的字符。

/*---------------续上----------------*/

   /* 若为通码,只处理数组中定义的键以及alt_right和ctrl键,全是make_code */
   else if ((scancode > 0x00 && scancode < 0x3b) ||
            (scancode == alt_r_make) ||
            (scancode == ctrl_r_make))
   {
      bool shift = false; // 判断是否与shift组合,用来在一维数组中索引对应的字符
      if ((scancode < 0x0e) || (scancode == 0x29) ||
          (scancode == 0x1a) || (scancode == 0x1b) ||
          (scancode == 0x2b) || (scancode == 0x27) ||
          (scancode == 0x28) || (scancode == 0x33) ||
          (scancode == 0x34) || (scancode == 0x35))
      {
         /****** 代表两个字母的键 ********
             0x0e 数字'0'~'9',字符'-',字符'='
             0x29 字符'`'
             0x1a 字符'['
             0x1b 字符']'
             0x2b 字符'\\'
             0x27 字符';'
             0x28 字符'\''
             0x33 字符','
             0x34 字符'.'
             0x35 字符'/'
         *******************************/
         if (shift_down_last)
         { // 如果同时按下了shift键
            shift = true;
         }
      }
      else
      { // 默认为字母键
         if (shift_down_last && caps_lock_last)
         { // 如果shift和capslock同时按下
            shift = false;
         }
         else if (shift_down_last || caps_lock_last)
         { // 如果shift和capslock任意被按下
            shift = true;
         }
         else
         {
            shift = false;
         }
      }

      uint8_t index = (scancode &= 0x00ff); // 将扫描码的高字节置0,主要是针对高字节是e0的扫描码.
      char cur_char = keymap[index][shift]; // 在数组中找到对应的字符

      /* 只处理ascii码不为0的键 */
      if (cur_char)
      {
         put_char(cur_char);
         return;
      }

      /* 记录本次是否按下了下面几类控制键之一,供下次键入时判断组合键 */
      if (scancode == ctrl_l_make || scancode == ctrl_r_make)
      {
         ctrl_status = true;
      }
      else if (scancode == shift_l_make || scancode == shift_r_make)
      {
         shift_status = true;
      }
      else if (scancode == alt_l_make || scancode == alt_r_make)
      {
         alt_status = true;
      }
      else if (scancode == caps_lock_make)
      {
         /* 不管之前是否有按下caps_lock键,当再次按下时则状态取反,
          * 即:已经开启时,再按下同样的键是关闭。关闭时按下表示开启。*/
         caps_lock_status = !caps_lock_status;
      }
   }
   else
   {
      put_str("unknown key\n");
   }

Ⅳ.环形输入缓冲区

构建缓冲区保存键盘扫描码转换的字符,每次从缓冲区取出字符,完成打印。

利用生产者消费者模式构建环形队列,实现字符的取出和保存。主要完成缓冲区存储状态、添加1字节、删除1字节等操作。

1.环形队列结构体
/*  环形队列    */
struct ioqueue {
    struct lock lock;
    struct task_struct *producer;
    struct task_struct *consumer;
    // 缓冲区大小
    char buf[bufsize];
    // 队首,数据往队首处写入
    int32_t head;
    // 队尾,数据从队尾处读出
    int32_t tail;
};
2.缓冲区满
/*  返回 pos 在缓冲区中的下一个位置值  */
static int32_t next_pos (int32_t pos) {
    return (pos+1)%bufsize;
}
/*  采用头插法加入元素,判断队列是否已满  */
bool ioq_full(struct ioqueue* ioq) {
    ASSERT(intr_get_status() == INTR_OFF);
    return next_pos(ioq->head) == ioq->tail;
}
3.缓冲区为空
/*  采用头插法加入元素,判断队列是否为空  */
bool ioq_empty(struct ioqueue* ioq) {
    ASSERT(intr_get_status() == INTR_OFF);
    return ioq->head == ioq->tail;
}
4.缓冲区为空时,等待
/*      当前生产者或消费者在此缓冲区上等待      */
static void ioq_wait(struct task_struct ** waiter) {
    ASSERT((waiter != NULL) && (&waiter == NULL));
    *waiter = running_thread();
    thread_blocked(TASK_BLOCKED);
}
5.缓冲区不为空,唤醒waiterr
/*  唤醒 waiter    */
static void wakeup(struct task_struct** waiter) {
    ASSERT(&waiter != NULL);
    thread_unblocked(*waiterr);
    *waiterr = NULL;
}
6.缓冲区添加一字节
/*  生产者往 ioq 队列中写入一个字符 byte    */
void ioq_putchar(struct ioqueue* ioq, char byte) {
    ASSERT(intr_get_intr() == INTR_OFF);
    while(ioq_full(ioq)){
        lock_acquire(&ioq->lock);
        ioq_wait(&ioq);
        lock_release(&ioq->lock);
    }
    ioq->buf[ioq->head] = byte;
    ioq->head = next_pos(ioq->head);

    if(ioq->consumer != NULL){
        wakeup(&ioq->consumer);
    }
}
  1. 判断缓冲区是否满了
  2. 满了则请求锁,并wait,阻塞当前线程,释放锁
  3. 反之,头插法,在队列头插入新元素
  4. 唤醒消费者
7.缓冲区取出一字节
/*  消费者从 ioq 队列中获取一个字符   */
char ioq_getchar(struct ioqueue* ioq) {
    ASSERT(intr_get_status() == INTR_OFF);
    while(ioq_empty(ioq)){
        lock_acquire(&ioq->lock);
        ioq_wait(&ioq);
        lock_release(&ioq->lock);
    }

    char byte = ioq->buf[ioq->tail];
    ioq->tail = next_pos(ioq->tail);
    if(ioq->producer != NULL){
        wakeup(&ioq->consumer);
    }
    return byte;
}
  1. 判断缓冲区是否为空
  2. 为空则请求锁,并wait,阻塞当前线程,释放锁
  3. 反之,从队尾取出新元素
  4. 唤醒生产者

over

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1080092.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大数据flink篇之三-flink运行环境安装(一)单机Standalone安装

一、安装包下载地址 https://archive.apache.org/dist/flink/flink-1.15.0/ 二、安装配置流程 前提基础&#xff1a;Centos环境&#xff08;建议7以上&#xff09; 安装命令&#xff1a; 解压&#xff1a;tar -zxvf flink-xxxx.tar.gz 修改配置conf/flink-conf.yaml&#xff1…

最新AI创作系统源码ChatGPT网站源码/支持Midjourney,AI绘画/支持OpenAI GPT全模型+国内AI全模型

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如…

RxJava介绍及基本原理

随着互联网的迅猛发展&#xff0c;Java已成为最广泛应用于后端开发的语言之一。而在处理异步操作和事件驱动编程方面&#xff0c;传统的Java多线程并不总是最佳选择。这时候&#xff0c;RxJava作为一个基于观察者模式、函数式编程和响应式编程理念的库&#xff0c;为我们提供了…

【Nuget】程序包源

程序包源地址(部分) Azure 中国区的官方 NuGet 程序包源地址 https://nuget.cdn.azure.cn/v3/index.json 官方 NuGet 程序包源地址 V2 https://www.nuget.org/api/v2 官方 NuGet 程序包源地址 V3 https://api.nuget.org/v3/index.json MyGet 上 Eto.Forms 框架的程序包源地址 h…

杨冰:分布式数据库助力企业数实融合,跨越数字化转型深水区

近日&#xff0c;2023 inclusion外滩大会在上海黄浦世博园区举办。由赛迪顾问与 OceanBase 联合主办的外滩大会“分布式数据库助力数实融合”见解论坛圆满落幕。 会上&#xff0c;OceanBase CEO 杨冰发表了《分布式数据库助力企业数实融合&#xff0c;跨越数字化转型深水区》的…

一个完整的初学者指南Django-part1

源自&#xff1a;https://simpleisbetterthancomplex.com/series/2017/09/04/a-complete-beginners-guide-to-django-part-1.html 一个完整的初学者指南Django - 第1部分 介绍 今天我将开始一个关于 Django 基础知识的新系列教程。这是一个完整的 Django 初学者指南。材料分为七…

mysql面试题38:count(1)、count(*) 与 count(列名) 的区别

该文章专注于面试&#xff0c;面试只要回答关键点即可&#xff0c;不需要对框架有非常深入的回答&#xff0c;如果你想应付面试&#xff0c;是足够了&#xff0c;抓住关键点 面试官&#xff1a; count(1)、count(*) 与 count(列名) 的区别 当使用COUNT函数进行数据统计时&…

echarts折线图(其他图也是一样)设置tooltip自动滚动

按顺序自动滚动效果 <div class"leftComp-charts" id"chartsBox"></div>chartsData: {roadNorm: [],time: []},eChartsTimer: nullinitChartsBox() {this.option {tooltip: {trigger: "axis",axisPointer: {// 方法一type: "s…

2023年中国稻谷加工机械分类、市场规模及发展前景分析[图]

稻谷加工机械设备主要包括砻谷机、碾米机、抛光机、碎米机和砻糠机&#xff1b;通过物理和机械方式将稻谷加工成可供人们食用的大米&#xff0c;同时还可以提取出一些有价值的副产品&#xff0c;如砻糠可以用作饲料。 稻谷加工机械制造行业分类 资料来源&#xff1a;共研产业咨…

设计模式-相关内容

文章目录 一、设计模式概述二、UML图1.类的表示方法2.类与类之间关系的表示方法(1)关联关系(2)聚合关系(3)组合关系(4)依赖关系(5)继承关系(6)实现关系 三、软件设计原则1.开闭原则2.里氏代换原则3.依赖倒转原则4.接口隔离原则5.合成复用原则6.迪米特法则 一、设计模式概述 创…

集群分发脚本xysnc

一、scp&#xff08;secure copy&#xff09; 安全拷贝 1.定义 scp&#xff08;Secure Copy&#xff09;是一个用于在不同计算机之间安全地复制文件和目录的命令行工具。它使用 SSH 协议进行连接和文件传输&#xff0c;提供了加密和身份验证机制&#xff0c;确保数据传输的安…

Android 项目增加 res配置

main.res.srcDirs "src/main/res_test" build->android->sourceSets

从裸机启动开始运行一个C++程序(七)

前序文章请看&#xff1a; 从裸机启动开始运行一个C程序&#xff08;六&#xff09; 从裸机启动开始运行一个C程序&#xff08;五&#xff09; 从裸机启动开始运行一个C程序&#xff08;四&#xff09; 从裸机启动开始运行一个C程序&#xff08;三&#xff09; 从裸机启动开始运…

Mall脚手架总结(四) —— SpringBoot整合RabbitMQ实现超时订单处理

前言 在电商项目中&#xff0c;订单因为某种特殊情况被取消或者超时未支付都是比较常规的用户行为&#xff0c;而实现该功能我们就要借助消息中间件来为我们维护这么一个消息队列。在mall脚手架中选择了RabbitMQ消息中间件&#xff0c;接下来荔枝就会根据功能需求来梳理一下超时…

SRE实战:如何低成本推进风险治理?稳定性与架构优化的3个策略

一分钟精华速览 SRE 团队每天面临着不可控的各类风险和重复发生的琐事&#xff0c;故障时疲于奔命忙于救火。作为技术管理者&#xff0c;你一直担心这些琐事会像滚雪球一样&#xff0c;越来越多地、无止尽地消耗你的团队&#xff0c;进而思考如何系统性地枚举、掌控这些风险&a…

ctf中ping命令执行绕过

相关wp参考&#xff1a;CTF中的命令执行绕过方式 - 知乎 CTFping命令绕过及符号用法_ctf ping-CSDN博客 在用linux命令时候,我们可以 一行执行多条命令 或者 有条件的执行下一条命令 linux命令中一些符号的用法 1. “;”分号用法 方式&#xff1a;command1 ; command…

【ccf-csp题解】第7次csp认证-第三题-路径解析超详细题解-字符串模拟

本题思路来源于acwing ccfcsp认证课 题目描述 思路分析 首先&#xff0c;为了处理路径中的反斜杠符号&#xff0c;我们可以实现一个get函数&#xff0c;把一个路径中每一对反斜杠之间的内容存到vector<string>中&#xff0c;如果有连续的多个反斜杠则只看成一个 举个例…

“.NET视频总结:认识框架的结构和组件,掌握开发工具的奥妙“一

目录 第一单元&#xff1a;二十一世纪程序执行 背景: 总结&#xff1a; 第二单元:对象导向与类别设计 背景: 总结&#xff1a; 第三单元&#xff1a;使用类别与基底类别库 总结: 第四单元:Windows开发程序 背景: 总结: 第五单元:防护式程序设计 背景: 总结: 第六…

数据库中的DECODE函数,SIGN函数

oracle中的if(),oracle中if/else的三种实现方式详解_电竞GO的博客-CSDN博客 DECODE(CONCAT(b.AZZ231,%),%,,CONCAT(b.AZZ231,%)) czwcl,

Acrel-6000电气火灾监控系统应用

安科瑞 崔丽洁 摘要 建筑电气火灾在建筑物火灾中占较大的比例&#xff0c;起火原因也很多&#xff0c;包括短路、过热、漏电、雷击和电气等故障&#xff0c;火灾危害也较大。因此&#xff0c;各种原因引起的火灾都应得到有效控制。目前&#xff0c;短路、过热、雷击等保护措施…