深圳市网站首页/百度学术官网入口
前言:在上一期的线程章节中,我们的线程输出貌似有大问题,今天我们便要来学习同步锁来解决这个问题,同时再次基础上拿下键盘输入,实现操作系统的输入和输出。从今天开始我们的操作系统不在是一块“看板”了!!!
一,上期线程输出的不足
其实13 Day的线程输出并不完美,有很大的问题。但是这个问题经过我多天的观察其实有两种。
1,我们会发现有的线程输出的内容,并没有输出完成,而是被很大的一片空白取代
2,触发global exception 异常
至于为什么会导致这样子,相信聪明的小伙伴已经知道了,如果有学过并发编程相关的知识肯定会对该情况十分熟悉。
首先我们知道,屏幕输出的操作由多个指令组成,且需要操作显存,那么就会出现以下问题
- 显存作为公共空间,也就是临界区(不过多介绍),所有的线程进行输出都需要操作这块空间,就会导致线程安全问题
- 屏幕输出操作有多个指令组成,这就说明屏幕输出并不是原子性的,也就是说线程的切换可能导致另一个线程屏幕输出只执行了一半,就被别人踹下台去了,就会出现原本要输出Hello,可是只输出了Hell
那我们要怎么解决这种问题呢?没错就是关闭中断,这样操作就变成原子性的,线程无阻碍的往下执行。我们可以在原本main函数的两个线程方法while的上下加上一个 关中断与开中断,当大家再次进行输出就会发现输出正常了。
但这大家仔细想想整个while模块里面有些操作并不需要都是原子性的,我们只有某些关键操作是原子性,把整个线程的函数都进行关中断那不就变成以前的串行操作了吗?这就涉及到一个关键性问题,关中断与开中断之间的粒度,也就是JUC中经常讨论到的一点:锁的粒度,他锁定的范围越小,那么线程切换工作的效率就越高。在这里我们显然只要对输出函数进行加锁处理,那么如何让一个方法加锁请接着往下看
① 信号量
有学过操作系统大学课程的同学肯定知道信号量,其中P,V代表信号量的操作,这是一个荷兰语。P代表减少,V代表上升。那么对应锁的话就是如下几个操作:
- up:
(1) 信号量的值+1
(2) 唤醒在此信号量上等待的线程
- down:
(1)判断信号量是否>1
(2)信号量大于0,信号量-1
(3)信号量等于0,线程阻塞,在此信号量上等待
② 同步锁的实现
知道以上操作之后,我们便可以来实现一个锁了,在此之前我们还需要完善线程的两个操作:阻塞与唤醒
thread/thread.c
void thread_block(enum task_status stat) {ASSERT(((stat == TASK_BLOCKED) || (stat == TASK_WAITING) || (stat == TASK_HANGINH)));enum intr_status old_status = intr_disable();struct task_struct* cur_thread = running_thread();cur_thread->status = stat;schedule();intr_set_status(old_status);
}void thread_unblock(struct task_struct* pthread) {enum intr_status old_status = intr_disable();ASSERT((pthread->status == TASK_BLOCKED) || (pthread->status == TASK_WAITING) || (pthread->status == TASK_HANGINH));if (pthread->status != TASK_READY) {ASSERT(!find(&thread_ready_list, &pthread->general_tag));if (find(&thread_ready_list, &pthread->general_tag)) {PANIC("thread_unblock");}push(&thread_ready_list, &pthread->general_tag);pthread->status = TASK_READY;}intr_set_status(old_status);
}
接下来我们来实现同步锁
/thread/sync.h
#ifndef _THREAD_SYNC_H
#define _THREAD_SYNC_H
#include "list.h"
#include "stdint.h"
#include "thread.h"struct semaphore {uint8_t value; //信号量值struct list waiters; //等待队列
};struct lock {struct task_struct* holder; //持有锁的线程struct semaphore semaphore;uint32_t holder_repeat_nr; //锁重入次数
};
void lock_init(struct lock* plock);
void try_lock(struct lock* plock);
void try_release(struct lock* plock);
#endif // ! _THREAD_SYNC_H
/thread/sync.c
#include "sync.h"
#include "stdint.h"
#include "list.h"
#include "thread.h"
#include "interrupt.h"
#include "debug.h"
void sema_init(struct semaphore* psema, uint8_t value) {psema->value = value;list_init(&psema->waiters);
}void lock_init(struct lock* plock) {plock->holder = NULL;plock->holder_repeat_nr = 0;sema_init(&plock->semaphore,1);
}void sema_down(struct semaphore* psema) {enum intr_status old_status = intr_disable();while (psema->value == 0) {//检测当前线程是否在等待队列中,在的话则报错if (find(&psema->waiters, &running_thread()->general_tag)) {PANIC("sema_down: thread blocked has been in waiter");}append(&psema->waiters, &running_thread()->general_tag);thread_block(TASK_BLOCKED);}--psema->value;ASSERT(psema->value == 0);intr_set_status(old_status);
}void sema_up(struct semaphore* psema) {enum intr_status old_status = intr_disable();//从waiter队列中唤醒第一个线程if (!empty(&psema->waiters)) {struct task_struct* thread_blocked = elem2entry(struct task_struct, general_tag, pop(&psema->waiters));thread_unblock(thread_blocked);}++psema->value;ASSERT(psema->value == 1);intr_set_status(old_status);
}void try_lock(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;}
}void try_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);//注意这里不允许调换sema_up的位置,如果调换位置,就会导致中断开启,但是plock->hodler还没设为空就进行线程切换了plock->holder = NULL;plock->holder_repeat_nr = 0;sema_up(&plock->semaphore);
}
② 实现终端输出
这样子信号量我们就完成了,接下来我们用锁来实现一个终端输出,在此之前我们先来了解了解
什么是虚拟终端。
虚拟终端(tty):为了能让更多的人同时使用计算机,必须在一个显示器下实现多个用户,为每个用户虚拟出一个显示器
原理:不同的用户使用不同显存区域,让显存分快显示,就达到了虚拟终端的效果
device/console.h
#include "stdint.h"void console_init();
void console_acquire();
void console_release();
void console_put_str(char* str);
void console_put_char(uint8_t ch);
void console_put_int(uint32_t num);
device/console.c
#include "console.h"
#include "stdint.h"
#include "sync.h"
#include "print.h"
static struct lock console_lock;void console_init() {lock_init(&console_lock);put_str("\nconsole init done!\n");
}void console_acquire() {try_lock(&console_lock);
}void console_release() {try_release(&console_lock);
}void console_put_str(char* str) {console_acquire();put_str(str);console_release();
}void console_put_char(uint8_t ch) {console_acquire();put_char(ch);console_release();
}void console_put_int(uint32_t num) {console_acquire();put_int(num);console_release();
}
将main函数中的普通put_str,改为console_put_str,接下来修改一下makefile文件,运行结果是密密麻麻的输出字符即代表我们锁编写成功(时代太过久远,忘记截图了)
三,键盘键入的原理
1,两个芯片
键盘嘛,相信大家都见过的哈,没见过的同学我相信你也肯定看不到我的文章捏。在计算机这个系统中,键盘作为一个外部设备,他并不是完完全全独立于操作系统存在的,他的功能实现涉及到了两个功能独立的芯片
- Intel 8048(或兼容芯片):位于键盘内部,称作键盘编码器,负责监听键盘按键事件并向键盘控制器报告哪个按键按下,哪个按键弹起
- Intel 8042(或兼容芯片):位于计算机主板,称作键盘控制器,负责接受键盘编码起的按键消息,并将其解码保存,然后向中断代码发送中断,之后处理器读取8042处理过并保存的数据
当然为了知道键盘到底安乐哪个键,8042与8048之间必须保持一个协议,那就是所有按键与对应竖直组成一个类似于hash映射的键盘扫描码
2,键的编码
键的编码
一个键通常对应两个码,按下的通码与松开的断码,当你一直按着键不松开,会产生练习相同的码,当你松开的时候才会终止这个就叫做断码
键盘扫描码
键盘的扫描码有三套:scan code set 1,scan code set 2,scan code set 3,下图分别是三套扫描码的附图
在此我们使用第一套键盘扫描码:
- 当今键盘内部芯片默认使用第二套编码,但是我们使用第一套编码,所以说8048将编码信息发送给8042的时候会进行一次转换
- 8042每次接受到一个字节的扫描码后就会向中断代理发送信号,一个按键操作至少触发两次中断(通码和断码各一次)
- 一般中断处理程序不会处理断码信息
- 当按下a键时,8048向8042发出a键的第二套扫描码0x1c,8042将其转换为第一套编码0x1e并保存到自己的缓冲区,当保存完毕后向中断代理发送中断,中断处理程序开始执行并从8042缓冲区读取0x1e,当松开后重复上述操作。
3,8042简介
8048是键盘的控制者(键盘监控,键盘设置,灯光),8042是键盘的IO接口,因此8042是8048的代理,8048通过PS/2,USB接口 与8042通信,处理器通过端口与8042通信。
8042有4个8位寄存器
- 8042作为8048的中转站,8048通过out 0x60,将自己的数据写入8042中,让自己的数据通过8042被操作系统读入
- 8042作为8048的输入缓冲区,将8048的信息暂存在8042缓冲区,操作系统通过 in 0x60读入数据
要注意一点的是,当8042缓冲区已经有数据时,就不会再次读入数据,要等到数据被中断程序读取后才会再次读入8048的数据,他是根据一个状态寄存器的第0位来判断的。
8042有3个寄存器,状态寄存器,输入缓冲区寄存器,控制寄存器,由于我们只需要用到部分功能我也就贴出图大家稍微参考一下即可
三,编写键盘驱动
#include "interrupt.h"
#include "stdint.h"
#include "keyboard.h"
#include "io.h"
#include "print.h"#define KB_BUF_PORT 0x60#define esc '\033'
#define backspace '\b'
#define tab '\t'
#define enter '\r'
#define delete '\177'#define char_invisible 0
#define ctrl_l_char char_invisible
#define ctrl_r_char char_invisible
#define shift_l_char char_invisible
#define shift_r_char char_invisible
#define alt_l_char char_invisible
#define alt_r_char char_invisible
#define caps_lock_char char_invisible#define shift_l_make 0x2a
#define shift_r_make 0x36
#define alt_l_make 0x38
#define alt_r_make 0xe038
#define alt_r_break 0xe0b8
#define ctrl_l_make 0x1d
#define ctrl_r_make 0xe01d
#define ctrl_r_break 0xe09d
#define caps_lock_make 0x3astatic bool ctrl_status, alt_status, caps_status, shift_status, ext_scancode;char keymap[][2] = {/* 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}
};static void intr_keyboard_handler(void) {bool ctrl_down_last = ctrl_status;bool shift_down_last = shift_status;bool caps_lock_last = casps_lock_status;bool break_code;uint16_t scancode = inb(KB_BUF_PORT);//判断是不是为多余字符,是的话马上退出处理下一个if (scancode == 0xe0) {ext_scancode = true;return;}//如果上次以0xee0开头,则合并通码,并去除ext_scancode标志if (ext_scancode) {scancode |= 0xe000;ext_scancode = false;}//获取break codebreak_code = ((scancode & 0x0080) != 0);//如果是断码,对ctrl和shift和alt进行状态判断if (break_code) {uint16_t make_code = (scancode &= 0xff7f);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_char || make code == alt_r_make) {alt_status = false;}return;}//如果为通码else if ((scancode > 0x00 && scancode < 0x3b) ||scancode == alt_r_make || scancode == ctrl_r_make) {bool shift = false;if ((scancode < 0x0e) || (scancode == 0x29) || (scancode == 0x1a) || (scancode == 0x1b)|| (scancode == 0x2b) || (scancode == 0x27)|| (scancode == 0x28) || (scancode == 0x33)|| (scancode == 0x34) || (scancode == 0x35)) {if (shift_down_last) {shift = true;}}else {if (shift_down_last && caps_lock_last) {shift = false;}else if (shift_down_last || caps_lock_last) {shift = true;}else {shift = false;}}uint8_t index = (scancode &= 0x00ff);char cur_char = keymap[index][shift];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_char || scancode == alt_r_make) {alt_status = true;}else if (scancode == caps_lock_make) {caps_lock_status = !caps_lock_status;}} else {put_str("unknown key\n");}}void keyboard_init() {put_str("keyboard_init start\n");register_intr(0x21, intr_keyboard_handler, "keyboard");put_str("keyboard_init done\n");
}
四,环形输入缓冲区
我们知道,操作键盘通常是为了和系统交互,而和系统交互一般都是写入某些shell指令,而这些shell指令需要用一个缓冲区存入起来,当形成一个完整的命令时,再一并由其他模块处理。
① 生产者消费者模型
对于缓冲区我们要有以下几个认识:
- 缓存数据
- 公共区域,多个线程同时使用该空间,需要对存取操作进行上锁
- 数据可能存在存满和取空两种状态
线程之间要相互合作,存在资源共享问题。对此我们便使用由Dijkstra提出的生产者消费者模型
生产者消费者模型是什么,我不太想用学术的语言来介绍,我在此来举个例子:
以前去吃过KFC的同学应该都了解,吃KFC的时候是需要排队的,那么此时我们把服务员当作生产者(就当作他又做饭又服务吧),备餐口就是缓冲区,而顾客则是消费者
① 当客流量比较少时,供大于求,服务员做好一大堆汉堡放在备餐区,当餐做满时便停下来休息并告诉顾客:”新鲜的汉堡做好了,来取餐嘞“(唤醒消费者),休息的顾客听到了便来到备餐区取餐
② 当客流量中等时,供等于求,只要服务员看到备餐口有空位,马上开始忙活起来。
③ 当客流量很大时,供不应求,餐取完的时候,顾客便会在等待队列中等待,并催促服务员:“搞快点咯,肚子饿了“(唤醒生产者),摸鱼的服务员便会快马加鞭的做汉堡。
总结:对于有限大小的公共缓冲区,同步生产者和消费者的运行,对共享缓冲区互斥访问,并不会过度消费和过度生产,这便是生产者消费者模型,
以及疯狂星期四KFCV50😋
② 环形缓冲区实现
其实线形的缓冲区也是OK的,怎么设计因人而异,我这里用队列来实现一个环形缓冲区,环形缓冲区的本质就是没有起始地址,没有终止地址,很简单我们直接开始吧。
device/ioqueue.h
#ifndef _DEVICE_IOQUEUE_H
#define _DEVICE_IOQUEUE_H
#include "stdint.h"
#include "thread.h"
#include "sync.h"#define bufsize 64 //一个缓冲区的大小struct ioqueue {struct lock lock;struct task_struct* producer;struct task_struct* consumer;char buf[bufsize];int32_t head; //生产是头指针移动int32_t tail; //消费时尾指针移动
};void ioqueue_init(struct ioqueue* ioq);
int32_t next_pos(int32_t pos);
bool ioq_full(struct ioqueue* ioq);
bool ioq_empty(struct ioqueue* ioq);
void ioq_wait(struct task_struct** waiter);
void ioq_wakeup(struct task_struct** waiter);
char ioq_getchar(struct ioqueue* ioq);
void ioq_setchar(struct ioqueue* ioq, char byte);
#endif // ! _DEVICE_IOQUEUE_H
device/ioqueue.c
#include "debug.h"
#include "ioqueue.h"
#include "interrupt.h"void ioqueue_init(struct ioqueue* ioq) {lock_init(&ioq->lock);ioq->producer = ioq->consumer = NULL;ioq->head = ioq->tail = 0;
}int32_t next_pos(int32_t pos) {return (pos + 1) % bufsize;
}bool ioq_full(struct ioqueue* ioq) {ASSERT(intr_get_status() == INTR_OFF);return (ioq->tail - ioq->head) == 1;
}bool ioq_empty(struct ioqueue* ioq) {ASSERT(intr_get_status() == INTR_OFF);return ioq->head == ioq->tail;
}//使当前的生产者或消费之在缓冲区上等待
void ioq_wait(struct task_struct** waiter) {ASSERT(*waiter == NULL && waiter != NULL);*waiter == running_thread();thread_block(TASK_BLOCKED);
}//唤醒生产者或消费者
void ioq_wakeup(struct task_struct** waiter) {ASSERT(*waiter != NULL);thread_unblock(*waiter);*waiter = NULL;
}char ioq_getchar(struct ioqueue* ioq) {//先判断是否关闭中断ASSERT(intr_get_status() == INTR_OFF);//判断是否为空,如果为空消费者需要休眠等待while (ioq_empty(ioq)) {try_lock(&ioq->lock);ioq_wait(&ioq->consumer);try_release(&ioq->lock);}//获取buf中的数据char ch = ioq->buf[ioq->tail];ioq->tail = next_pos(ioq->tail);if (ioq->producer != NULL) {ioq_wakeup(&ioq->producer);}return ch;
}void ioq_setchar(struct ioqueue* ioq, char byte) {//先判断是否关闭中断ASSERT(intr_get_status() == INTR_OFF);//判断是否满缓冲区,如果满了生产需要休眠等待while (ioq_full(ioq)) {try_lock(&ioq->lock);ioq_wait(&ioq->producer);try_release(&ioq->lock);}//获取buf中的数据ioq->buf[ioq->head]=byte;ioq->head = next_pos(ioq->head);if (ioq->consumer != NULL) {ioq_wakeup(&ioq->consumer);}return byte;
}
然后我们完善一下键盘操作
device/keyboard.c
struct ioqueue ioqueue;void keyboard_init()
{put_str("keyboard init start\n");register_handler(0x21, intr_keyboard_handler);init_ioqueue(&ioqueue);put_str("keyboard init done\n");
}
void intr_keyboard_handler(void)
{
//......if (cur_char){if (!ioq_full(&ioqueue))ioq_putchar(&ioqueue, cur_char);return;}
//......
}
接下来我们让两个内核线程变成消费者
kernel/main.c
#include "print.h"
#include "init.h"
#include "debug.h"
#include "thread.h"
#include "console.h"
#include "keyboard.h"
#include "ioqueue.h"
#include "interrupt.h"void g_thread(void* arg);
void g_thread2(void* arg);
void main(void) {put_str("Hello GeniusOS\n");put_int(2023);put_str("\n");init_all();thread_start("genius", 5, g_thread, "A_");thread_start("genius2", 31, g_thread2, "B_");intr_enable();while (1) {//console_put_str("Main ");}
}void g_thread(void* arg) {while (1) {enum intr_status old_status = intr_disable();if (!ioq_empty(&kbd_buf)) {console_put_str(arg);char byte = ioq_getchar(&kbd_buf);console_put_char(byte);console_put_char(' ');}intr_set_status(old_status);}
}void g_thread2(void* arg) {while (1) {enum intr_status old_status = intr_disable();if (!ioq_empty(&kbd_buf)) {console_put_str(arg);char byte = ioq_getchar(&kbd_buf);console_put_char(byte);console_put_char(' ');}intr_set_status(old_status);}
}
改一下makefile,运行一下,当你敲下键盘,且字符在屏幕上显示,这就代表你成功了,恭喜你!!!完成本章任务!!!
相关文章:

14 Day:同步锁与操作系统输入输出
前言:在上一期的线程章节中,我们的线程输出貌似有大问题,今天我们便要来学习同步锁来解决这个问题,同时再次基础上拿下键盘输入,实现操作系统的输入和输出。从今天开始我们的操作系统不在是一块“看板”了!…...

Gradle 的下载安装教程
Gradle 8.0.1 下载安装教程笔者的环境: Java 17.0.1 Gradle 8.0.1 Windows 10 教育版 64位 在继续阅读本教程之前,需要先完成 JDK 的安装。JDK 需要选择 8 及以上的版本。关于 JDK 的安装,可见笔者的另一篇博客: Java 的下载安…...

「Python 基础」常用模块
文章目录1. 内建模块datetimecollectionsnamedtuple()dequedefaultdictOrderedDictChainMapCounterbase64structhashlib摘要算法摘要的应用hmacitertoolscontextlib\_\_enter\_\_ 和 \_\_exit\_\_contextmanagerclosingurllibGETPOSTHandlerXMLDOMSAXHTMLParser2. 第三方模块Pi…...

Java【二叉搜索树和哈希表】详细图解 / 模拟实现 + 【Map和Set】常用方法介绍
文章目录前言一、二叉搜索树1、什么是二叉搜索树2、模拟实现二叉搜索树2.1, 查找2.2, 插入2.3, 删除3、性能分析二、模型三、哈希表1、什么是哈希表1.1, 什么是哈希冲突1.2, 避免, 解决哈希冲突1.2.1, 避免: 调节负载因子1.2.2, 解决1: 闭散列(了解)1.2.3, 解决2: 开散列/哈希桶…...

如何用 C 语言实现文本特征提取?
文本特征提取是一种将文本转换为数字或向量表示的技术,它是自然语言处理中的重要步骤。以下是一些用 C 语言实现文本特征提取的基本方法:基于词袋模型的特征提取词袋模型是一种将文本表示为单词频率的方法,可以通过以下步骤实现:将…...

ESD静电保护器件分类简介及场景应用
文章目录 1. ESD介绍1.1 ESD简介1.2 ESD产生原理1.3 ESD危害2. 器件级ESD模型2.1 人体模型(HBM)2.2 机器模型(MM)2.3 带电器件模型(CDM)3. 系统级ESD模型3.1 介绍3.2 防护器件分类简介3.2.1 TVS二极管3.2.2 MLCC陶瓷电容3.2.3 ESD抑制管3.2.4 MOV压敏电阻3.2.5 比较4. ES…...

硅谷银行倒闭的几点启示
摘要:本文从公开资料分析一下硅谷银行对信息科技行业的我们有一些什么启示。硅谷银行“拔网线”了,想创业的您,该注意了。1.硅谷银行是谁我们从其官网的说明来看看。The financial partner of the innovation economy.(翻译成中文…...

【AWS入门】IAM基本应用-2023/3/4
目录IAM概述根用户和IAM用户参考IAM概述 IAM(Identity Access Management)是身份和访问管理服务,要访问AWS服务和资源,就要使用IAM进行身份验证和授权。当我们通过控制台,CLI,或API访问AWS服务时,都需要通…...

RabbitMQ系列(1)--RabbitMQ简介
1、RabbitMQ概念RabbitMQ是一个消息中间件,不对消息进行处理,只对消息做接收、存储和转发。2、RabbitMQ四大核心概念(1)生产者产生数据发送信息的程序(2)交换机交换机是RabbitMQ中一个非常重要的部件,接收来着生产者的消息并把消息推送到队列…...

aws dynamodb 使用awsapi和PartiQL掌握dynamodb的CRUD操作
总结一下 dynamodb通常和java等后端sdk结合使用使用的形式可以是api或partiql语法调用dynamodb的用法不难,更重要的是维护成本,所需的服务集成,技术选型等和大数据结合场景下有独特优势 之后可能再看看java sdk中DynamoDBMapper的写法&…...

【C++学习】类和对象(上)
前言: 由于之前电脑“嗝屁”了,导致这之前一直没有更新博客,今天才拿到电脑,在这里说声抱歉。接下来就进入今天的学习,在之前我们已经对【C】进行了初步的认识,有了之前的知识铺垫,今天我们将来…...

一文带你深入理解【Java基础】· Java反射机制(下)
写在前面 Hello大家好, 我是【麟-小白】,一位软件工程专业的学生,喜好计算机知识。希望大家能够一起学习进步呀!本人是一名在读大学生,专业水平有限,如发现错误或不足之处,请多多指正࿰…...

JVM的几种GC
GC JVM在进行GC时,并不是对这三个区域统一回收。大部分时候,回收都是新生代~ 新生代GC(minor GC): 指发生在新生代的垃圾回收动作,因为Java对象大多都具备朝生夕灭的特点,所以minor GC发生得非…...

掌握Shell脚本的if语句,让你的代码更加精准和高效
前言 大家好,我是沐风晓月,本文首发于csdn, 作者: 我是沐风晓月。 文章收录于 我是沐风晓月csdn专栏 【系统架构实战】专栏中的【shell脚本入门到精通】专栏。 本专栏从零基础带你层层深入,学会shell脚本,不是梦。 &…...

音质好的蓝牙耳机有哪些?音质最好的蓝牙耳机排行
说起当代人外出必备是数码产品,蓝牙耳机肯定存在。不管是听歌还是追剧,蓝牙耳机在音质上的表现也是越来越好了。下面,我来给大家推荐几款音质好的蓝牙耳机,一起来看看吧。 一、南卡小音舱蓝牙耳机 参考价:259 蓝牙版…...

一次Android App NDK崩溃问题的分析及解决
文章目录小结NDK崩溃的问题通过logcat查看崩溃日志提取tombstone的记录通过ndk-stack来输出日志取得的日志分析并解决分析使用add2line定位具体报错的行数解决参考小结 最近碰一次Android App NDK崩溃的问题,这个NE(Native Exception)是从ND…...

因果图判定表法
因果图&判定表法 在了解了等价类和边界值比较适宜搭档的测试用例方法之后 接下来我们来了解另外一队就是因果图和判定表 因果图会产生判定表法 因果图法 等价类划分法和边界值分析方法都是着重考虑输入条件而不考虑输入条件的各种组合、输入条件之间的相互制约关系。例…...

Oracle 数据库相关信息清单列表
Oracle 数据库相关信息清单列表 一、设置Oracle安装目录 Oracle基目录(ORACLE_BASE):D:\databases\oracle\oracle_11g\app\Administrator 软件位置(ORACLE_HOME):D:\databases\oracle\oracle_11g\app\Administrator\product\11.2.0\dbhome_1 数据库文件位置:D:\databa…...

射频资料搜集--推荐几个网站和链接
https://picture.iczhiku.com/resource/eetop/wHKYFQlDTRRShCcc.pdfhttps://picture.iczhiku.com/resource/eetop/wHKYFQlDTRRShCcc.pdfVCO pulling的资料 模拟滤波器与电路设计手册 - 射频微波仿真 - RF技术社区 Practical RF Amplifier Design Using the Available Gain Pr…...

B1048 数字加密
decription 本题要求实现一种数字加密方法。首先固定一个加密用正整数 A,对任一正整数 B,将其每 1 位数字与 A 的对应位置上的数字进行以下运算:对奇数位,对应位的数字相加后对 13 取余——这里用 J 代表 10、Q 代表 11、K 代表 …...

Qt使用FFmpeg播放视频
一、使用场景 因为项目中需要加载MP4播放开机视频,而我们的设备所使用的架构为arm架构,其中缺乏一些多媒体库。安装这些插件库比较麻烦,所以最终决定使用FFmpeg播放视频。 二、下载编译ffmpeg库 2.1 下载源码 源码下载路径:http…...

Win32 ListBox控件
Win32 ListBox控件 创建ListBox控件 创建窗口函数 HWND CrateWindowEx(DWORD dwExStyle , // 窗口的扩展风格,基本没用LPCTSTR lpClassName, // 已经注册的窗口类名称LPCTSTR lpWindowName, // 窗口标题栏的名字DWORD dwStyle, // 窗口的基本风格int x, // 左上角水平坐标int …...

最大值池化与均值池化比较分析
1 问题在深度学习的卷积网络过程中,神经网络有卷积层,池化层,全连接层。而池化层有最大值池化和均值池化两种情况,而我们组就在思考,最大值池化和均值池化有什么区别呢?两者的模型准确率是否有所不同&#…...

统计学 多元线性回归
文章目录统计学 多元线性回归多元线性回归模型拟合优度显著性检验线性关系检验回归系数检验多重共线性及其处理多重共线性的问题多重共线性的识别与处理变量选择利用回归方程进行预测哑变量回归统计学 多元线性回归 多元线性回归模型 多元线性回归模型:设因变量为…...

tar和gzip压缩和解压
打包和压缩的区别:打包:将多文件 封装在一起压缩:将多文件 封装在一起 通过特定的算法 将冗余的数据 进行删除tar默认是打包命令,如果想用tar进行压缩 必须加选项1、gzip格式压缩:tar zcvf 压缩包包名 文件1 文件2 文件…...

搭建Docker企业私有仓库
什么是仓库 仓库(Repository)是存储和分发 Docker 镜像的地方。镜像仓库类似于代码仓库,Docker Hub 的命名来自 GitHub,Github 是我们常用的代码存储和分发的地方。同样 Docker Hub 是用来提供 Docker 镜像存储和分发的地方。 谈…...

[NOIP2009 提高组] 最优贸易(C++,tarjan,topo,DP)
题目描述 $C 国有国有国有 n 个大城市和个大城市和个大城市和 m$ 条道路,每条道路连接这 nnn个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 mmm 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的…...

计算机网络:移动IP
移动IP相关概念 移动IP技术是移动结点(计算机/服务器)以固体的网络IP地址,实现跨越不同网段的漫游功能,并保证了基于网络IP的网络权限在漫游中不发生任何改变。移动结点:具有永久IP地址的设备。归属代理(本…...

binutils工具集——GNU binutils工具集简介
以下内容源于网络资源的学习与整理,如有侵权请告知删除。 GNU binutils是一个二进制工具集,主要包括: ld,GNU链接器。as,GNU汇编器。addr2line,把地址转化为文件名和行号。nm,列出目标文件的符…...

Golang编译选项(ldflags)有趣应用
本文介绍如何在构建时使用ldflags选项给Golang应用程序注入变量,用于给Go可执行文件增加版本标识或GIT提交摘要等信息。 应用程序的版本信息 我们首先查看Docker Cli 包含的提交信息: docker version 返回结果: Server: Docker Engine - Co…...