【看表情包学Linux】进程的概念 | 进程控制块 PCB | 父进程与子进程 | 进程 ID | task_struct

news2024/11/13 15:54:57

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

💭 写在前面:本章我们将带着大家深入理解 "进程" 的概念,"进程" 这个概念其实使我们一直在接触的东西,只不过这个概念我们没有框出进行详细讲解罢了,本章我们就把 "进程" 这货挖出来好好地深入理解一番!引出进程的概念后,我们最后再讲解一下 PCB,针对什么是 PCB 以及为什么要有 PCB 等一系列问题进行讲解。本章结束后,我们接下来会谈论进程状态和优先级,我们会重点关注状态的讲解。后续讲的过程中我们还会串入竞争、独立、并发和并行的概念,还会涉及到进程调度和切换的理解。

    本篇博客全站热榜排名:13


Ⅰ. 进程的概念(Process)

0x00 引入:什么是进程?

" Process is a running program. "

进程是一个运行起来的程序。

这句话在很多教科书上出现,但是这说了跟没说一样,什么是运行起来的程序呢,

跑或没跑?跑起来的程序,和没跑起来的程序?我们不放首先来思考一个问题:

❓ 思考:程序是文件吗?

是!都读到这一章了,这种问题都无需思考!文件在磁盘哈。

本章一开始讲的冯诺依曼,磁盘就是外设,和内存与 CPU 打交道,它们之间有数据交互。

你的程序最后要被 CPU 运行,所以要运行起来必须先从磁盘外设加载到内存中。

因此,当可执行文件被加载到内存中时,该程序就成为了一个进程。

0x01 承上启下:先描述再组织

我们还是首先思考一个问题,通过问题去引出我们的知识点。

❓ 思考:操作系统中可能存在多个进程吗?

操作系统里面可能同时存在大量的进程!

既然如此,那操作系统要不要将所以后的进程管理起来呢?

当然要,不要不就乱套了?当前想调用哪个进程,想让哪个进程占用 CPU 资源,

想执行哪个资源,数据一大你不管怎么行?所以我们刚才再次讲解了操作系统管理的概念:

被管理对象的管理本质上是对数据的管理。那么 对进程的管理,本质上就是对进程数据的管理。

所以还是那句话 —— 我们需要 先描述再组织。(上一章我们讲过)

所以,当一个程序加载到内存时,操作系统做的不仅仅只是把代码和数据加入到内存,

还要管理进程,创建对应的数据结构。我们讲的是 Linux 操作系统,

Linux 操作系统的内核是 C 语言写的,所以我们管理进程,就要先描述再组织,

那描述一个事物我们当然是要用 ——  

0x02 进程控制块(PCB)

/* Process Ctrl Block */
struct task_struct {
    进程的所有属性数据
};

在操作系统中,我们把描述进程的结构体称为 \textrm{PCB} (Process Ctrl Block) 。

在很多教材中,会把 \textrm{PCB} 称为 进程控制块

❓ 为什么每个进程都要有 \textrm{PCB} 呢 (task_struct)?

💡 因为操作系统要管理我们的进程,想要管理就必须要 "先描述再组织" 。

❓ 为什么我们的 task_struct 每个进程都要有呢?

💡 因为这是为了管理进程而描述进程所设计的结构体类型,将来当有一个进程加载到内存时,
操作系统在内核中一定要为该进程创建 task_struct 结构体变量,
并且要将该变量链入到全局的链表当中。要删掉一个进程,实际上就是遍历所有的链表结点,
把对应进程的 \textrm{PCB} 和代码都释放掉,这就叫对链表做管理。
最终你会发现,操作系统对进程的管理,最终变成了对链表的增删查改。

什么是进程?目前为止我们可以总结成:进程 = 可执行程序 + 该进程对应的内核数据结构

💭 task_struct 是一个非常大的结构体:

struct task_struct {
    volatile long state;
    void *stack;
    atomic_t usage;
    unsigned int flags;     
    unsigned int ptrace;
    unsigned long ptrace_message;
    siginfo_t *last_siginfo; 

    int lock_depth;         

#ifdef CONFIG_SMP
#ifdef __ARCH_WANT_UNLOCKED_CTXSW
    int oncpu;
#endif
#endif

...
}

0x03 系统接口

OS 为神马要给我们提供服务呢?因为计算机和 OS 设计出来就是为了给人提供服务的。

printf or cout \rightarrow 向显示器打印,显示器是硬件 

 所谓的打印,本质就是将数据写到硬件。

你自己的 C 程序,有资格向硬件写入吗?你是没有资格这么做的。

 如何提供服务?

操作系统不相信任何人的,不会直接暴露自己的任何数据结构,代码逻辑,其他数据相关的细节。

想做系统是通过 系统调用 的方式,对外提供接口服务的。

Linux 操作系统是用C语言写的,这里所谓的 "接口",本质就是C函数。

我们学习系统编程,本质上就是学习这里的系统接口。

Ⅱ. 进程查看

0x00 通过指令查看进程

我们先创建一个 mytest.c 文件,然后写上一个死循环,每隔1秒就打印一句话:

生成 mytest 可执行文件后,使用 ldd 和 file 去查看:

对我们来说,既然它是 executable 那么就是可执行文件,它就是在磁盘上放着。

而我们使用的是云服务器,所以不是在你自己电脑的磁盘上,而是在云服务器的磁盘上放着。

接下来我们 ./mytest 去运行它,此时这个程序就变成了一个进程:

那么此时,在系统中我们可以使用  ps  查看进程:

$ ps aux
$ ps ajx

/* 含义 */
1)ps a     显示现行终端机下的所有程序,包括其他用户的程序。
2)ps -A    显示所有程序。
3)ps c     列出程序时,显示每个程序真正的指令名称,而不包含路径,参数或常驻服务的标示。
4)ps -e    此参数的效果和指定"A"参数相同。
5)ps e     列出程序时,显示每个程序所使用的环境变量。
6)ps f     用ASCII字符显示树状结构,表达程序间的相互关系。
7)ps -H    显示树状结构,表示程序间的相互关系。
8)ps -N    显示所有的程序,除了执行ps指令终端机下的程序之外。
9)ps s     采用程序信号的格式显示程序状况。
10)ps S    列出程序时,包括已中断的子程序资料。
11)ps -t   <终端机编号>  指定终端机编号,并列出属于该终端机的程序的状况。
12)ps u   以用户为主的格式来显示程序状况。
13)ps x   显示所有程序,不以终端机来区分。
14)ps -l   显示详细PID信息

我们这里就先用 ps aux 来做个演示:

此时他就会将你系统中所有的进程显示出来,这些都是系统中所对应的相关启动进程。

我们刚才直接使用 ps aux  打出来的都是以行为单位,如何我想查看我们刚才的 mytest 进程呢?

 我们可以尝试使用 grep 抓一下:

$ ps aux | grep 'mytest'

🚩 结果如下:

诶?这个grep 怎么也看得到?不要惊讶,请坐:

如果你不想见到 grep 进程,你就把 grep 关键字屏蔽掉就行:

$ ps aux | grep 'mytest' | grep -v grep

看到这里,你应该能发现了,其实没有什么神奇的,就相当于所有的指令是进程而已。

Windows 下的任务管理器:

0x01 通过 proc 目录查看进程信息

上面我们讲述了查看进程的第一种方式,即最常用的 ps aux 。

下面我们要来讲解第二种方式,在讲解之前我们先来探讨一下 "当前路径"

ls /

proc:内存文件系统,里面放的是当前系统实时的 进程信息

既然如此,现在我们就用 \textrm{​{\color{Blue} proc}} 看一下我们的 process 进程信息:

这些都是什么玩意?乱七八糟的,蓝色标出的是目录……

此时我们要先引入一个新的概念:进程 pid  (process id)

0x02 进程 ID(pid)

上面的这些蓝色的数字,实际上就是进程的 \textrm{pid} ,这个我们讲完 \textrm{​{\color{Blue} proc}} 之后会说。

每一个进程在系统中,都会存在一个惟一的标识符!

这就如同每个人都有身份证号一样,进程也需要标号的,所以每个进程都存在有一个 \textrm{pid}

我们的 mytest 现在还在后台欢快的跑着呢,此时我们可以把所有的 title 列名称显示出来:

ps aux | head -1

此时我们成功把属性提取出来了,我们使用 && 进行下一步操作

(逻辑与,前面指令成功再执行下面的指令)

ps aux | head -1 && ps aux | grep 'mytest' | grep -v grep

这就是当前进程的 \textrm{pid},刚才我们说了: \textrm{​{\color{Blue} proc}} 里保存的是内存当中实时的进程信息。

那我们在 \textrm{​{\color{Blue} proc}} 目录下找到这个 24422 ,发现这个 {\color{Blue} 24422} 目录确实存在!

ls /proc/找到的pid

 既然是实时的,那我们把跑的正欢的 mytest 进程 ctrl+ c 干掉,

看看这个文件夹是否还健在:

我们再用同样的指令去查,那 {\color{Blue} 24422} 目录下的内容应当是不复存在的:

我们已经证明了实时的概念,现在我们再去研究一下进程的信息,我们再把进程启动起来。

启动之后再查 {\color{Blue} 24422} ,发现还是没有:

呵呵,那是当然的,原因很好猜,因为重开了嘛!我们在用指令去查看新的 \textrm{pid}

ps aux | head -1 && ps aux | grep 'mytest' | grep -v grep

进程 \textrm{pid} 发生了变化:{\color{Blue} 24422} \rightarrow {\color{Blue} 10117}

好了,现在我们知道 \textrm{pid} 是 {\color{Blue} 10117} 了,我们进去查看下进程属性:

这里面的东西很多,目前想搞懂里面都是做什么的还为时尚早,我们先 -al 看看细节:

$ ls /proc/10117 -al

我们重点去关注 exe cwd

  • exe:指出进程对应的可执行程序的磁盘文件
  • cwd:指出进程当前的工作路径

下面我们先终止进程,修改一下 mytest.c 文件的内容,给它加一个文件操作:

#include <stdio.h>
#include <unistd.h>

int main(void) {
    FILE* fp = fopen("log.txt", "w");  // 若不存在就创建之
    while (1) {
        printf("I am m a process!\n");
        sleep(1);
    }
}

成功运行,此时我们 ls 就能发现当前路径下多出一个 log.txt 文件,这就是我们自己创建的:

我们早在《维生素C语言》专栏就说过,fopen 后面如果不带路径,那么会默认在当前路径。

所谓的当前路径,其本质!也浮现出来了 —— 当前进程所在的路径

进程会自己维护,进程会知道自己的工作路径在哪里:

(说实话,博主真的画嗨了哈哈哈哈受不了了太好笑了)

\textrm{pid},当前路径,这些东西在哪里呢?

 进程的内部属性!在进程的进程控制块 \textrm{PCB} (task_struct) 结构体中!

0x03 获取 pid(getpid 函数)

 下面我们隆重介绍下获取 \textrm{pid} 的函数 —— getpid() 

想要查看进程 \textrm{pid},一定是这个进程得运行起来。

我们不妨先问问 Linux 手册中的那个男人,getpid 的下落:

$ man 2 getpid

💬 我们修改一下刚才的 mytest.c 代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(void) {
    while (1) {
        printf("I am m a process! , pid: %d\n",getpid());
        sleep(1);
    }
}

🚩 运行结果如下:

启动后,我们发现我们的 mytest 可执行程序的 \textrm{pid} 为 17599

是否果真如此?我们还是用 ps aux 验证一下看看:

ps aux | head -1 && ps aux | grep 'mytest' | grep -v grep

0x04 杀进程(kill -9)

我们再来回忆一下我们是如何杀掉一个进程的…… \textrm{ctrl + c} 

这是我们之前讲的,在 Linux 命令行中的热键,遇到问题解决不了可以用它来中止。

所谓的 \textrm{ctrl + c} 就是用来杀进程的。除此之外,你也可以选择在另一个终端中使用  kill  命令:

$ kill -9 [pid]   # 给这个进程发送9号信号

当前你只需要知道可以通过 kill -9 命令杀掉进程就行了,至于这个 9 号信号,我们会放在后面的信号章节去讲!

比如我们现在想杀掉刚才运行的, 打出进程 \textrm{pid} 的 mytest 进程,其 \textrm{pid} 为  17599

0x05 父进程 ID(ppid)

\textrm{ppid}  (parent process id) 其实就是父进程 id

\textrm{pid} 可以通过 getpid() 函数获取,其实 \textrm{ppid} 也有与之对应的函数,那就是 getppid() 。

我们还是从 mytest.c 下手,刚才我们加入了 getpid, 现在我们再给句子后面加入 getppid。

我们再次清楚那个男人 —— man 手册出来:

💬 代码:mytest.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(void) {
    while (1) {
        printf("I am m a process! , pid: %d, ppid: %d\n",getpid(), getppid());
        sleep(1);
    }
}

🚩 代码运行结果:

我们从中得知,其 \textrm{pid }= 375,\, \textrm{ppid} = 24506 

我们还是验证一下,这里要看 \textrm{ppid},刚才的 ps aux 是显示不到的,这里介绍一下  ps ajx 

ps ajx | head -1 && ps ajx | grep 'mytest' | grep -v grep

 ps ajx  就能把 \textrm{ppid} 和 \textrm{pid} 同时显示出来了。

我们刚才发觉到 \textrm{pid} 在每次启动都会重新分配,但是好像这里的 \textrm{ppid} 似乎恒定不变啊。

❓ 思考:我的父进程为什么不变?是谁呢?

这个神奇的父进程 24506 是何许人也?我们来把他挖出来看看:

ps axj | head -1 && ps axj | grep 24506

我们的父进程竟然是一个叫  \textrm{-bash}  的东西!这个现象,我们可以推导出一个假设:

几乎我们在命令行上所执行的所有指令包括你自己定义的 cmd,都是 \textrm{-bash} 进程的子进程。

0x06 使用 fork() 创建子进程

" fork!!!其实就是父亲用来造孩子的工具 "

fork()

fork 函数是用来创建子进程的, 它有两个返回值。父进程返回子进程的 \textrm{pid},给子进程返回 0。

" 哈哈哈哈,fork 函数居然有 2 个返回值。"

💬 代码演示:我们来看看会发生什么

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(void) {
    pid_t id = fork();

    printf("Hello, World!\n");
    sleep(1);
}

🚩 运行结果如下:

现在我们再来验证一下返回值的问题,我们把 id 给打印出来:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(void) {
    pid_t id = fork();

    printf("Hello, World! id: %d\n", id);
    sleep(1);
}

  打印了两次 printf ……

❓ 思考:

  • 同一个 id 值,使用打印,没有修改,却打印出来了不同的值?为什么?这合理吗?
  • fork 如何做到会有不同的返回值?

哈哈哈真的有够逆天的,如果你之前没有学过这块知识,只是学习了C语言,

(这部分知识我们将在进程地址空间中讲解)

刚才已经很离谱了,现在我们再看一个离谱的东西 ——

C 语言上 if else if 可以同时执行吗?C语言中,有没有可能两个以上的死循环同时运行?

不可能,绝对不可能。但是马上你就能看到这一神奇现象:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(void) {
    pid_t id = fork();

    /* id:  0 子进程, >0 父进程 */
    if (id == 0) {
        // child
        while (1) {
            printf("我是子进程,我的pid: %d,我的父进程是 %d\n", getpid(), getppid());
            sleep(1);
        }
    } else {
        // parent
        while (1) {
            printf("我是父进程,我的pid: %d,我的父进程是 %d\n", getpid(), getppid());
            sleep(1);
        }
    }
}

🚩 运行结果如下:

我们发现,这两块代码是可以同时执行的。

原因:fork 之后,父进程和子进程会共享代码,一般都会执行后续的代码。这也是为什么刚才的 printf 会打印两次的原因。fork 之后,父进程和子进程返回值不同,所以可以通过不同的返回值去判断,让父子执行不同的代码块。

❓ 问题1:父进程返回子进程的 \mathrm{pid},给子进程返回 0,为什么?

父进程必须有标识子进程的方案,fork 之后给父进程返回子进程的 \mathrm{pid}。子进程最重要的是要知道自己被创建成功了,因为子进程找父进程的成本非常低。

如果想获取,直接 getppid() 即可。

❓ 问题2:为什么 fork 会返回两次?

fork 函数,OS syscall call,fork 之后,OS 做了什么?是不是系统多了一个进程?

  • task_struct + 进程代码和数据
  • task_struct + 子进程的代码和数据

子进程的 task_struct 对象内部的数据基本是从父进程继承下来的。

子进程执行代码,计算数据的,子进程的代码从哪里来呢?
和父进程执行同样的代码,fork 之后,父子进程代码共享,而数据要各自独立!

父进程代码共享,让不同的返回值,让不同的进程执行不同的代码。

"如此一来,就让父子有了协作。"

🔺 总结:我们在系统调用后,fork 本质是系统多了一个子进程,也就多了一个 task_struct,该进程控制块会几乎继承父进程,代码父子进程共享,但数据是各自私有的。

fork 的时候是要执行很多创建代码的逻辑的,最终 fork 会有两个返回值,一定是它曾经返回了2,次,因此一定会调用,return pid。

调用一个函数,当这个函数准备 return 的之后,那么这个函数的核心功能完成了吗?

当我们函数准备执行 return 的时候,函数的核心功能已经完成:

① 子进程已经被创建了
② 将子进程放入运行队列

最后,return 是代码吗?是的!所以当我们走到 return 时父进程有了,子进程也已经在运行队列了,fork 后代码共享,父子进程当然会执行后续被共享的 return 代码。因此,父进程执行一次 return,子进程执行一次 return,最后就是两个返回值了。

"以后凡是说进程,必须先想到进程的 task_struct "

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

📜 参考资料 

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 he

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

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

相关文章

一文深入搞懂 mmap 涉及的所有内容

内存映射&#xff0c;简而言之就是将内核空间的一段内存区域映射到用户空间。映射成功后&#xff0c;用户对这段内存区域的修改可以直接反映到内核空间&#xff0c;相反&#xff0c;内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间与用户空间两者之间需要大量数…

一文讲清同步异步,消息队列,宏任务 微任务...

单线程多线程 什么是线程进程? 进程&#xff1a;是cpu分配资源的最小单位&#xff1b;&#xff08;是能拥有资源和独立运行的最小单位&#xff09; 线程&#xff1a; 是cpu调度的最小单位&#xff1b;&#xff08;线程是建立在进程的基础上的一次程序运行单位&#xff0c;一…

Kafka第一章:环境搭建

系列文章目录 Kafka第一章&#xff1a;环境搭建 文章目录系列文章目录前言一、环境安装1.前置环境2.软件下载3.上传集群并解压4.编写配置文件5.分发配置文件6.修改参数7.环境变量8.启动服务9.编写启动脚本二、主题命令行操作1.查看topic2.创建 first topic3.查看主题的详情4.修…

录制屏幕为什么没有声音?教您录制声画同步的视频

有时我们只顾着录制电脑的画面&#xff0c;而忽视了录制视频的声音&#xff0c;导致录制的视频文件只有画面没有声音。那您知道录制的视频为什么没有声音吗&#xff1f;怎样才能录制声画同步的录屏文件呢&#xff1f;想要录制带声画同步的视频&#xff0c;首先您得拥有一款支持…

[kubernetes]-k8s通过psp限制nvidia-plugin插件的使用

导语&#xff1a; k8s通过psp限制nvidia-plugin插件的使用。刚开始接触psp 记录一下 后续投入生产测试了再完善。 通过apiserver开启psp 静态pod会自动更新 # PSP(Pod Security Policy) 在默认情况下并不会开启。通过将PodSecurityPolicy关键词添加到 --enbale-admission-plu…

【Cocos新手入门】使用 cocos creator 创建多个场景,并通过代码和事件绑定进行切换场景的方法

本篇文章主要讲解使用 cocos creator 创建多个场景&#xff0c;并通过代码和事件绑定进行切换 作者&#xff1a;任聪聪 日期&#xff1a;2023年1月31日 cocos 引擎版本 2.4.3 场景的创建 步骤一、右击资源管理器下的assets目录&#xff0c;点击新建&#xff0c;献出案件一个sc…

NX二开ufun函数UF_MODL_create_section_surface(样条曲线构建截面特征)

本节主要介绍通过样条曲线及截面OPEN API结构体构建截面特征&#xff0c;函数名 UF_MODL_create_section_surface&#xff0c;效果图如下&#xff1a; 1、函数结构 int UF_MODL_create_section_surface &#xff08; UF_MODL_secsrf_data_p_t section_surface_data&#xff0c…

vue2 数据响应式Object.defineProperty

我们通常可以对进行输入框进行数据的监听&#xff0c;只需要用到了input 事件或 change事件&#xff0c;就可以实时监听到数据的改变&#xff0c;但是如果只是一个单独的数据呢&#xff1f;怎么去做监听&#xff0c;watch吗&#xff1f;&#xff1f;哈哈。 所以 vue响应式就用…

基于微信小程序的懒人美食帮小程序

文末联系获取源码 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.…

graalvm把springboot 3.0应用编译为原生应用

文章目录1、GraalVM Native Support依赖2、编译为原生应用2.1、编译为docker镜像2.2、编译为原生应用文件之前的文章《Graalvm 安装和静态编译&#xff08;https://blog.csdn.net/penngo/article/details/128006244&#xff09;》介绍了graalvm的安装和环境配置&#xff0c;普通…

【Rust】6. 结构体与方法

6.1 结构体的定义和实例化 6.1.1 结构体定义、创建实例 6.1.2 创建实例&#xff1a;字段初始化简写语法 6.1.3 创建实例&#xff1a;结构体更新语法&#xff08;注意&#xff1a;数据的移动特性&#xff01;&#xff09; .. 语法&#xff1a;指定了剩余未显式设置值的字段应有…

通信原理笔记—差分脉冲编码调制

目录 差分脉冲编码调制(DPCM)&#xff1a; 预测编码&#xff1a; 预测编码的基本原理: 信号预测的基本方法: ΔPCM与DPCM的主要区别: 差分脉冲编码调制(DPCM)&#xff1a; 实际信源的特点&#xff1a;实际信源大都是有记忆的信源&#xff1a;信源的相邻输出符号间(如&…

无广告,小体积,实用性拉满的5款软件

人类与99%的动物之间最大差别在于是否会运用工具&#xff0c;借助好的工具&#xff0c;能提升几倍的工作效率。 1. 无损放大图片——Bigjpg 大杀器&#xff01;深度卷积神经网络实现噪点和锯齿部分补充&#xff0c;从而达到图片无损放大。图片边缘也不会有毛刺和重影,。更重要…

(深度学习快速入门)第四章第二节:什么是卷积神经网络

文章目录一&#xff1a;为什么DNN不适合图像处理&#xff08;1&#xff09;图像的空间信息被丢失&#xff08;不具备空间不变性&#xff09;&#xff08;2&#xff09;参数爆炸二&#xff1a;什么是卷积神经网络三&#xff1a;CNN应用一&#xff1a;为什么DNN不适合图像处理 &…

【关于PostgreSQL的系统信息函数的OID】

一、自带的OID的相关脚本 在PostgreSQL的安装包的src/include/catalog目录下&#xff0c;有着两个脚本&#xff0c;unused_oids和 duplicate_oids。通过这两个可执行脚本&#xff0c;可以查看当前源码包配置里的符合要求的OID。unused_oids可以查看若根据当前源码包初始化产生…

YOLOv5训练结果性能分析

入门小菜鸟&#xff0c;希望像做笔记记录自己学的东西&#xff0c;也希望能帮助到同样入门的人&#xff0c;更希望大佬们帮忙纠错啦~侵权立删。 可参照以下博客一起看&#xff08;涉及一些概念解析&#xff09;深度学习之常用模型评估指标&#xff08;一&#xff09;—— 分类…

Python大数据处理利器,PySpark的入门实战

PySpark极速入门 一&#xff1a;Pyspark简介与安装 什么是Pyspark&#xff1f; PySpark是Spark的Python语言接口&#xff0c;通过它&#xff0c;可以使用Python API编写Spark应用程序&#xff0c;目前支持绝大多数Spark功能。目前Spark官方在其支持的所有语言中&#xff0c;…

OpenCV-Python学习(18)—— OpenCV 图像几何变换之图像平移(cv.warpAffine)

1. 学习目标 学习图像的平移矩阵&#xff1b;学习 OpenCV 图像平移函数。 2. 图像的平移矩阵 平移是物体位置在水平和垂直方向的移动。 像素点 (x,y) 沿 x 轴平移 dx、沿 y 轴平移 dy&#xff0c;公式&#xff1a; 3. 图像平移函数 3.1 cv.warpAffine() 函数使用 cv.war…

R语言生物群落(生态)数据统计分析与绘图

包含&#xff1a;《R语言基础》、《tidyverse数据清洗》、《多元统计分析》、《随机森林模型》、《回归及混合效应模型》、《结构方程模型》、《统计结果作图》七合一版本 R 语言作的开源、自由、免费等特点使其广泛应用于生物群落数据统计分析。生物群落数据多样而复杂&#…

手动启动Oracle服务和Oracle监听服务和init.ora文件相关

Oracle 11g 安装未完全成功&#xff1b;安装完以后&#xff0c;服务只有2个&#xff1b;这样是用不了&#xff0c;oracle服务和oracle监听服务都没有&#xff1b; 尝试启动一下数据库&#xff0c;出现12560错误&#xff1b; 根据资料&#xff0c;可用如下命令启动Oracle服务&am…