Linux基础IO【重定向及缓冲区理解】

✨个人主页: 北 海
🎉所属专栏: Linux学习之旅
🎃操作环境: CentOS 7.6 阿里云远程服务器

成就一亿技术人


文章目录

  • 🌇前言
  • 🏙️正文
    • 1、文件描述符
      • 1.1、先描述,再组织
      • 1.2、files_struct
      • 1.3、分配规则
      • 1.4、一切皆文件
    • 2、重定向
      • 2.1、重定向的本质
      • 2.2、利用指令重定向
      • 2.3、利用函数重定向
    • 3、缓冲区
      • 3.1、缓冲区存在的意义
      • 3.2、缓冲区刷新策略
      • 3.3、普通缓冲区与内核级缓冲区
  • 🌆总结


🌇前言

文件描述符 fd 是基础IO中的重要概念,一个 fd 表示一个 file 对象,如常用的标准输入、输出、错误流的 fd 分别为 012,实际进行操作时,OS 只需要使用相应的 fd 即可,不必关心具体的 file,因此我们可以对标准流实施 重定向,使用指定的文件流,在实际 读/写 时,为了确保 IO 效率,还需要借助 缓冲区 进行批量读取,最大化提高效率。关于上述各种概念,将会在本文中详细介绍,且听我娓娓道来

IO


🏙️正文

1、文件描述符

在使用 C语言 相关文件操作函数时,可以经常看到 FILE 这种类型,不同的 FILE* 表示不同的文件,实际进行读写时,根据 FILE* 进行操作即可

#include<iostream>
#include <cstdio>

using namespace std;

int main()
{
    //分别打开三个 FILE 对象
    FILE* fp1 = fopen("test1.txt", "w");
    FILE* fp2 = fopen("test2.txt", "w");
    FILE* fp3 = fopen("test3.txt", "w");

    //对不同的 FILE* 进行操作
    //……

    //关闭
    fclose(fp1);
    fclose(fp2);
    fclose(fp3);
    fp1 = fp2 = fp3 = NULL;

    return 0;
}

那么在 C语言 中,OS 是如何根据不同的 FILE* 指针,对不同的 FILE 对象进行操作的呢?

  • 答案是 文件描述符 fd,这是系统层面的标识符,FILE 类型中必然包含了这个成员

FILE
如何证明呢?实践出真知,在上面代码的基础上,加入打印语句

注:stdin 等标准流在 C语言 中被覆写为 FILE 类型

//标准文件流
cout << "stdin->fd: " << stdin->_fileno << endl;
cout << "stout->fd: " << stdout->_fileno << endl;
cout << "stderr->fd: " << stderr->_fileno << endl;
cout << "===================================" << endl;
cout << "此时标准流的类型为:" << typeid(stdin).name() << endl;
cout << "此时文件流的类型为:" << typeid(fp1).name() << endl;
cout << "===================================" << endl;
//自己打开的文件流
cout << "fp1->fd: " << fp1->_fileno << endl;
cout << "fp2->fd: " << fp2->_fileno << endl;
cout << "fp3->fd: " << fp3->_fileno << endl;

结果
可以看出,FILE 类型中确实有 fd 的存在

文件描述符 是如何设计的?新打开的文件描述符为何是从 3 开始?别急,接着往下看

1.1、先描述,再组织

操作系统是一个伟大的产物,它可以调度各种资源完成各种任务,但资源太多、任务太重,不合理的分配会导致效率低下,因此在进行设计时,必须确保 OS 操作时的高效性

比如现在学习的 文件系统,倘若不进行设计的话,在进行 IO 时,OS 必须先将所有文件扫描一遍,找到目标文件后才能进行操作,这是非常不合理的

因此,根据 先描述、再组织 原则,OS 将所有的文件都统一视为 file 对象,获取它们的 file* 指针,然后将这些指针存入指针数组中,可以进行高效的随机访问和管理,这个数组为 file* fd_array[],而数组的下标就是神秘的 文件描述符 fd

当一个程序启动时,OS 会默认打开 标准输入、标准输出、标准错误 这三个文件流,将它们的 file* 指针依次存入 fd_array 数组中,显然,下标 0、1、2 分别就是它们的文件描述符 fd;后续再打开文件流时,新的 file* 对象会存入当前未被占用的最小下标处,所以用户自己打开的 文件描述符一般都是从 3 开始

除了文件描述符外,还需要知道文件权限、大小、路径、引用计数、挂载数等信息,将这些文件属性汇集起来,就构成了 struct files_struct 这个结构体,而它正是 task_struct 中的成员之一

1.2、files_struct

files_struct 结构体是对已打开文件进行描述后形成的结构体,其中包含了众多文件属性,本文探讨的是 文件描述符 fd

files_strutc
进程的 PCB 信息中必然包含文件操作相关信息,这就是 files_struct

注:文件被打开后,并不会加载至内存中(这样内存早爆了),而是静静的躺在磁盘中,等待进程与其进行 IO,而文件的 inode 可以找到文件的详细信息:所处分区、文件大小、读写权限等,关于 inode 的更多详细信息将会在 【深入理解文件系统】 中讲解

1.3、分配规则

fd 的分配规则为:先来后到,优先使用当前最小的、未被占用的 fd

存在下面两种情况:

  1. 直接打开文件 file.txt,分配 fd3
  2. 先关闭标准输入 stdin 中原文件执行流(键盘),再打开文件 file.txt,分配 fd0,因为当前 0 为最小的,且未被占用的 fd
#include<iostream>
#include <cstdio>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

using namespace std;

int main()
{
    //先打开文件 file.txt
    int fd = open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    assert(fd != -1);   //存在打开失败的情况

    cout << "单纯打开文件 fd: " << fd << endl;

    close(fd);  //记得关闭

    //先关闭,再打开
    close(0);   //关闭1号文件执行流
    fd = open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);

    cout << "先关闭1号文件执行流,再打开文件 fd: " << fd << endl;

    close(fd);

    return 0;
}

结果
注意: 假若将标准输出 stdout 中的原文件执行流(显示器)关闭了,那么后续的打印语句将不再向显示器上打印,而是向此时 fd1 的文件流中打印

//关闭 显示器 写入数据
close(1);

int fd = open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
assert(fd != -1);   //存在打开失败的情况

cout << "单纯打开文件 fd: " << fd << endl;
cout << "you can see me! not on screen" << endl;

close(fd);  //记得关闭

结果
这其实就是 重定向 的基本操作

1.4、一切皆文件

如何理解 Linux 中一切皆文件这个概念?

  • 现象:即使是标准输入(键盘)、标准输出(显示器) 在 OS 看来,不过是一个 file 对象
  • 原理:无论是硬件(外设),还是软件(文件),对于 OS 来说,只需要提供相应的 读方法写方法 就可以对其进行驱动,打开文件流后,将 file* 存入 fd_array 中管理即可,因此在 Linux 中,一切皆文件

多态


2、重定向

在学习重定向前,首先要明白 标准输入、输出、错误 的用途

  • 标准输入(stdin)-> 设备文件 -> 键盘文件
  • 标准输出(stdout)-> 设备文件 -> 显示器文件
  • 标准错误(stderr)-> 设备文件 -> 显示器文件

标准输入:从键盘中读取数据
标准输出:将数据输出至显示器中
标准错误:将可能存在的错误信息输出至显示器中

标准输出 与 标准错误 都是向显示器中输出数据,为什么不合并为一个?

  • 因为在进行排错时,可能需要单独查看错误信息,若是合并在一起,查看日志时会非常麻烦;但如果分开后,只需要将 标准错误 重定向后,即可在一个单独的文件中查看错误信息

C/C++ 中进行标准输入、输出、错误对应流:
标准输入:stdin / cin
标准输出:stdout / cout
标准错误:stderr / cerr

使用 cerr 函数可直接向标准错误流中打印信息

2.1、重定向的本质

前面说过,OS 在进行 IO 时,只会根据标准输入、输出、错误对应的文件描述符 012 来进行操作,也就是说 OS 作为上层不必关心底层中具体的文件执行流信息(fd_array[] 中存储的对象) 因此我们可以做到 “偷梁换柱”,将这三个标准流中的原文件执行流进行替换,这样就能达到重定义的目的了

原理

2.2、利用指令重定向

下面直接在命令行中实现输出重定向,将数据输出至指定文件中,而非屏幕中

echo you can see me > file.txt

结果
可以看到数据直接输出至文件 file.txt

当然也可以 file.txt 中读取数据,而非键盘

cat < file.txt

结果
现在可以理解了,> 可以起到将标准输出重定向为指定文件流的效果,>> 则是追加写入
< 则是从指定文件流中,标准输入式的读取出数据

除此之外,我们还可以利用程序进行操作,在运行后进行重定向即可

#include <iostream>

using namespace std;

int main()
{
    cout << "标准输出 stdout" << endl;
    cerr << "标准错误 stderr" << endl;
    return 0;
}

直接运行的结果,此时的标准输出和标准错误都是向显示器上打印
结果
利用命令行只对 标准输出 进行重定向,file.txt 中只收到了来自 标准输出 的数据,这是因为 标准输出标准错误 是两个不同的 fd,现在只重定向了 标准输出 1

结果
标准错误 也进行 重定向,打印内容至 file.txt

结果
标准输出 打印至 file.txt 中,标准错误 打印至 log.txt

结果

以上只是简单演示一下如何通过命令行进行 重定向,在实际开发中进行重定向操作时,使用的是函数 dup2

2.3、利用函数重定向

系统级接口 int dup2(int oldfd, int newfd)

演示

函数解读:将老的 fd 重定向为新的 fd,参数1 oldfd 表示新的 fd,而 newfd 则表示老的 fd,重定向完成后,只剩下 oldfd,因为 newfd 已被覆写为 oldfd 了;如果重定向成功后,返回 newfd,失败返回 -1

参数设计比较奇怪,估计作者认为 newfd 表示重定向后,新的 fd

下面来直接使用,模拟实现报错场景,将正常信息输出至 log.normal,错误信息输出至 log.error

#include <iostream>
#include <cstdlib>
#include <cerrno>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

using namespace std;

int main()
{
    //打开两个目标文件
    int fdNormal = open("log.normal", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    int fdError = open("log.error", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    assert(fdNormal != -1 && fdError != -1);

    //进行重定向
    int ret = dup2(fdNormal, 1);
    assert(ret != -1);
    ret = dup2(fdError, 2);
    assert(ret != -1);

    for(int i = 10; i >= 0; i--)
        cout << i << " ";  //先打印部分信息
    cout << endl;

    int fd = open("cxk.txt", O_RDONLY); //打开不存在的文件
    if(fd == -1)
    {
        //对于可能存在的错误信息,最好使用 perror / cerr 打印,方便进行重定向
        cerr << "open fail! errno: " << errno << " | " << strerror(errno) << endl;
        exit(-1);   //退出程序
    }

    close(fd);

    return 0;
}

结果

在开发大型项目时,将 错误信息 单独剥离出来是一件很重要的事

学习了 重定向 相关知识后,我们可以对 【简易版 bash】 进行功能更新(已于 2023.3.28 更新)


3、缓冲区

3.1、缓冲区存在的意义

在【基础IO】 中还存在一个重要概念:缓冲区

缓冲区 其实就是一个 buffer 数组,配合不同的刷新策略,起到提高 IO 效率的作用

感性理解:

假设你家养有一条二哈,当你在投喂食物时,如果你每次都只往嘴里丢入一粒狗粮,那么你的整个喂食过程将持续非常长的时间,这已经严重影响了你写代码的时间,你一天啥都不做,就光喂狗去了;于是你想了一个办法:给它安排了一个狗碗(缓冲区),每次都只需将狗粮倒入其中,等待它自己进食即可,这样一来不但提高了二哈的进食效率,同时也给你写代码留足了时间;类似的例子还有很多,比如如果没有垃圾桶,那么你扔的每个垃圾都得跑到垃圾站中去处理;如果货车没有货箱,那么一批货得拉好几天;如果手机没有电池,那么再高级的功能也摆脱不了充电线的桎梏

图解

理性理解:
CPU 计算速度非常快!而磁盘的读取速度相对于 CPU 来说是非常非常慢的,因此需要先将数据写入缓冲区中,依据不同的刷新策略,将数据刷新至内核缓冲区中,供 CPU 进行使用,这样做的是目的是尽可能的提高效率,节省调用者的时间

本来 IO 就慢,如果没有缓冲区的存在,那么速度会更慢,下面通过一个代码来看看是否进行 IO 时,CPU 的算力差距

#include <iostream>
#include <unistd.h>
#include <signal.h>

using namespace std;

int count = 0;

int main()
{
    //定一个 1 秒的闹钟,查看算力
    alarm(1);   //一秒后闹钟响起
    while(true)
    {
        cout << count++ << endl;
    }
    return 0;
}

最终在 1s 内,count 累加了 10w+ 次(有 IO 的情况下)
闹钟

下面改变程序,取消 IO

int count = 0;

void handler(int signo)
{
    cout << "count: " << count << endl;
    exit(1);
}

int main()
{
    //定一个 1 秒的闹钟,查看算力
    signal(14, handler);
    alarm(1);   //一秒后闹钟响起
    while(true) count++;
    
    return 0;
}

最终在没有 IO 的情况下,count 累加了 5亿+ 次,由此可以看出频繁 IOCPU 计算的影响有多大,假若没有缓冲区,那么整个累加值将会更多(因为需要花费更多的时间在 IO 上)

结果

因此在进行 读取 / 写入 操作时,常常会借助 缓冲区 buffer

#include <iostream>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

using namespace std;

int main()
{
    int fd = open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    assert(fd != -1);

    char buffer[256] = { 0 };   //缓冲区
    int n = read(0, buffer, sizeof(buffer));    //读取信息至缓冲区中
    buffer[n] = '\0';

    //写入成功后,在写入文件中
    write(fd, buffer, strlen(buffer));

    close(fd);
    return 0;
}

结果

3.2、缓冲区刷新策略

缓冲区有多种刷新策略,比如 C语言scanf 的缓冲区刷新策略为:遇到空白字符或换行就刷新,因此在输入时需要按一下回车,缓冲区中的数据才能刷新至内核缓冲区中,而 printf 的刷新策略为 行缓冲,即遇到 \n 才会进行刷新

总体来说,缓冲区的刷新策略分为以下三种:

  1. 无缓冲 -> 没有缓冲区
  2. 行缓冲 -> 遇到 \n 才进行刷新,一次冲刷一行
  3. 全缓冲 -> 缓冲区满了才进行刷新

一般而言,显示器的刷新策略为 行缓冲,而普通文件的刷新策略为 全缓冲

一个简单的 demo 观察 行缓冲

#include <iostream>
#include <unistd.h>

using namespace std;

int main()
{
    while(true)
    {
        //未能触发行缓冲的刷新策略,只能等缓冲区满了被迫刷新
        printf("%s", "hehehehe");
        sleep(1);
    }

    return 0;
}

运行结果:无内容打印

结果
稍微改一下代码

while(true)
{
    //能触发行缓冲的刷新策略
    printf("%s\n", "hehehehe");
    sleep(1);
}

运行结果:每隔一秒,打印一次

结果

3.3、普通缓冲区与内核级缓冲区

每一个 file 对象中都有属于自己的缓冲区及刷新策略,而在系统中,还存在一个内核级缓冲区,这个缓冲区才是 CPU 真正进行 IO 的区域

IO 流程:

  • 先将普通缓冲区中的数据刷新至内核级缓冲区中,CPU 再从内核级缓冲区中取数据进行运算,然后存入内核级缓冲区中,最后再由内核级缓冲区冲刷给普通缓冲区

图解
出自知乎 《Linux 实现原理 — I/O 处理流程与优化手段》

这里有一段比较有意思的代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <unistd.h>

using namespace std;

int main()
{
    fprintf(stdout, "hello fprintf\n");
    const char* str = "hello write\n";
    write(1, str, strlen(str));

    fork(); //创建子进程
    return 0;
}

当我们直接运行程序时,结果如下:

结果

而当我们进行重定向后,结果如下:

结果

重定向前后出现两种截然不同的打印结果

原因分析:

  • 显示器刷新策略为 行缓冲,而普通文件为 全缓冲
  • 直接运行程序时:此时是向 显示器 中打印内容,因为有 \n,所以两条语句都直接进行了冲刷
  • 进行重定向后:此时是向 普通文件 中打印内容,因为普通文件是写满后才能刷新,并且 fprintf 有属于自己的缓冲区,这就导致 fork() 创建子进程后,父子进程的 fprintf 缓冲区中都有内容,当程序运行结束后,统一刷新,于是就是打印了两次 hello fprintf

注:系统级接口是没有自己的缓冲区的,直接冲刷至内核级缓冲区中,比如 write,所以创建子进程对 write 的冲刷没有任何影响

C语言 中的 FILE 类型设计还是比较复杂的,需要考虑很多种情况,不过本质上都是在调用系统级接口,我们现在已经可以模拟实现一个简易版 myFILE 结构体了,具体实现步骤将在下文中揭晓


🌆总结

以上就是本次有关 Linux 基础IO【重定向及缓冲区理解】的全部内容了,在这篇文章中,我们深入理解了文件描述符的概念,学习了重定向的多种方法,最后还学习了缓冲区的相关知识,清楚了普通文件与特殊文件的不同刷新策略。如果你对于本文中所提到的知识点还有疑惑的话,欢迎在评论区或私信中发表你的看法,我们可以相互探讨学习


星辰大海

相关文章推荐

Linux基础IO【文件理解与操作】

Linux【模拟实现简易版bash】

Linux进程控制【进程程序替换】

Linux进程控制【创建、终止、等待】

===============

Linux进程学习【进程地址】

Linux进程学习【环境变量】

Linux进程学习【进程状态】

Linux进程学习【基本认知】

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

跨平台Office文档预览原生插件,非腾讯X5,支持离线,稳定高可用

引言 2023年4月13日零时起&#xff0c;腾讯浏览服务内核文档能力正式下线&#xff0c;要实现真正离线文档预览&#xff0c;于是有了这边文章。 前面写了多篇关于<跨平台文件在线预览解决方案>&#xff0c;不管使用pdf.js、LibreOffice&#xff0c;还是永中DCS&#xff…

单列文本数据快速导入表格

文本数据导入Excel似乎是个老生常谈&#xff0c;方法也有很多&#xff0c;例如 使用文本编辑器打开文本文件&#xff0c;拷贝粘贴到Excel然后分类Power Query中的【从文本/CSV】如下图所示。 但是这个需求略有不同&#xff0c;文本数据为单列&#xff0c;每7行数据为一组&am…

MYSQL-数据库管理(下)

查看数据库信息 show database 查看数据库中的表信息 use 数据库名 #切换到书库中 show tables show tables in mysql 显示数据表的结构&#xff08;字段&#xff09; describe user; Field:字段名称 type:数据类型 Null :是否允许为空 Key :主键 Type:数据类型 Null :是否…

缓存空间优化实践

导读 缓存 Redis&#xff0c;是我们最常用的服务&#xff0c;其适用场景广泛&#xff0c;被大量应用到各业务场景中。也正因如此&#xff0c;缓存成为了重要的硬件成本来源&#xff0c;我们有必要从空间上做一些优化&#xff0c;降低成本的同时也会提高性能。 下面以我们的案…

【Git】Gitee免密push(TencentCloudLinux)

前提&#xff1a; 我用的是腾讯云的Centos(Linux)服务器 我创建好了仓库 我配置过git 可以正常用密码push 以上自行解决 我们直接配置公钥解决免密push 1.在服务器上创建公钥 在用户根目录创建 公钥 邮箱写自己的 随意写 我写的是gitee绑定的邮箱 ssh-keygen -t ed25519 -C…

数据结构(六)—— 二叉树(2)遍历

文章目录 递归三要素一、深度优先遍历&#xff08;前中后序&#xff09;1.1 递归遍历1.1.1 前序&#xff08;中左右&#xff09;1.1.2 中序&#xff08;左中右&#xff09;1.1.3 后序&#xff08;左右中&#xff09; 1.2 迭代遍历1.2.1 前序1.2.2 后序1.2.3 中序 二、广度优先遍…

Renesas瑞萨A4M2和STM32 CAN通信

刚好拿到一块瑞萨开发板&#xff0c;捣鼓玩下CAN&#xff0c;顺便试下固件升级。 A4M2 工程创建 详细可以参考&#xff0c;我之前写的文章 Renesa 瑞萨 A4M2 移植文件系统FAT32 CAN0 配置信息&#xff0c;使能FIFO&#xff0c;接收标准帧 ID为0x50&#xff0c;数据帧。 代…

密码学【java】初探究加密方式之对称加密

文章目录 一 常见加密方式二 对称加密2.1 Cipher类简介2.2 Base算法2.3 补充&#xff1a;Byte&bit2.4 DES加密演示2.5 DES解密2.6 补充&#xff1a;对于IDEA控制台乱码的解决方法2.7 AES加密解密2.8 补充&#xff1a; toString()与new String ()用法区别2.9 加密模式2.9.1 …

内网渗透(六十二)之 NTLM Realy 攻击

NTLM Realy 攻击 NTLM Realy 攻击其实应该称为Net-NTLM Realy 攻击,它发生在NTLM认证的第三步,在Response 消息中存在Net-NTLM Hash,当攻击者获得了 Net-NTLM Hash 后,可以重放Net-NTLM Hash 进行中间人攻击。 NTLM Realy 流程如图所示,攻击者作为中间人在客户端和服务器…

【C++】异常,你了解了吗?

在之前的C语言处理错误时&#xff0c;会通过assert和错误码的方式来解决&#xff0c;这导致了发生错误就会直接把程序关闭&#xff0c;或者当调用链较长时&#xff0c;就会一层一层的去确定错误码&#xff0c;降低效率&#xff0c;所以c针对处理错误&#xff0c;出现了异常&…

奥斯汀独家对话|从机构的「拉扯」中成长的美国加密监管

‍前言 4月25日&#xff0c;在美国得克萨斯州的首府奥斯汀&#xff0c;这座充满活力和创造力的城市&#xff0c;欧科云链研究院与来自哥伦比亚商学院的Austin Campbell教授就美国加密监管以及其相关话题进行了一次深入探讨。双方讨论了美国整体的监管问题、监管逻辑、最新的稳…

git把我本地文件传到我的指定的仓库

在使用Git将本地文件推送到指定仓库之前&#xff0c;请确保已经安装了Git并进行了基本配置。接下来&#xff0c;遵循以下步骤将本地文件推送到远程仓库&#xff1a; 兄弟先赏析悦目一下&#xff0c;摸个鱼 首先&#xff0c;在本地文件夹中打开命令行界面&#xff08;在Windows上…

SpringBoot整合Mybatis-Plus、Jwt实现登录token设置

Spring Boot整合Mybatis-plus实现登录常常需要使用JWT来生成用户的token并设置用户权限的拦截器。本文将为您介绍JWT的核心讲解、示例代码和使用规范&#xff0c;以及如何实现token的生成和拦截器的使用。 一、JWT的核心讲解 JWT&#xff08;JSON Web Token&#xff09;是一种…

【P1】Jmeter 准备工作

文章目录 一、Jmeter 介绍1.1、Jmeter 有什么样功能1.2、Jmeter 与 LoadRunner 比较1.3、常用性能测试工具1.4、性能测试工具如何选型1.5、学习 Jmeter 对 Java 编程的要求 二、Jmeter 软件安装2.1、官网介绍2.2、JDK 安装及环境配置2.3、Jmeter 三种模式2.4、主要配置介绍2.4.…

外卖项目优化-02-

文章目录 瑞吉外卖项目优化-Day02课程内容前言1. MySQL主从复制1.1 介绍1.2 搭建1.2.1 准备工作1.2.2 主库配置1.2.3 从库配置 1.3 测试 2. 读写分离案例2.1 背景介绍2.2 ShardingJDBC介绍2.3 数据库环境2.4 初始工程导入2.5 读写分离配置2.6 测试 3. 项目实现读写分离3.1 数据…

HTML(四) -- 多媒体设计

目录 1. 视频标签 2. 音频标签 3. 资源标签&#xff08;定义媒介资源 &#xff09; 1. 视频标签 属性值描述autoplayautoplay如果出现该属性&#xff0c;则视频在就绪后马上播放。controlscontrols表示添加标准的视频控制界面&#xff0c;包括播放、暂停、快进、音量等…

【LLM】离线部署ChatGLM-6B模型

目录 前言 准备环境 打包环境 下载/上传模型 部署模型 前言 甲方出手&#xff0c;天下我有&#x1f929;。圆梦了圆梦了~一直想整一台GPU服务器尝尝鲜&#xff0c;奈何钱包空空&#xff0c;虽然有可以在CPU上部署的方案&#xff0c;但效果却不是让人那么满意&#xff0c…

跟着杰哥学强化学习:多臂老虎机问题

多臂老虎机问题 现在有3台外观一模一样的老虎机,每个老虎机的赔率是不同的,摇动一次需要1块钱,现在给你100块钱,如何获取最大的收益。 如果我们知道了每个老虎的赔率,那么只要选择收益最高的那个老虎机就可以了,但现在问题是并不知道每个老虎机的收益。为了简单,我们假…

忆暖行动|“他一个人推着老式自行车在厚雪堆的道路上走,车上带着学生考试要用的司机”

忆暖行动|“他一个人推着老式自行车在厚雪堆的道路上走&#xff0c;车上带着学生考试要用的sj” 一头白发&#xff0c;满山青葱 在那斑驳的物件褶皱中&#xff0c;透过泛黄的相片&#xff0c;掩藏着岁月的冲刷和青葱的时光。曾经的青年早已经不复年轻&#xff0c;但是那份热爱…

Nachos系统的上下文切换

Fork调用创建进程 在实验1中通过gdb调试初步熟悉了Nahcos上下文切换的基本流程&#xff0c;但这个过程还不够清晰&#xff0c;通过源码阅读进一步了解这个过程。 在实验1中通过执行Threadtest&#xff0c;Fork创建子进程&#xff0c;并传入SimpleThread执行currentThread->…