操作系统 4.1-I/O与显示器
外设工作起来

操作系统让外设工作的基本原理和过程,具体来说,它概括了以下几个关键步骤:
-
发出指令:操作系统通过向控制器中的寄存器发送指令来启动外设的工作。这些指令通常是通过I/O指令(如
out指令)来实现的。 -
控制器处理:控制器接收到指令后,根据寄存器中的内容来操控硬件。控制器内部可能包含有计算电路,能够根据CPU发出的指令来具体操作设备。
-
中断处理:一旦外设完成其任务,它会向CPU发送一个中断信号。CPU接收到中断后,会暂停当前的工作,转而处理中断,这可能涉及到数据传输等操作。
-
统一的文件接口:为了让外设的使用变得简单,操作系统提供了一种统一的视图,即文件视图。这意味着,无论操作哪种外设,用户都可以通过统一的文件操作接口(如
open、read、write等)来进行。
总结来说,操作系统让外设工作的核心原理非常简单,即通过发出指令让外设工作,然后编写中断处理程序来响应外设完成任务后的中断信号。此外,操作系统通过提供统一的文件接口,使得用户可以方便地使用各种外设,而无需关心底层的硬件细节。接下来我们将围绕这三个方面讲解。
外设工作的开始

提取的代码如下:
int fd = open("/dev/xxx");
for (int i = 0; i < 10; i++) {write(fd, i, sizeof(int));
}
close(fd);
外设工作的开始可以总结为以下几个步骤:
-
打开设备:
-
使用
open函数打开指定的设备文件(/dev/xxx),这个文件是系统中外设的接口。 -
open函数返回一个文件描述符fd,用于后续对该设备的操作。
-
-
数据传输:
-
通过
write函数将数据传输到外设。在这个例子中,数据是一个整数i,大小为sizeof(int)。 -
这个过程在一个循环中进行,循环10次,每次写入一个整数。
-
-
关闭设备:
-
完成数据传输后,使用
close函数关闭设备文件,释放文件描述符fd所占用的资源。
-
文件视图概念

文件视图是操作系统提供的两大视图之一,它将所有的I/O设备统一抽象为文件,使得用户可以通过一组标准的文件操作接口(如open、read、write、close)来访问和操作这些设备。这种抽象极大地简化了用户与硬件设备的交互,并隐藏了底层硬件的具体细节。
在文件视图中,操作系统将设备属性数据和设备驱动程序结合在一起,通过系统调用接口与用户空间进行交互。当用户程序调用这些系统调用时,操作系统会进行解释,并将其转换为对特定设备的命令。这些命令随后被发送到相应的设备控制器(如键盘控制器或磁盘控制器),并由控制器执行具体的硬件操作。
文件视图的样貌可以总结如下:
-
统一接口:无论什么设备,用户都通过统一的系统调用接口(
open、read、write、close)来进行操作。 -
设备抽象:不同的设备对应不同的设备文件(如
/dev/xxx),操作系统根据这些设备文件找到控制器的地址、内容格式等信息。 -
设备驱动:设备驱动程序是操作系统与硬件设备之间的桥梁,它负责将系统调用转换为对特定硬件的操作。
-
中断处理:当设备完成操作后,会通过中断机制通知操作系统,操作系统再进行相应的中断处理。
-
I/O系统:操作系统中的I/O系统负责管理设备属性数据和设备驱动程序,协调用户程序与硬件设备之间的交互。
通过这种文件视图,操作系统为用户提供了一个简单、统一的方式来操纵外设,同时隐藏了底层硬件操作的复杂性。这种抽象不仅简化了用户程序的开发,还提高了系统的可移植性和可扩展性。
代码思路讲解

提取的代码如下:
int sys_write(unsigned int fd, char *buf, int count) {struct file* file;file = current->filp[fd]; // fd是找到file的索引inode = file->f_inode; // file的目的是得到inode
}
总结显示器输出的过程:
-
系统调用:
-
用户程序通过
printf函数输出信息,printf函数内部会先创建一个缓存区(buf),将格式化后的输出写入该缓存区。
-
-
写入系统调用:
-
printf函数最终会调用write系统调用,将缓存区中的数据写入指定的文件描述符(fd)。
-
-
文件描述符索引:
-
在Linux内核中,
sys_write函数通过文件描述符(fd)找到对应的文件结构体(file)。文件描述符是用户空间和内核空间之间的索引。
-
-
获取inode:
-
从文件结构体中获取inode结构体,inode包含了文件的元数据和设备信息。对于设备文件(如显示器),inode中包含了设备驱动的相关信息。
-
wirte->filp

提取的代码如下:
int copy_process(...){*p = *current;for (i = 0; i < NR_OPEN; i++)if ((f = p->filp[i])) f->f_count++;
}
void main(void) {if (!fork()) { init(); }
}
void init(void) {open("/dev/tty0", O_RDWR, 0);dup(0);dup(0);execve("/bin/sh", argv, envp);
}
fd=1的filp从哪里来?
在UNIX和Linux系统中,当一个进程创建一个新的子进程时(通常是通过fork系统调用),子进程会继承父进程的文件描述符。这意味着子进程会拥有与父进程相同的文件描述符集合,包括指向相同文件结构(struct file)的指针。
在提供的代码中,copy_process函数负责复制父进程的文件描述符信息到子进程。这是通过遍历父进程的filp数组(每个元素都是指向struct file的指针)并递增相应文件结构的引用计数来实现的。这样做确保了文件在两个进程间正确共享。
在main函数中,通过调用fork创建了一个新的子进程。如果fork返回0(表示这是子进程),则调用init函数。在init函数中,首先打开/dev/tty0(通常是控制台设备),然后使用dup(0)将标准输入、输出和错误都重定向到这个控制台设备。最后,通过execve调用替换当前进程映像为/bin/sh(shell),从而启动一个新的shell进程。
因此,fd=1(通常用于标准输出)的filp指针是从父进程继承来的,并且在子进程中通过dup(0)调用被重定向到/dev/tty0设备。这样,当shell进程写入标准输出时,数据会被发送到控制台设备。
filp->open

提取的代码如下:
int sys_open(const char* filename, int flag) {i = open_namei(filename, flag, &inode);current->filp[fd] = f; // 第一个空闲的fdf->f_mode = inode->i_mode;f->f_inode = inode;f->f_count = 1;return fd;
}
open系统调用完成了什么?
open系统调用主要完成了以下步骤:
-
解析目录,找到inode:系统需要解析传入的文件名,找到对应的inode结构,inode包含了文件的元数据和设备信息。
-
分配文件描述符(fd):在进程的文件描述符数组(
filp)中找到一个空闲的文件描述符,并将其分配给这个文件。 -
建立文件结构体(file):创建一个文件结构体(
file),该结构体包含了文件的状态信息,如文件模式(f_mode)和指向inode的指针(f_inode)。
开始输出

提取的代码如下:
// sys_write function in linux/fs/read_write.c
int sys_write(unsigned int fd, char *buf, int cnt) {inode = file->f_inode;if (S_ISCHR(inode->i_mode))return rw_char(WRITE, inode->i_zone[0], buf, cnt);...
}
// rw_char function in linux/fs/char_dev.c
int rw_char(int rw, int dev, char *buf, int cnt) {crw_ptr call_addr = crw_table[MAJOR(dev)];call_addr(rw, dev, buf, cnt);...
}
-
系统调用:用户程序通过
write系统调用向内核请求写操作,传递文件描述符(fd)、缓冲区地址(buf)和要写入的字节数(cnt)。 -
字符设备检查:在
sys_write函数中,首先获取文件结构体的inode,并检查该inode表示的是否为字符设备(通过S_ISCHR(inode->i_mode)判断)。 -
调用设备驱动:如果是字符设备,调用
rw_char函数,传入写操作标志(WRITE)、inode中的设备信息(i_zone[0])、缓冲区地址和字节数。 -
设备驱动操作:在
rw_char函数中,根据设备的主要号码(MAJOR(dev))从字符设备驱动表(crw_table)中获取对应的操作函数指针,并调用该函数执行实际的写操作。 -
输出到屏幕:对于显示器这样的字符设备,
rw_char函数最终会调用显示器的驱动函数,将缓冲区中的数据写入显示器的显存,实现向屏幕的输出。
这个过程展示了从用户空间的 printf 调用开始,经过系统调用接口,到内核空间的文件操作,再到设备驱动程序,最终实现数据向硬件设备的输出。这是操作系统中I/O系统工作的一个典型流程。
rw_char->crw_table

提取的代码如下:
// 定义字符设备操作函数指针数组
static crw_ptr crw_table[] = {..., rw_ttyx, ...};
// 函数指针类型定义
typedef (*crw_ptr)(int rw, unsigned minor, char *buf, int count);
// 字符设备读写函数
static int rw_ttyx(int rw, unsigned minor, char *buf, int count) {return ((rw == READ) ? tty_read(minor, buf) : tty_write(minor, buf));
}
// 真正的写函数
int tty_write(unsigned channel, char *buf, int nr) {struct tty_struct *tty;tty = channel + tty_table;sleep_if_full(&tty->write_q);...
}
总结代码所做的事情及用途:
-
定义字符设备操作函数指针数组(
crw_table):-
crw_table是一个数组,包含了指向不同字符设备操作函数的指针。这些函数负责对字符设备进行读写操作。
-
-
函数指针类型定义(
crw_ptr):-
crw_ptr是一个函数指针类型,用于指向符合特定签名的函数,即接受读写标志、次要设备号、缓冲区指针和计数作为参数的函数。
-
-
字符设备读写函数(
rw_ttyx):-
rw_ttyx函数根据传入的读写标志(rw),决定调用tty_read还是tty_write函数。这个函数作为字符设备的通用入口点,根据操作类型分发到具体的读写处理函数。
-
-
真正的写函数(
tty_write):-
tty_write是实现字符设备(如终端)写操作的核心函数。它负责将数据从内核缓冲区写入到设备。 -
函数首先通过
channel和tty_table获取到tty_struct结构体,该结构体包含了终端设备的相关信息和状态。 -
然后检查输出队列(
write_q)是否已满,如果已满,则调用sleep_if_full函数使进程休眠,等待队列有空间。 -
一旦队列有空间,数据就被写入队列,后续操作(可能是中断处理程序)会负责将队列中的数据实际输出到设备。
-
crw_table->tty_write

提取的代码如下:
// 在 linux/kernel/tty_io.c 中的 tty_write 函数
int tty_write(unsigned channel, char *buf, int nr) {char c, *b = buf;while (nr > 0 && !FULL(tty->write_q)) {c = get_fs_byte(b); // 从用户缓存区读if (c == '\r') { PUTCH(13, tty->write_q); continue; }if (O_LCUC(tty)) c = toupper(c);b++; nr--;PUTCH(c, tty->write_q);} // 输出完事或写队列满tty->write(tty);
}
总结代码所做的事情及用途:
-
初始化:
-
定义字符变量
c和字符指针b指向缓冲区buf的起始位置。
-
-
循环处理每个字符:
-
使用
while循环,当还有字符要写入(nr > 0)且写队列未满(!FULL(tty->write_q))时,继续处理。 -
从用户空间的缓冲区中读取一个字符到
c。
-
-
处理回车字符:
-
如果字符是回车符(
'\r'),将其转换为换行符('\n')并继续下一个循环。
-
-
字符大小写转换:
-
如果终端设置为转换为大写(
O_LCUC(tty)),将字符c转换为大写。
-
-
写入队列:
-
将处理后的字符放入终端的写队列
tty->write_q中。 -
更新缓冲区指针
b和字符计数nr。
-
-
触发实际写操作:
-
一旦所有字符都已处理或写队列满,调用
tty->write(tty)触发实际的写操作,将队列中的数据输出到屏幕上。
-

-
提取的代码如下:
-
// 在 include/linux/tty.h 中定义的 tty_struct 结构体 struct tty_struct {void (*write)(struct tty_struct *tty);struct tty_queue read_q, write_q; }; // tty_struct 结构体数组的初始化 struct tty_struct tty_table[] = {{con_write, {0,0,0,0,""}, {0,0,0,0,""}},{}, ... }; // con_write 函数在 linux/kernel/chr_drv/console.c 中的定义 void con_write(struct tty_struct *tty) {GETCH(tty->write_q, c);if (c > 31 && c < 127) {__asm__ ("movb _attr, %%ah\n\t""movw %%ax, %1\n\t::" "a"(c),"m"(*(short*)pos):"ax");pos += 2;} }-
con_write函数是 Linux 内核中负责将字符输出到控制台显示器的关键函数。它通过直接操作显存来实现字符的显示,这是 Linux 内核中实现控制台输出的底层机制。 -
通过这种方式,内核可以将用户程序的输出(如通过
printf函数)转换为屏幕上的可见字符,实现用户与系统的交互。
-
-
总结代码所做的事情及用途:
con_write函数定义:-
如果字符
c在可打印范围内(ASCII码 32 到 126),则通过内联汇编代码将其写入显存(视频内存)的特定位置。 -
函数从
tty->write_q队列中获取一个字符c。 -
con_write函数是tty_struct结构体中的write函数指针所指向的实际函数,负责将字符写入显示器。
-
tty_write->mov pos


-
这两张图片提供了关于如何在Linux内核中实现向屏幕输出字符的详细信息。以下是提取的代码和总结:
提取的代码:
-
// 在 include/linux/tty.h 中定义的 tty_struct 结构体 struct tty_struct {void (*write)(struct tty_struct *tty);struct tty_queue read_q, write_q; }; // tty_struct 结构体数组的初始化 struct tty_struct tty_table[] = {{con_write, {0,0,0,0,""}, {0,0,0,0,""}}, {}, ... }; // con_write 函数在 linux/kernel/chr_drv/console.c 中的定义 void con_write(struct tty_struct *tty) {GETCH(tty->write_q, c);if (c > 31 && c < 127) {__asm__ ("movb _attr, %%ah\n\t""movw %%ax, %1\n\t"::"a"(c),"m"(*(short*)pos):"ax");pos += 2;} }总结代码的作用:
用途:
-
con_write函数是 Linux 内核中负责将字符输出到控制台显示器的关键函数。它通过直接操作显存来实现字符的显示,这是 Linux 内核中实现控制台输出的底层机制。 -
通过这种方式,操作系统能够统一管理不同程序的输出,提供一致的接口给用户程序,同时隐藏了硬件操作的复杂性。
-
这种机制是操作系统中设备驱动程序的一部分,它展示了如何通过编程接口与硬件设备进行交互,是学习操作系统工作原理和设备驱动开发的重要内容。
关于
mov pos的解释:-
mov pos, c是完成显示中最核心的秘密,它将字符c的值移动到pos指向的显存位置,从而在屏幕上显示字符。 -
pos指向显存的起始地址(例如0xA0000),每次写入一个字符后,pos的值会增加,以指向下一个字符的位置。 -
这种直接操作显存的方法是早期计算机系统中常见的屏幕输出方式,它允许操作系统直接控制屏幕上的每个像素点。
关于
pos += 2的解释:-
在彩色图形适配器(CGA)中,屏幕上的一个字符在显存中除了字符本身还应该有字符的属性(如颜色等)。因此,每个字符及其属性占用两个字节。
-
pos += 2表示在写入一个字符后,pos的值增加2,以指向下一个字符及其属性的起始位置。 -
这种机制确保了字符及其属性能够正确地存储在显存中,从而在屏幕上正确显示。
-
总结

printf 的整个过程涉及多个步骤和组件,具体如下:
-
库函数(printf):
-
用户程序调用标准库中的
printf函数来输出格式化的文本。
-
-
系统调用(write):
-
printf函数处理完格式化字符串后,通过系统调用write将数据写入文件描述符指向的设备。
-
-
字符设备接口(crw_table[]):
-
系统调用
write通过字符设备接口数组crw_table[]找到对应的设备处理函数。
-
-
tty设备写(tty_write):
-
对于终端设备,
tty_write函数负责将数据写入write_q队列。
-
-
write_q队列:
-
write_q队列用于暂存要写入设备的数据,直到设备准备好接收数据。
-
-
显示器写(con_write):
-
con_write函数负责将write_q队列中的数据实际写入显存。
-

相关文章:
操作系统 4.1-I/O与显示器
外设工作起来 操作系统让外设工作的基本原理和过程,具体来说,它概括了以下几个关键步骤: 发出指令:操作系统通过向控制器中的寄存器发送指令来启动外设的工作。这些指令通常是通过I/O指令(如out指令)来实现…...
前端-Vue3
1. Vue3简介 2020年9月18日,Vue.js发布版3.0版本,代号:One Piece(n 经历了:4800次提交、40个RFC、600次PR、300贡献者 官方发版地址:Release v3.0.0 One Piece vuejs/core 截止2023年10月,最…...
Facebook账号类型一览
对于跨境出海从业者来说,Facebook是必不可少的内容营销和广告投放平台。针对Facebook的营销策略和发挥空间都很丰富,因此了解Facebook账号的类型、特点、适用场景和相关工具还是很有用的。 一、账号类型及特点 1.小黑号 无主页、无好友、无历史操作&am…...
Kotlin 通用请求接口设计:灵活处理多样化参数
在 Kotlin 中设计一个通用的 ControlParams 类来处理不同的控制参数,有几种常见的方法:方案1:使用密封类(Sealed Class) sealed class ControlParamsdata class LightControlParams(val brightness: Int,val color: S…...
Java学习手册:Java基本语法与数据类型
Java语言以其简洁明了的语法和强大的数据类型系统而闻名。掌握Java的基本语法和数据类型是成为一名合格Java开发者的第一步。本文将深入探讨Java的基本语法结构和数据类型,帮助读者打下坚实的基础。 Java的基本语法 Java语言的语法设计简洁而强大,强调…...
通过扣子平台将数据写入飞书多维表格
目录 1.1 创建飞书开放平台应用 1.2 创建飞书多维表格 1.3 创建扣子平台插件 1.1 创建飞书开放平台应用 1.1.1 打开地址:飞书开放平台,点击创建应用 注:商店应用需要申请ISV资质,填写企业主体信息,个人的话&#x…...
C++-Mongoose(2)-https-server-openssl
OpenSSL生成HTTPS自签名证书 - 简书 1.Openssl windowsubuntu下载http://www.openssl.vip/download1.VS2019编译OpenSSL 2.VS2019编译第一个OpenSSL项目 1.ubuntu编译OpenSSL 3.0 2.编写第一个OpenSSL 1.windows下编译OpenSSL 安装vs2019 perl nasm安装activePerl…...
【GDB】调试程序的基本命令和用法(Qt程序为例)
1. 引言 GDB(GNU Debugger)是一个强大的命令行调试工具,它可以帮助开发者在程序运行时查找和修复错误。当调试Qt程序时,GDB同样适用,并且能够帮助开发者定位诸如数组越界挂死等复杂问题。 2. 基本命令 2.1 启动GDB …...
力扣DAY46-50 | 热100 | 二叉树:展开为链表、pre+inorder构建、路径总和、最近公共祖先、最大路径和
前言 中等 、困难 √,越来越有手感了,二叉树done! 二叉树展开为链表 我的题解 前序遍历树,当遇到左子树为空时,栈里pop节点,取右子树接到左子树位置,同时断开该右子树与父节点的连接&#x…...
服务器DNS失效
服务器异常 xx.t.RequestException: java.net.UnknownHostException: test.ac.xxxx.cn现象分析 本地测试正常,说明域名本身无问题。服务器 DNS 解析异常,导致 UnknownHostException。**服务器可正常解析 ****baidu.com**,说明网络正常&#…...
用excel做九乘九乘法表
公式: IF($A2>B 1 , 1, 1,A2 & “" & B$1 & “” & $A2B$1,”")...
企业数据安全如何保障?深度解析AIGC系统源码本地化部署
—从数据加密到权限管控,构建企业级AI安全防线 企业AIGC面临的5大数据安全风险 1. 数据出境违规 典型场景: 使用ChatGPT处理客户信息 → 数据经美国服务器中转 → 违反《个人信息保护法》第38条某金融公司因通过Midjourney生成宣传图,导致产…...
《妖风》-来自DeepSeek
《妖风》 周明揉了揉酸胀的眼睛,电脑屏幕上的Excel表格已经模糊成一片绿色的小格子。窗外,三月的阳光懒洋洋地洒进来,带着春天特有的那种让人昏昏欲睡的温暖。办公室里中央空调的嗡嗡声像是一首催眠曲,他的眼皮越来越重。 "…...
鬼泣:蓄力攻击
文章目录 蓄力攻击:有两个动作,蓄力时触发蓄力动作,攻击时触发攻击动作1.蓄力动作2.攻击动作 浮空上挑1.蓄力对齐位置 2.攻击 下劈斩1.蓄力对齐位置 2.攻击 beiwuf debug事件分发器发送:调用发送器即可发送消息接收:绑…...
企业指标设计方法指南
该文档聚焦企业指标设计方法,适用于企业中负责战略规划、业务运营、数据分析、指标管理等相关工作的人员,如企业高管、部门经理、数据分析师等。 主要内容围绕指标设计展开:首先指出指标设计面临的困境,包括权责不清、口径不统一、缺乏标准规范、报表体系混乱、指标…...
CSS学习02 动态列数表格开发,解决多组数据布局与边框重合问题
概要 在前端开发中,表格常用于展示结构化数据。当数据组的字段数量不统一时(如有的行包含 3 组数据,有的行包含 2 组或 1 组),传统固定列数的表格会出现结构错位、边框重合等问题。本文通过 HTML/CSS 规范方法&#x…...
加载js/mjs模块时服务器返回的 MIME 类型不对导致模块被拒绝执行
浏览器报错 Failed to load module script: The server responded with a non-JavaScript MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.Understand this errorAI 核心问题 浏览器加载模块脚本(如…...
大唐杯省赛安排来了!还有7天,该如何准备?
(一)赛道一:工程实践赛 1、理论赛阶段由参赛队伍使用两台电脑分别登录学唐平台作答,仿真实践赛阶段为参赛队伍共用一台电脑,以竞赛小组方式共同作答(按照报名顺序,用第1选手账号登录仿真平台)。最终统计理…...
iframe学习与应用场景指南
一、iframe核心原理与学习路径 1. 嵌套网站的本质原理 技术特性: • 浏览器为每个iframe创建独立的window对象和DOM环境 • 资源独立加载:子页面拥有自己的CSS/JS/Cookie作用域 • 跨域限制:同源策略下无法直接访问DOM(需CORS或…...
WebGL数学手记:矩阵基础
一、矩阵的定义 矩阵,数学术语。在数学中,矩阵(Matrix)是一个按照长方阵列排列的复数或实数集合。 1.英文发音(Matrix) Matrix的发音类似于中文的[美吹克斯],知道它的发音。方便后期看教程时…...
IO流——字符输入输出流:FileReader FileWriter
一、文件字符输入流:FileReader 作用:以内存为基准,可以把文件中的数据以字符的形式读入到内存中去 public class Test5 {public static void main(String[] args) {try (Reader fr new FileReader("E:\\IDEA\\JavaCodeAll\\file-io-t…...
Graphpad Prism for Mac医学绘图
Graphpad Prism for Mac医学绘图 一、介绍 GraphPad Prism for Mac是一款功能强大、易于使用的科学和统计分析软件,适用于各种类型的数据处理和可视化需求。无论您是进行基础研究、临床试验还是学术写作,GraphPad Prism for Mac都能为您短时间内做出最…...
使用人工智能大模型腾讯元宝,如何免费快速做高质量的新闻稿?
今天我们学习使用人工智能大模型腾讯元宝,如何免费快速做高质量的新闻稿? 手把手学习视频地址:https://edu.csdn.net/learn/40402/666431 第一步在腾讯元宝对话框中输入如何协助老师做新闻稿,通过提问,我们了解了老师…...
破解root密码
一、背景: 必须是服务器的管理者,涉及重启服务器 二、破解过程: 1)重启系统,进入 救援模式 开机过程中,按e进入救援模式 在linux开头的该行,将此行的ro修改为rw 然后空格输入 rd.break 按 ctrl x 启动,…...
嵌入式---烧录器
一、核心定义与本质功能 烧录器(Programmer)是一种将用户编写的程序代码(如.hex/.bin文件)写入单片机内部存储器(Flash/EEPROM/ROM)的专用工具,核心功能包括: 程序烧写:…...
机器学习 | 强化学习基本原理 | MDP | TD | PG | TRPO
文章目录 📚什么是强化学习🐇监督学习 vs 强化学习🐇马尔科夫决策过程(MDP)📚基本算法(value-based & policy-based)🐇时序差分算法(TD)🐇SARSA和Q-learning🐇策略梯度算法(PG)🐇REINFORCE和Actor-Critic🐇信任区域策略优化算法(TRPO)⭐️参考…...
Spring中使用Kafka的详细配置,以及如何集成 KRaft 模式的 Kafka
在 Spring 中使用 Apache Kafka 的配置主要涉及 Spring Boot Starter for Kafka,而开启 KRaft 模式(Kafka 的元数据管理新模式,替代 ZooKeeper)需要特定的 Kafka Broker 配置。以下是详细步骤: 一、Spring 中集成 …...
llinux上的pip国内镜像全局配置
1、创建全局 pip 配置文件(推荐) sudo tee /etc/pip.conf <<EOF [global] index-url https://pypi.tuna.tsinghua.edu.cn/simple trusted-host pypi.tuna.tsinghua.edu.cn EOF2、验证配置 任意用户运行以下命令应显示配置的镜像: …...
ASEG的鉴定
等位基因特异性表达(Allele-Specific Expression, ASE)基因的鉴定是研究杂种优势和基因表达调控的重要手段。以下是鉴定ASE基因的详细流程和方法: ### **1. 实验设计与样本准备** - **选择材料**:选择杂交种及其亲本作为研究材料。例如,玉米中的B73和Mo17及其杂交组合B73…...
swift菜鸟教程14(闭包)
一个朴实无华的目录 今日学习内容:1.Swift 闭包1.1闭包定义1.2闭包实例1.3闭包表达式1.3.1sorted 方法:据您提供的用于排序的闭包函数将已知类型数组中的值进行排序。1.3.2参数名称缩写:直接通过$0,$1,$2来顺序调用闭包的参数。1.3.3运算符函…...
