【Linux】进程退出 | 初始缓冲区 | 子进程回收(六)

目录

前言:

一、main函数的返回值

二、退出码有什么用?

三、perror/strerror/erron

四、erron变量

五、exit函数 

六、_exit变量

七、初始缓冲区

八、wait函数和folk函数的返回值

九、父进程获取子进程退出信息waitpid函数

1.返回值

2.第一个参数(pid_t pid)

3.第二个参数(int* status)

3.1重谈进程退出

3.2观察进程退出信号

3.3WIFEXITED(status)宏和WEXITSTATUS(status)宏

4.第三个参数(int options)非阻塞等待

5.参数总览 

总结:


前言:

我们已经知道了很多的进程属性,还有进程调度算法,进程地址空间和写时拷贝等等(这是一系列文章,大家感兴趣可以在我的主页追剧,零基础就从基础命令开始看起)。本篇来聊一聊一个进程退出时肯定也需要让OS知道其是如何退出的,包括我们经常说到的缓冲区,我们这篇先来对缓冲区有一个初步的认识,这部分知识很重要!

一、main函数的返回值

main函数的返回值是什么?是返回给父进程/OS的。我们写一个C++代码,并执行,之后echo $?

我们已经在关于进程的第二篇讲过echo $?是用来查看上次进程退出信息的。

二、退出码有什么用?

退出码有什么用?表明错误原因:0表示成功,非0表示错误。

用不用的数字表明不同的原因。系统提供了一批错误码,约定退出码(C/C++提供)。

三、perror/strerror/erron

各位如果学过C,是否还记得里面的这几个内容,我们先使用系统手册来查看这三个是什么东西:

这里提前再次声明系统手册:

由于这几个函数/变量都是C语言的,所以查3号手册。

man 3 perror

有可能你使用Xshell连接会有显示问题,这时候推荐在vim编译器中打开查看,在底行模式中(一直点Esc,之后输入":"(冒号)),输入以下命令:

!man 3 perror

strerror函数是将erron传入,之后返回字符串,打印错误信息。 

erron是一个全局变量,是一个错误码。其在errno.h头文件中。

perror = printf + strerror

我们举一个perror的例子:

#include<stdio.h>
#include<stddef.h>
#include <stdlib.h>
#include <errno.h>

int main() {
    FILE* file = fopen("non_existent_file.txt", "r");
    if (file == NULL) {
        perror("Error opening file");
        return EXIT_FAILURE;
    }

    // 文件操作...

    fclose(file);
    return EXIT_SUCCESS;
}

注意程序最后的"代码为1"是获取的erron

四、erron变量

我们举一个栗子:

打开一个文件以读的方式,这个文件并不存在,所以会报错,我们在文件读取前打印错误码和错误信息;在文件读取后打印错误码和错误信息

#include<iostream>
#include<string.h>
#include<cstdio>
#include<error.h>

int main()
{
    printf("before: errno: %d, errstring: %s\n", errno, strerror(errno));

    FILE *fp = fopen("./log.txt", "r");
    if (fp == NULL)
    {
        printf("after: errno: %d, errsting: %s\n", errno, strerror(errno));
        return errno;
    }
    return 10;
}

错误码(errno)就是给系统/父进程看的,错误信息(strerror)是给人看的。我们不知道一共有多少个错误信息,这里更改代码,上限为200全部打印一遍。

#include<iostream>
#include<string.h>
#include<cstdio>
#include<error.h>

int main()
{
    for (int i = 0; i < 200; i++)
    {
        std::cout << "code: " << i << ",errstring: " << strerror(i) << std::endl; 
    }
}

里面可以看到我们很常见的错误信息。 我们可以演示一下,比如错误码2(code: 2,errstring: No such file or directory):

五、exit函数 

我们今天来正式认识一下exit函数,我们编写process.cc文件,调用一个函数,打印之后执行exit(100):

所以exit里面接受的也就是错误码,这就意味着退出码可以自定,不必要一定使用系统的退出码。

我们将函数的exit修改为return,再次编译执行观察结果:

所以可以可以得出结论,在代码的任何地方调用exit表示进程结束。 

六、_exit变量

我们来认识一个新的变量:_exit。

它的用法和exit的使用方法几乎一致。

可以看出这是Linux的系统操作手册中的变量。

这两个是有区别的,还是否记得我们之前有缓冲区的概念?exit会将缓冲区的内容刷新,而_exit不会将缓冲区的内容刷新

七、初始缓冲区

这里直接上图:

对于缓冲区的详细知识,我们后期会详细讲解,这里先抛砖引玉。 

八、wait函数和folk函数的返回值

大家是否还记得,当子进程死亡以后,没有人回收它,子进程就会僵尸,等待父进程回收,今天我们就要完善这个代码,让父进程回收它。

我们先来看folk函数的返回值:

我们先用一段代码说明一下,这段代码创建子进程,如果folk创建失败返回-1,对父进程返回子进程pid;对子进程返回0,我们让子进程一共打印3次信息,并退出时返回0(exit返回),父进程死循环打印:

#include<iostream>
#include<string.h>
#include<cstdio>
#include<error.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
    pid_t id = fork();
    if (id < 0)
    {
        printf("errno: %d, errstring: %s\n", errno, strerror(errno));
        return errno;   
    }
    else if (id == 0) 
    {
        int cnt = 3;
        while (cnt)
        {
            printf("子进程运行中, pid: %d\n", getpid());
            sleep(1);
            cnt--;
        }

        exit(0);
    }
    else 
    {
        //父进程
        while(true)
        {
            printf("我是父进程:pid: %d\n", getpid());
            sleep(1);
        }
    }
    return 0;
}

并再开一个窗口进行监视。

 还是否记得我们循环检测进程的语句?

while :; do ps ajx | head -1 ; ps ajx | grep process | grep -v grep; sleep 1; done

小提示:当XShell发生BUG时,特别是vim编辑器会发生BUG,可以查看进程那些启动了。杀死一些无关的vim进程。

此时父进程并没有回收子进程,子进程处于僵尸状态。 

对于创建的子进程,父进程就必须等待子进程(回收子进程),当子进程变成僵尸之后,父进程就要对其回收,使用系统提供的wait函数回收。在父进程中写入wait函数使其等待。

在父进程等待期间,子进程不退出,父进程就需要阻塞在wait函数内部,类似于scanf。

wait等待任意一个子进程,返回值大于0就是等待成功,返回的是子进程的PID返回-1就是等待失败,可能是没有子进程造成的

int main()
{
    pid_t id = fork();
    if (id < 0)
    {
        printf("errno: %d, errstring: %s\n", errno, strerror(errno));
        return errno;   
    }
    else if (id == 0) 
    {
        int cnt = 3;
        while (cnt)
        {
            printf("子进程运行中, pid: %d\n", getpid());
            sleep(1);
            cnt--;
        }

        exit(0);
    }
    else 
    {
        //父进程
        pid_t rid = wait(nullptr); //这里调用wait函数
        if (rid > 0) 
        {
            printf("wait sub process success, rid = %d\n", rid);
        }
        while(true)
        {
            printf("我是父进程:pid: %d\n", getpid());
            sleep(1);
        }
    }
    return 0;
}

九、父进程获取子进程退出信息waitpid函数

但是还有问题,父进程等待完毕以后,肯定也要获取子进程的对应退出信息,也就是想知道子进程完成任务到底怎样,所以我们平时使用的函数是waitpid函数。

1.返回值

我们先来介绍一下waitpid的返回值:
> 0: 等待成功,返回的是子进程pid

== 0: 等待成功,但是子进程没有退出(重要)

< 0: 等待失败

2.第一个参数(pid_t pid)

我们先来从第一个参数开始了解:

所以我们将代码做一些调整:

#include<iostream>
#include<string.h>
#include<cstdio>
#include<error.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id = fork();
    if (id < 0)
    {
        printf("errno: %d, errstring: %s\n", errno, strerror(errno));
        return errno;   
    }
    else if (id == 0) 
    {
        int cnt = 3;
        while (cnt)
        {
            printf("子进程运行中, pid: %d\n", getpid());
            sleep(1);
            cnt--;
        }

        exit(0);
    }
    else 
    {
        //父进程
        //pid_t rid = wait(nullptr);
        //pid_t rid = waitpid(-1, nullptr, 0); // == wait
        pid_t rid = waitpid(id, nullptr, 0); //返回给父进程的本身就是子进程的pid
        if (rid > 0) 
        {
            printf("wait sub process success, rid = %d\n", rid);
        }
        while(true)
        {
            printf("我是父进程:pid: %d\n", getpid());
            sleep(1);
        }
    }
    return 0;
}

当然,我们也可以查看进程等待失败的情况,将waitpid的id值+1观察情况:

3.第二个参数(int* status)

waitpid第二个参数status,它会帮助父进程获取子进程的退出信息。

我们定义一个status变量,并传入,之后将子进程的退出码设置为1,并打印status。

#include<iostream>
#include<string.h>
#include<cstdio>
#include<error.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id = fork();
    if (id < 0)
    {
        printf("errno: %d, errstring: %s\n", errno, strerror(errno));
        return errno;   
    }
    else if (id == 0) 
    {
        int cnt = 3;
        while (cnt)
        {
            printf("子进程运行中, pid: %d\n", getpid());
            sleep(1);
            cnt--;
        }

        exit(1); //设置退出码为1
    }
    else 
    {
        //父进程
        //pid_t rid = wait(nullptr);
        //pid_t rid = waitpid(-1, nullptr, 0); // == wait
        int status = 0;
        pid_t rid = waitpid(id, &status, 0); //返回给父进程的本身就是子进程的pid
        if (rid > 0) 
        {
            printf("wait sub process success, rid = %d, status = %d\n", rid, status);
        } 
        else 
        {
            perror("waitpid"); //等待失败的情况
        }
        while(true)
        {
            printf("我是父进程:pid: %d\n", getpid());
            sleep(1);
        }
    }
    return 0;
}

但结果并不是1,而是256。

因为status不仅仅包含进程退出码,我们写的exit和_exit都是程序正常退出,但是程序只会正常退出吗?

进程结束分为两种:

1.正常结束

2.异常结束

因为代码可能跑不到exit和return,在中途可能会有野指针段错误等直接就崩溃了,退出码也就没有意义了。

所以异常退出也属于进程退出的信息

所以status看起来是一个整数,实际上是一个位图,一共32位:

status低16为的次8位记录退出码,所以我们这样修改代码,通过位运算获取退出码:

if (rid > 0) 
{
    printf("wait sub process success, rid = %d, status code = %d\n", rid, (status >> 8) & 0xFF);
}

你可能会问,子进程可不可以通过一个全局变量把退出码给父进程,这是不行的,因为修改全局变量之后,发生写时拷贝,即使地址一样,父进程也拿不到,更好验证了进程具有隔离性,只能通过系统调用函数来完成。

3.1重谈进程退出

1.代码跑完了,结果对,return 0。

2.代码跑完了,结果不对,return !0。

3.进程异常了。

只要进程出错,OS必须知道,一般就会把这个进程通过信号干掉(以后我们可以有方法让它不崩掉退出)。3退出进程退出信息中,会记录下来自己的退出信号。

status低7位表示退出信号的值

所以退出码范围为[0, 255],退出信号值范围为[0, 127]。

3.2观察进程退出信号

接下来我们再次修改代码,将进程的退出信号也打印出来观察:

if (rid > 0) 
{
    printf("wait sub process success, rid = %d, status code = %d, exit signal: %d\n", rid, (status >> 8) & 0xFF, status & 0x7F);
} 

接下来我们在子进程中制造几种错误:

我们先来观察一个正常的程序跑出野指针错误会报什么错误。 新写一个.c文件:

其实这个即使OS发送了 11) SIGSEGV 这个信号。

我们验证一下,写一个死循环程序并给他发送这个信号:

 使用之前子进程的指针问题代码打印出来其OS发送的信号并验证信号:

再看信号列表中的字段:

进程的退出信息会维护在PCB中。

3.3WIFEXITED(status)宏和WEXITSTATUS(status)宏

其实写成位操作去观察退出码的方法并不优雅,系统提供了宏可以直接查看。

WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码) 

我们一般将其两者连用,所以我们再次修改代码:

int main()
{
    pid_t id = fork();
    if (id < 0)
    {
        printf("errno: %d, errstring: %s\n", errno, strerror(errno));
        return errno;   
    }
    else if (id == 0) 
    {
        int cnt = 3;
        while (cnt)
        {
            printf("子进程运行中, pid: %d\n", getpid());
            sleep(1);
            cnt--;
        }

        exit(1); //设置退出码为1
    }
    else 
    {
        //父进程
        //pid_t rid = wait(nullptr);
        //pid_t rid = waitpid(-1, nullptr, 0); // == wait
        int status = 0;
        pid_t rid = waitpid(id, &status, 0); //返回给父进程的本身就是子进程的pid
        if (rid > 0) 
        {
            if (WIFEXITED(status))
            {//程序正常退出
                printf("wait sub process success, rid: %d, status code: %d\n", rid, WEXITSTATUS(status));//打印退出码
            }
        } 
        else 
        {
            perror("child process quit error\n"); //等待失败的情况
        }
        while(true)
        {
            printf("我是父进程:pid: %d\n", getpid());
            sleep(1);
        }
    }
    return 0;
}

所以回答最开始的问题,为什么"退出码"是256?因为退出码是1,信号是0(因为进程正常退出,系统发送信号0),所以第9位是1,低8位是0,所以是256。

我们一直在讲创建子进程,但是我们创建出来子进程以后就只是完成了打印,并没有做一些实质上的任务,而且我们说过退出码可以由程序员自行约定,接下来我们写一个小项目来说明。

我们一直在添加数据(在vector中添加数据),之后每隔10s都要向一个文件中写入,这个文件以时间戳命名。在备份的时候,不能影响主逻辑,也就是说备份时主进程数据还在添加(其实还是阻塞等待了,但是这里演示一下)。

#include<iostream>
#include<vector>
#include<cstdio>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

std::vector<int> data;

void Save()
{
    //创建子进程
    pid_t id = fork();
    if (id == 0)
    {
        //子进程
        exit(0);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid > 0)
    {
        printf("wait child sucee, exit code: %d\n", WEXITSTATUS(status));
    }
    else 
    {
        perror("waitpid");
    }
}

int main()
{
    int cnt = 1;
    while(true)
    {
        data.push_back(cnt++);
        sleep(1);
        
        if (cnt % 10 == 0)
        {
            Save();
        }
    }
    return 0;
}

此时子进程就要开始写文件。 这时候我们定义一个SaveBegin的函数,因为要写文件,所以文件可能会打开失败,所以可以使用枚举常量来自行约定退出码:

#include<iostream>
#include<vector>
#include<cstdio>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

std::vector<int> data;

enum CLASS
{
    OK,
    OPEN_FILE_ERROR
};

const std::string sep = " ";

int SaveBegin()
{
    //获取时间 以时间命名文件
    std::string name = std::to_string(time(nullptr));
    name += ".backup";
    FILE* fp = fopen(name.c_str(), "w"); //以写的方式

    //文件打开失败
    if (fp == nullptr)
    {
        return OPEN_FILE_ERROR;
    } 
    
    std::string dataStr;
    for (auto d : data)
    {
        dataStr += std::to_string(d);
        dataStr += sep;
    }
    fputs(dataStr.c_str(), fp);
    
    fclose(fp);
    return OK;
}

void Save()
{
    //创建子进程
    pid_t id = fork();
    if (id == 0)
    {
        //子进程
        int code = SaveBegin();
        exit(code);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid > 0)
    {
        int code = WEXITSTATUS(status);
        if (code == 0) printf("备份成功, exit code: %d\n", code);
        else printf("备份失败, exit code: %d\n", code);
    }
    else 
    {
        perror("waitpid");
    }
}

int main()
{
    int cnt = 1;
    while(true)
    {
        data.push_back(cnt++);
        sleep(1);
        
        if (cnt % 10 == 0)
        {
            Save();
        }
    }
    return 0;
}

其实有两种等待方式,我们刚才以阻塞等待方式完成,父进程其实还是在等待子进程。还可以非阻塞等待,这和waitpid第三个参数有关。

4.第三个参数(int options)非阻塞等待

非阻塞等待由我们自己循环调用非阻塞接口,完成轮询检测,让父进程可以做更多自己的事情。

服务器中,当其卡住时我们称其hang住了;当其崩溃我们称其宕机了。

我们先举一个非阻塞的例子:

int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        //子进程
        while (true)
        {
            printf("我是子进程, pid: %d\n", getpid());
            sleep(1);
        }
        exit(0);    
    }

    //父进程
    while(true)
    {
        sleep(1);
        pid_t rid = waitpid(id, nullptr, WNOHANG);
        if (rid > 0)
        {
            printf("等待子进程 %d 成功\n", rid);
            break;
        }
        else if (rid < 0)
        {
            printf("等待子进程失败\n");
            break;
        }
        else 
        {
            printf("子进程尚未退出\n");
        }
    }

    return 0;
}

5.参数总览 

总结:

我们又学习了很多新内容,进程退出信息非常重要,明白了进程退出时需要向上级汇报工作,也知道了一些关于缓冲区的知识,但是这些只是还是凤毛麟角,我们接下来要讲解进程替换和其他重磅内容,大家继续追剧哦!

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

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

相关文章

【vscode-解决方案】vscode 无法登录远程服务器的两种解决办法

解决方案一&#xff1a; 查找原因 命令 ps ajx | grep vscode 可能会看到一下这堆信息&#xff08;如果没有大概率不是这个原因导致&#xff09; 这堆信息的含义&#xff1a;当你使用 vscode 远程登录服务器时&#xff0c;我们远程机器服务端要给你启动一个叫做 vscode serv…

制氧机分子筛的材质选择与解析‌

制氧机中的分子筛&#xff0c;是一种可以在分子水平上筛选物质的多孔材料。这种材料的主要成分是人工合成的晶体铝硅酸盐&#xff0c;也被称为沸石材料。 二、常用分子筛材质分析 1. 沸石分子筛 沸石分子筛是目前制氧机中最常用的材质之一。它具有以下显著优点&#xff1a; ‌吸…

如何把网络ip改为动态:全面指南

在数字化时代&#xff0c;网络IP地址作为设备在网络中的唯一标识&#xff0c;扮演着至关重要的角色。随着网络环境的不断变化&#xff0c;静态IP地址的局限性逐渐显现&#xff0c;而动态IP地址则因其灵活性和安全性受到越来越多用户的青睐。那么&#xff0c;如何把网络IP改为动…

如何在docker上部署java服务

目录结构 首先 Dockerfile FROM bladex/alpine-java:openjdk17_cn_slimMAINTAINER admin@rsz.comENV TZ=Asia/ShanghaiRUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezoneRUN mkdir -p /xhWORKDIR /xhEXPOSE 8106ADD ./blade-system.…

进行性核上性麻痹患者的生活护理指南

进行性核上性麻痹是一种神经系统退行性疾病&#xff0c;合理的生活护理能有效改善症状&#xff0c;提高生活质量。 居家环境要安全。移除地面杂物&#xff0c;铺设防滑垫&#xff0c;安装扶手&#xff0c;降低跌倒风险。在浴室、厨房等湿滑区域要特别加强防护措施。建议在床边、…

基于Springboot博物馆文博资源库系统【附源码】

基于Springboot博物馆文博资源库系统 效果如下&#xff1a; 系统登陆页面 文物信息管理页面 流动申请页面 文物报修页面 个人信息页面 文物保修管理页面 系统主页面 文物类型页面 研究背景 随着信息技术的飞速发展&#xff0c;博物馆文博资源的管理与利用日益受到重视。传统…

在Linux环境下利用MTCNN进行人脸检测(基于ncnn架构)

概述 本文将详细介绍如何在Linux环境下部署MTCNN模型进行人脸检测&#xff0c;并使用NCNN框架进行推理。 1. CMake的安装与配置 下载CMake源码 前往CMake官网下载&#xff0c;找到适合您系统的最新版本tar.gz文件链接&#xff0c;或者直接通过wget下载&#xff1a;CMake官方…

Ruby 数组(Array)

一、参数 符号赋值数组对象 时&#xff0c;是指向同一个数组对象 eg: irb(main):019:0> a [] //定义一个数组 > [] irb(main):020:0> b a //将变量a赋值给b&#xff0c;a和b指向同个内存 > [] irb(main):021:0> a.object_id //a的对象id > 25204596 …

【无人机】无人机飞行日志下载及分析,飞行日志分析软件的使用

目录 一、飞行日志下载 1.1 通过地面站下载 1.1.1 QGroundControl&#xff08;QGC&#xff09;地面站 1.1.2 Mission Planner 地面站 1.2 通过内存卡读卡器下载 1.3 通过数传模块下载&#xff08;数传日志&#xff09; 二、飞行日志分析 2.1 使用 Flight Review 分析 …

Coredns延迟NodeLocalDNS解决之道

#作者&#xff1a;邓伟 文章目录 问题列表问题分析&#xff1a;问题分析解决方案详情方案验证部署步骤验证结论回滚方案回滚验证注意事项NodeLocalDNS介绍 问题列表 近来发现K8s频繁出现5s超时问题&#xff0c;业务反馈收到一定影响&#xff0c;问题包括&#xff1a; coredn…

由堆栈异常引发的一系列问题启发

文章目录 背景分析现象排查定位数据比对解决方案反思背景 背景:今天下午初始化经营体数据到预发环境,需要将生产环境的经营体表的数据同步到预发环境,同步结束后。再调用批量禁用接口,目的是将原来一级经营体【中国移动】下的所有经营体禁用掉,然后导入Excel文件将新的省…

[2/11]C#性能优化-不要使用空析构函数-每个细节都有示例代码

前言 在C#开发中&#xff0c;性能优化是提升系统响应速度和资源利用率的关键环节。 当然&#xff0c;同样是所有程序的关键环节。 通过遵循下述建议&#xff0c;可以有效地减少不必要的对象创建&#xff0c;从而减轻GC的负担&#xff0c;提高应用程序的整体性能。记住&#xf…

Llama-factory详细基础使用说明

最近很多小伙伴在使用llama-factory时遇到了不少问题&#xff0c;特别是基础不太好的童鞋&#xff0c;对各种参数或者webui界面的内容不理解&#xff0c;总是一会一个问题&#xff0c;网上大部分的教程&#xff0c;都只是教到能跑起来就行&#xff0c;对于细的调整没有做介绍&a…

excel单、双字节字符转换函数(中英文输入法符号转换)

在Excel中通常使用函数WIDECHAR和ASC来实现单、双字节字符之间的转换。其中 WIDECHAR函数将所有的字符转换为双字节&#xff0c;ASC函数将所有的字符转换为单字节 首先来解释一下单双字节的含义。单字节一般对应英文输入法的输入&#xff0c;如英文字母&#xff0c;英文输入法…

VS Code 如何搭建CC++开发环境

VS Code 如何搭建C/C开发环境 文章目录 VS Code 如何搭建C/C开发环境1. VS Code是什么2. VS Code的下载和安装2.1 下载和安装2.2 环境的介绍 3. VS Code配置C/C开发环境3.1 下载和配置MinGW-w64编译器套件3.2 安装C/C插件3.3 重启VS Code 4. 在VS Code上编写C语言代码并编译成功…

间隙波导阵列天线 学习笔记2 使用加载脊U型辐射层的一种Ka波段高效率圆极化卫星天线的发展

摘要&#xff1a; 本文提出了一种低剖面&#xff0c;高增益背腔槽阵列天线&#xff0c;基于RGW技术&#xff0c;工作在Ka频段。天线阵列包含两层。上层是一个U型槽阵列&#xff0c;在金属脊上腔体的位置上方&#xff0c;同时下层是RGW公共分布网络&#xff0c;给每个背腔槽等辐…

【软考-架构】1.1、计算机硬件-CPU校验码

GitHub地址&#xff1a;https://github.com/tyronczt/system_architect ✨资料&文章更新✨ 计算机硬件组成 中央处理单元 运算器 算术逻辑单元ALU&#xff08;实现对数据的算术和逻辑运算&#xff09;;累加寄存器AC&#xff08;运算结果或源操作数的存放区&#xff09;;数…

【Spring】Spring AOP原理

目录 前言 代理模式 静态代理 优缺点 动态代理 JDK动态代理 工作原理 JDK动态原理实现关键步骤 CGLib动态代理 CGLIB动态代理实现关键步骤 总结 前言 在上一篇中&#xff0c;我们讲解了什么是AOP&#xff0c;以及Spring AOP是如何使用的&#xff0c;那么本篇我们就…

SpringBoot五:JSR303校验

精心整理了最新的面试资料和简历模板&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 松散绑定 意思是比如在yaml中写的是last-name&#xff0c;这个和lastName意思是一样的&#xff0c;-后的字母默认是大写的 JSR303校验 就是可以在字段增加…

uniapp 系统学习,从入门到实战(六)—— 样式与布局

全篇大概 4700 字(含代码)&#xff0c;建议阅读时间 30min &#x1f4da; 目录 Flex 布局在 UniApp 中的应用响应式设计与适配多端使用 SCSS 提升样式开发效率实战案例演示总结 1. Flex 布局在 UniApp 中的应用 1.1 基础布局实现 通过 display: flex 快速构建弹性容器&#…