【Linux】进程等待 | 详解 wait/waitpid 的 status 参数

news2025/1/16 13:41:08

  🤣 爆笑教程 👉 《看表情包学Linux》👈 猛戳订阅  🔥

💭 写在前面:在上一章中我们讲解了进程创建与进程终止,本章我们开始讲解进程等待。进程等待这部分知识相较于前面还是较为复杂的,我会由浅入深地讲解这部分的知识点,值得一提的是在学习本章前需要掌握进程状态的知识,复习链接我会贴到下面。我们先谈论进程等待的必要性,简单地讲解 wait 函数,然后我们主要讲解 waitpid 函数。由于 wait 只有一个参数 status,且 waitpid 有三个参数且其中一个也是 status,我们本章重点讲解这个 status 参数。因为要控制篇幅,本章没办法讲清楚 waitpid 的其余参数(比如 options 参数),我们将放到后期讲解,所以算是对 waitpid 函数的一个初探,后面我们还会继续讲解它的。

🔗 前置知识:【看表情包学Linux】进程状态


Ⅰ. 进程等待(Process wait)

0x00 引入:进程等待的必要性

❓ 为什么要进行进程等待?不知道大家是否还记得我们在之前的章节讲过的 "僵尸进程" 的概念。

僵尸状态:当一个 \textrm{Linux} 中的进程退出的时候,一般不会直接进入 X 状态(死亡,资源可以立马被回收),而是进入 Z 状态。

子进程退出如果父进程不管不顾,就 可能造成僵尸进程的问题,一直占内存进而引发内存泄露。

"这是典型的占着茅坑不拉翔行为"

此外,进程一旦变成僵尸状态,就会变得刀枪不入,不可被杀,因为我们说过:

甚至 "杀进程不眨眼的" kill -9 也无法奈其何!

所以我们必须让其从 Z 状态变为 X 状态:

Z\rightarrow X

进而允许操作系统能去释放它(将代码和数据 free 掉,将相关数据结构归还给 slab 分派器)。

 上面我们讲的实际上就是我们需要进程等待的一个原因 —— 解决内存泄露问题 

然而不仅仅这一个原因,我们还需要进程等待来 获取子进程的退出状态

我们需要知道父进程派给子进程的任务完成的如何。

比如子进程运行完成,结果对还是不对,是否正常退出?

通过进程等待的方式,回收子进程资源,获得子进程退出信息。

(当然了,并不是所有的父进程需要关心子进程的。所以是存在父进程不等待子进程的情况的,处理的方案我们在之前也有所铺垫,该部分话题我们会在后期讲信号的时候进一步讲解。本章我们只关心父进程必须等待子进程的情况,解决僵尸进程问题让子进程尽快恢复,获取子进程的退出状态)

获取子进程的退出状态是否需要将曾经子进程的退出信息保存起来,然后被恢复、读取呢?

 这和我们刚才讲的进程退出有着大大的关系!我们知道了进程退出是有退出码的。

我们需要让子进程退出时它的 return 结果或者 exit 的结果是需要被父进程读到的。

🔺 总结:需要进程等待的原因:① 解决内存泄露问题     ② 获取子进程的退出状态

0x01 wait 函数

说得好,那么如何等待呢?

我们先来介绍一下 wait 函数。第一种方式,就是让父进程调用 wait 即可。

wait() 可以解决回收子进程 Z 状态,让子进程进入 X 状态。

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int* status);

我们看到,wait 有一个叫 status 的参数(我们暂时不说,下面讲 waitpid 后会详细讲解

我们下面验证一下这个函数是否可以 Z\rightarrow X,我们会把 status 暂且设置为空。

💬 验证:调用  函数回让父进程 "礼貌地" 等待子进程

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

int main (
    void
    ) 
{
    pid_t id = fork();
    if (id == 0) {
        // child
        while (1) {
            printf("我是子进程,我正在运行... Pid: %d\n", getpid());
            sleep(1);
        }
    }
    else {
        printf("我是父进程: pid: %d,我将耐心地等待子进程!\n", getpid());
        sleep(20);   // 为了便于观察,我们让父进程休眠20s

        // 苏醒后,父进程执行 wait,耐心地等待子进程
        pid_t ret = wait(NULL);  // 暂且将status参数设置为NULL
        if (ret < 0) {
            printf("等待失败!\n");
        }
        else {
            printf("等待成功!\n");   // 此时 Z → X
        }

        sleep(20);  // 子进程退出后,再让父进程存在一段时间
    }
}

💡 说明:简单来说,就是让父子进程都跑起来,并各自打印消息,子进程一直死循环运行,父进程将自己休眠 20 秒,在这 20 秒之内我们手动把处于死循环的子进程给 kill 掉,此时子进程会处于 Z 状态,此时父进程还在休眠不会立即调用 wait(这么做是为了便于观察)。

当 20 秒过后,父进程苏醒后执行了 wait 函数,用一个变量 ret 去接收 wait 的返回值,通过返回值来确定是否等待成功。如果等待成功了就会成功 Z\rightarrow X

为了观察进程状态,我们写一段监控脚本,做到每隔一秒就监控一下我们的 mytest 进程。

🔍 监控脚本:

$ while :; do ps ajx | head -1 && ps ajx | grep mytest | grep -v grep; echo "-----------------------------------------------------------------------"; sleep 1; done

 运行后我们只需要死死盯着 \textrm{STAT},观察状态的变化即可!

开三个窗口,分别用于运行监控脚本、运行 mytest 和 kill 子进程:

🚩 开始运行: ./mytest

 至此,我们成功验证了父进程等待了子进程。

通过 wait() 的方案,我们可以解决回收子进程的 Z 状态,让子进程进入 X 状态。

0x02 waitpid 函数初探

刚才讲的 wait 并不是主角,因为其功能比较简单,在进程等待时用的更多的是 waitpid

waitpid 可以把 wait 完全包含,wait  waitpid 的一个子功能。

📌 注意:wait/waitpid 都可以回收子进程的僵尸状态(Z\rightarrow X)。

waitpid:等待任意一个退出的子进程。

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int* status, int options);

对于返回值 pid_t

  • 如果 pid_t > 0:等待子进程成功,返回值就是子进程的 pid
  • 如果 pid_t < 0:等待失败。

对于参数 pid:

  • 设置参数 pid > 0:是几,就代表等待哪一个子进程,比如 pid=1234,指定等待。
  • 设置参数 pid = -1:待任意进程。

对于参数 options:

  • 0:阻塞等待   (本章不讲)

对于参数 status:

  • 该参数是一个输出型参数,通过调用该函数,从函数内部拿出特定的数据。

(本章我们将重点讲解参数 status)

 值得强调的是,wait/waitpid 是系统调用!

 

💡 说明:父进程等待子进程,子进程也会执行自己的代码。当子进程执行了 return/exit 退出后,子进程会将自己的退出码信息写入自己的进程控制块 (\textrm{PCB}) 中。子进程退出了,代码可以释放,子进程退出后变成 Z 状态,其本质上就是将自己的 task_struct 维护起来(代码可以释放,但是 task_struct 必须维护)。所谓的 wait/waitpid 的退出信息,实际上就是从子进程的 task_struct 中拿出来的,即 从子进程的 task_struct 中拿出子进程退出的退出码。

所以,我们的父进程在等待子进程死亡,等子进程一死,就直接把子进程的退出码信息拷贝过去,通过 wait/waitpid 传进来的参数后,父进程就拿到了子进程的退出结果。即 子进程会将自己的退出信息写入 task_struct  。

 子进程的 task_struct 是操作系统的内部数据结构这个 waitpid 自然就是系统调用,实际上就是调用操作系统的功能。

 那……这个 status 仅仅只是一个无情的拿退出码的机器吗?

当然不是!肯定还会包含其他信息的,这个我们一会讲解。

我们再来验证一下 "子进程会将自己的退出信息写入 task_struct" 这句话:

💡 解读:这里我们可以看到 exit_state,这就是退出状态了,下面的 exit_code 和 exit_signal 分别是退出码和退出信号,当一个进程异常退出时该进程会收到信号,所以收到的信号编号也记录在了这里。这两个信息会在子进程任何地方退出时填充,被填充后,父进程就可以读取 task_struct 中的这两个数据,来得到子进程的退出结果。

🔺 总结:父进程通过调用 wait/waitpid,通过 status 参数,就可以将子进程的信息拿到手。

 由此可见,这个 status 是多么的重要!我们现在就来重点来讲解一下 status

 

Ⅱ. 详解 wait/waitpid 的 status 参数

0x00 引入:status 参数是位图结构

waitwaitpid,都有这个 status 参数,我们刚才演示 wait 的时候传的是 NULL

如果传递 NULL,则表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

该参数是一个 输出型参数 (即通过调用该函数,从函数内部拿出来特定的数据)。

并且,status 参数是由操作系统填充的!是一个整数,该整数就是下面我们要详细研究的。

它虽然是一个 int 型整数,但是不能简单地将其看作整型,而是被当作一个 位图结构 看待。

不过,关于 status 我们只需要关心该整数的 低 16 个比特位!

我们不必去关心它的高 16 位,因为凭借低 16 位就足以判断了。

然而,整数的低 16 位,其中又可以分为 最低八位次低八位(具体细节看图):

 那么我们你可以通过它们来干什么呢?我们先来讲解低十六位中的 "次低八位" 。

0x01 次低八位:拿子进程退出码

📌 重点:通过提取 status 的次低八位,就可以拿到子进程的退出码。

💬 验证: 通过提取 status 的次低八位,可以拿到子进程的退出码。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

int main (
    void
    ) 
{
    pid_t id = fork();
    if (id == 0) {
        int cnt = 5;   // 循环5次
        // child
        while (1) {
            // 五秒之内运行状态
            printf("我是子进程,我正在运行... Pid: %d\n", getpid());
            sleep(1);

            // 五秒之后子进程终止
            cnt--;
            if (cnt == 0) {
                break; 
            }
        }

        exit(233);   // 方便辨识,退出码我们设置为233,这是我们的预期结果
    }
    else {
        printf("我是父进程: pid: %d,我将耐心地等待子进程!\n", getpid());
        
        // ***** 使用waitpid进行进程等待
        int status = 0;  // 接收 waitpid 的 status 参数

        pid_t ret = waitpid(id, &status, 0);
        if (ret > 0) {   // 等待成功
            printf (
                "等待成功,ret: %d, 我所等待的子进程退出码: %d\n", 
                ret,
                (status>>8)&0xFF
            );
        }

    }
}

💡 说明:我们让 child 子进程运行五秒后自动结束,之后通过 exit 返回,为了方便辨识,我们可以将退出码设置的花一点,这里我设置了个 233。父进程调用 waitpid 函数,并用 ret 接收,如果返回值大于 0 则代表等待成功,此时我们打印出子进程的退出码,因为 "通过提取 status 的次低八位,就可以拿到子进程的退出码。" ,所以我们可以通过 status 参数去拿。

我们说了,status 并不是整体使用的,而是区域性使用的,我们要取其次低八位。我们可以用 位操作 来完成,将 status 右移八位再按位与上 \textrm{0xFF},即 (status>>8)&0xFF ,就可以提取到 status 的次低八位了。

如果不出意外的话,运行后 waitpid 等待成功,子进程的返回值应当是 233,就证明了可以通过提取 status 的次低八位拿到子进程的退出码。

🚩 运行结果如下:

0x02 初识 core dump(核心转储)

(由于还没有讲解信号的部分的知识,我们先做一个简单的了解)

我们看到,core dump 是占了 1 个 比特位的:

core dump 指的是 核心转储,也可以称之为 "吐核"。

它是操作系统在进程收到某些信号而终止运行时,将此时进程地址空间的内容以及有关进程状态的其他信息写出的一个磁盘文件。目前只需要知道,该信息是用于调试的。

0x03 最低七位:提取子进程的退出信号

📌 重点:通过提取 status 的最低七位,就可以拿到子进程的退出信号。

我们的 status 的低八位用于表示处理异常的地方,其中有 1 位是 core dump,我们下面讲。

除去 core dump,剩余七位用于进程中的退出信号,这就是 最低七位

进程退出,如果异常退出,是因为这个进程收到了特定的信号。

我们虽然还没有开始讲解信号,但是我们前几张就介绍了 kill -9 这样的杀进程操作。

 这个 -9 我们当时说了,就是一个信号,发送该信号也确实可以终止进程。

刚才我们讲的 wait/waitpid 和次低八位的时侯,都是关于进程的 正常退出。

如果进程 异常退出 呢?我们来模拟一下进程的异常退出。

💬 代码演示:模拟异常退出的情况,让子进程一直跑,父进程一直等。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

int main (
    void
    ) 
{
    pid_t id = fork();
    if (id == 0) {
        // 子进程一直不退出,父进程会一直等待。
        // child
        while (1) {
            printf("我是子进程,我正在运行... Pid: %d\n", getpid());
            sleep(1);
        }

        exit(13);
    }
    else {
        printf("我是父进程: pid: %d,我将耐心地等待子进程!\n", getpid());
        
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        if (ret > 0) {   // 等待成功
            printf(
                "等待成功,ret: %d, 我所等待的子进程退出码: %d\n, 退出信号是: %d", 
                ret, (status>>8)&0xFF, 
                status&0x7F
            );
        }

    }
}

💡 说明:现在我们直接 while(1) 死循环让子进程往死里跑,此时父进程由于调用了 waitpid,就会一直等待子进程,父进程就会持续阻塞。

此时我们讲父进程打印的消息再增添一个 "退出信号",也就是提取 status 的最低七位,我们想让低七位保留,高二十五位保留,让 status 按位与上 \textrm{0x7F} 即可,即 status&0x7F  (低七位保留,高二十五位全部清零) 。如此一来,我们就提取到了子进程退出时的退出信号了。

🚩 运行结果如下:

因为子进程是个死循环,父进程又调了 waitpid,导致父进程一直在 "阻塞式" 地等待子进程。

父进程在等待子进程期间什么都没有干,就搬了张板凳坐在那等子进程死。

 父进程就像在一直在问子进程:

"你寄没寄?你寄没寄?你寄没寄?" 

我们知道,信号是可以杀掉进程的,我们现在主动输入 kill -9

此时我们就成功拿到了子进程的退出信号,9 是因为我们输入的信号就是 9

此时父进程看到子进程寄了,终于可以不用等了,可以给子进程收尸了(Z\rightarrow X

 父进程大喊道: 

"你终于寄了!寄你太美!oh baby,寄你太美!寄你实在太美!"

还是那句话,代码跑完结果是什么已经不重要了,我们最关心的是因为什么原因退出的。

当进程收到信号时,就代表进程异常了。进程程出,如果是异常退出,是因为该进程收到了特定的信号。其实除了 9 号信号还有很多信号,输入 kill -l 就可以查看这些:

比如我们遇到的除零警告,即  warning: division by zero

int a = 10 / 0;

 这时出现 8 号退出信号:\textrm{SIGFPE} (Float Pointer Error)

实际上,我们在编程学习中遇到的程序崩溃,崩溃程序就退出了,程序一崩溃程序就退出了,今天在我们操作系统的角度来看,这就是进程终止。其实是因为错误而导致了软硬件错误,操作系统通过发送信号的方式把进程杀掉了。至于操作系统凭什么可以终止, 又是怎么发信号的?又是如何终止的?详细的过程是什么?这些问题我们会放到后面的信号章节再做详细讲解!

🔺 总结:退出信号代表进程是否异常,退出码代表进程在退出之时代码对还是不对。

0x04 进程退出的宏

 我们今天写的代码,是通过位操作去截 status 得到退出码和退出信号的。

实际上,你也可以不用位操作,因为 \textrm{Linux} 已经给我们提供了一些宏供我们直接调用。

它们是 WEXITSTATUS 和 WIFEEXITED,在这之前,我们再思考一个问题:

❓ 思考:一个进程退出时,可以拿到退出码和推出信号,我们先看谁?

一旦程序发现异常,我们只关心退出信号,退出码没有任何意义。

 所以,我们先关注退出信号,如果有异常了我们再去关注退出码。

WEXITSTATUS 宏用于查看进程的退出码,若非 0,提取子进程退出码。

WEXITSTATUS(status)

WIFEEXITED 宏用于查看进程是否正常退出,如果是正常终止的子进程返回状态,则为真。

WIFEXITED(status)

 所以,我们可以通过宏来获取退出码和退出信号,是不是很方便?

💬 代码演示:运用 WEXITSTATUS 和 WIFEXITED 宏

    else {
        printf("我是父进程: pid: %d,我将耐心地等待子进程!\n", getpid());

        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        if (ret > 0) {   // 等待成功
            if (WIFEEXITED(status)) {
                printf("子进程正常退出,退出码: %d\n", WEXITSTATUS(status));
            }
        }
    }

当然了,如果你压根就不关注推出信息和退出码,你直接把 status 设置为 NULL 就行。

options 参数

默认为 0,表示阻塞等待。

当我们调用某些函数的时侯,因为条件不就绪,需要我们阻塞等待。

本质:就是当前进程自己变成阻塞状态,等条件就绪的时候,再被唤醒。

所谓的阻塞,本质就是进程阻塞。所谓的条就不就绪,可能是任意的软硬件条件。

还有一个非阻塞等待的知识点,我们放到后面再说吧。

📌 [ 笔者 ]   王亦优
📃 [ 更新 ]   2022.3.4
❌ [ 勘误 ]   /* 暂无 */
📜 [ 声明 ]   由于作者水平有限,本文有错误和不准确之处在所难免,
              本人也很想知道这些错误,恳望读者批评指正!

📜 参考资料 

C++reference[EB/OL]. []. http://www.cplusplus.com/reference/.

Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .

百度百科[EB/OL]. []. https://baike.baidu.com/.

比特科技. Linux[EB/OL]. 2021[2021.8.31 xi

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

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

相关文章

Vue2的生命周期(详解)

Vue的生命周期一、生命周期的概念二、钩子函数三、Vue2的生命周期3.1 初始化阶段3.2 挂载阶段3.3 更新阶段3.4 销毁阶段一、生命周期的概念 Vue实例的生命周期: 从创建到销毁的整个过程 二、钩子函数 Vue框架内置函数,随着组件的生命周期阶段,自动执行 作用:特定的时间点,执行特…

Unity Lighting -- 向场景中添加光源

本节笔记来实践向场景中添加光源。 除了平行光源外&#xff0c;还有两种常用的光源&#xff1a; 点光源&#xff08;Point Lights&#xff09;&#xff1a;点光源所发出的光是朝四面八方发散的&#xff0c;我们可以用点光源来模拟灯泡之类的发光物体。 聚光灯源&#xff08;Spo…

python每日学9 : windows上配置gitee的远程仓库,git的初步使用

在开发中&#xff0c;如果遇到复杂的项目&#xff0c;使用版本控制是非常有必要的&#xff0c;如果涉及到多端开发&#xff0c;那么还需要使用远程仓库。本文作个简单记录&#xff0c;记录下git初步使用。 1 下载与安装 git还有几个ui版本&#xff0c;但是开始使用的话&#…

【LeetCode】带环链表两道题

第一题&#xff1a;环形链表 问题介绍 给你一个链表的头节点head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪next指针再次到达&#xff0c;则链表中存在环。为了表示给定链表中的环&#xff0c;评测系统内部使用整数pos 来表示链表…

【Storm】【三】Storm 核心概念详解

Storm 核心概念详解 一、Storm核心概念1.1 Topologies&#xff08;拓扑&#xff09;1.2 Streams&#xff08;流&#xff09;1.3 Spouts1.4 Bolts1.5 Stream groupings&#xff08;分组策略&#xff09;二、Storm架构详解2.1 nimbus进程2.2 supervisor进程2.3 zookeeper的作用2.…

【蒸滴C】C语言结构体入门?看这一篇就够了

目录 一、结构体的定义 二、结构的声明 例子 三、 结构成员的类型 结构体变量的定义和初始化 1.声明类型的同时定义变量p1 2.直接定义结构体变量p2 3.初始化&#xff1a;定义变量的同时赋初值。 4.结构体变量的定义放在结构体的声明之后 5.结构体嵌套初始化 6.结构体…

24节气-惊蛰 // 诗句、海报分享,春风至,惊雷动。

惊蛰&#xff0c;古称"启蛰"&#xff0c;是二十四节气中的第3个节气&#xff0c;更是干支历卯月的起始;时间点在公历3月5-6日之间&#xff0c;太阳到达黄经345时。《月令七十二候集解》:"二月节……万物出乎震&#xff0c;震为雷&#xff0c;故曰惊蛰&#xff0…

【ONE·C || 动态内存管理】

总言 C语言&#xff1a;动态内存管理介绍。 文章目录总言1、为什么存在动态内存管理2、动态内存函数介绍2.1、malloc、free2.1.1、malloc函数2.1.2、free函数2.2、calloc、realloc2.2.1、calloc函数2.2.2、realloc函数3、常见的动态内存错误3.1、对NULL指针的解引用操作3.2、对…

TEX:显示文本

文章目录字体选择字体fontspec宏包根据字体形状控制字体为不同的字体形状选择不同的特征为不同的字体大小状选择不同的特征中文字体选择xeCJK宏包字体选择与设置XELATEX字体名查找字体集与符号居中与缩进居中单边调整两边缩进诗歌缩进列表itemize样例enumerate样例description样…

Java多线程(三)——线程池及定时器

线程池就是一个可以复用线程的技术。前面三种多线程方法就是在用户发起一个线程请求就创建一个新线程来处理&#xff0c;下次新任务来了又要创建新线程&#xff0c;而创建新线程的开销是很大的&#xff0c;这样会严重影响系统的性能。线程池就相当于预先创建好几个线程&#xf…

concrt140.dll丢失四种方法解决丨提示游戏里找不到concrt140.dll?

电脑提示concrt140.dll文件丢失怎么办&#xff1f;由于找不到concrt140.dll&#xff0c;无法继续执行代码&#xff1f; 我们平时在打开 Adobe 应用程序、Halo、Forza Horizon 5 地平线5 等时&#xff0c;可能会遇到找不到 concrt140.dll。因此&#xff0c;这不是特定于某个应用…

基频的后处理

基频归一化 基频为什么要归一化&#xff1f;为了消除人际随机差异&#xff0c;提取恒定参数&#xff0c;在语际变异中找到共性。 引言 声调的主要载体就是基频。但是对声调的感知会因人而异&#xff0c;例如某个听感上的高升调&#xff0c;不同的调查人员可能会分别描写成 […

Nginx的负载均衡

Nginx不仅可以作为一个web服务器或反向代理服务器&#xff0c;还可以按照权重、轮询、ip_hash、URL_hash等多种方式实现对后端服务器的负载均衡。 负载均衡的概念&#xff1a; 负载均衡就是将负载分摊到多个操作单元上执行&#xff0c;从而提高服务的可用性和相应速度&#xf…

数据仓库、数据中台、数据湖都是什么?

相信很多人都在最近的招聘市场上看到过招聘要求里提到了数据仓库、数据中台&#xff0c;甚至还有数据湖&#xff0c;这些层出不穷的概念让人困扰。今天我就来跟大家讲一讲数据仓库、数据中台以及数据湖的概念及区别。 数据库 在了解数据仓库、数据中台以及数据湖之前&#xff…

JDBC

JDBC核心技术 讲师&#xff1a;宋红康 微博&#xff1a;尚硅谷-宋红康 第1章&#xff1a;JDBC概述 1.1 数据的持久化 持久化(persistence)&#xff1a;把数据保存到可掉电式存储设备中以供之后使用。大多数情况下&#xff0c;特别是企业级应用&#xff0c;数据持久化意味着将内…

【java web篇】Tomcat的基本使用

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…

ceph介绍、原理、架构、算法...个人学习记录

前言 之前公司安排出差支援非结构化项目&#xff0c;采用springcloud(redismysql数据冷热处理)s3escephkafka还涉及一些区块链技术等等…&#xff0c;在与大佬的沟通交流下对ceph产生了兴趣&#xff0c;私下学习记录一下&#xff1b;后续工作之余会采用上面相关技术栈手动实现不…

Python模块化编程_Python编程之路

之前跟大家讲的是一些python的数据基础&#xff0c;从这篇文章开始&#xff0c;我们开始正式学习python的模块化编程 下面我们解释一下什么叫做模块 之前已经讲过怎么去定义一个方法&#xff0c;如果你是用python交互器(自带交互器&#xff0c;或者是ipython)来学习定义方法的…

4 通道 3.2GSPS(2 通道 6.4GSPS) 12 位 AD 采集子卡模块

FMC134 是一款 4 通道 3.2GSPS&#xff08;或者配置成 2 通道 6.4GSPS&#xff09; 采样率的 12 位 AD 采集 FMC子卡模块&#xff0c;该板卡为 FMC标准&#xff0c;符 合 VITA57.4 规范&#xff0c;可以作为一个理想的 IO 模块耦合至 FPGA 前端&#xff0c; 射频模拟信号数字化…

c语言经典例题-循环结构程序设计

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 求各位数字之积&#xff1a; 本关任务&#xff1a;计算正整数num的各位上的数字之积。 例如&#xff1a; 输入&am…