Linux 进程
文章目录
- 1. 冯诺依曼体系结构
- 1.1 什么是冯诺依曼体系结构
- 1.2 为什么选择冯诺依曼结构
- 2. 操作系统
- 2.1 操作系统是什么
- 2.2 操作系统如何对硬件资源进行管理
- 2.3 计算机的层状体系结构
- 3. 进程
- 3.1 进程是什么
- 3.2 进程的相关属性
- 3.3 在Linux中了解进程
- 3.3.1 查看进程
- 3.3.2 子进程由父进程创建
- 4. 进程状态
- 4.1 Linux中进程的双链表管理方式
- 4. 2 进程具体状态
- 4.2.1 新建状态
- 4.2.1 就绪状态和运行状态
- 4.2.2 阻塞状态
- 4.2.3 挂起状态
- 4.3 Linux中的进程状态
- 4.3.1 R状态
- 4.3.2 S状态
- 4.3.3 D状态
- 4.3.4 T状态
- 4.3.5 t 状态
- 4.3.6 x状态和Z状态
- 5. 进程调度
- 5.1 进程优先级
- 5.2 进程切换
- 5.2.1 进程相关概念补充
- 5.2.2 进程切换
- 5.3 进程调度
- 5.4 分时操作系统与实时操作系统
- 6. 命令行参数
- 6.1 命令行参数如何获取
- 6.2 命令行参数的意义
- 7. 环境变量
- 7.1 认识环境变量
- 7.2 多方法获取环境变量
- 7.2.1 main函数参数
- 7.2.2 通过函数获取单个环境变量
- 7.2.3 使用外部变量environ
- 7.2.4 环境变量获取的本质
- 7.3 环境变量的用途
- 7.4 环境变量的属性
- 7.4.1 全局属性
- 7.4.2 内建命令
1. 冯诺依曼体系结构
当代计算机的体系架构普遍采用冯诺依曼体系结构。
1.1 什么是冯诺依曼体系结构
冯诺依曼体系结构将计算机分为四大块:输入设备、存储器、输出设备以及中央处理器(即CPU,包括运算器和控制器)。
常用的输入设备有:鼠标、键盘、麦克风、硬盘等。
常见的输出设备有:屏幕、硬盘、打印机、音响等。
我们可以发现,有单纯的输入和输出设备,也有既是输入设备又是输出设备的,比如硬盘。
上图中的存储器实质是指计算机内存,像硬盘和U盘这类的存储器,不是内存,而是外存。
CPU分为运算器和控制器,运算器是用于进行算术和逻辑运算的,控制器则是用于进行逻辑控制。
在上图中,我们注意观察红色箭头代表的数据信号,我们可以发现,CPU仅直接与内存之间发生数据交流,而与外设(即输入输出设备)之间没有直接的数据交流。
因此,我们完全可以说,内存实质上是外设与CPU之间的一个缓存。而在冯诺依曼体系结构下,计算机的效率主要取决于内存。这是因为,在计算机硬件中,有这么一个规律,越快的数据传输或处理速度,意味着更高昂的造价,因此具备存储功能的硬件,通常都是速度效率与容量成反比的。比如,在容量上,CPU中的寄存器< 内存 < 硬盘;但在效率上,CPU > 内存 > 硬盘。而由于,内存直接与CPU进行数据交互,根据木桶的短板理论,实际CPU的运行速度要受到内存数据传输速度的限制,因此计算机的运行效率主要取决于内存。
1.2 为什么选择冯诺依曼结构
冯诺依曼结构的最大特点就在于引入内存充当外设与CPU之间的一个“缓存”。
试想,如果没有内存,而是外设直接与CPU进行数据交互,那么CPU的效率就会受到外设的直接限制,而外设由于较大的容量,必将导致较低的效率,这样就限制了高性能、低成本计算机的发展。在这样的情况下,想要提高效率,只有扩充CPU的容量,或者提高外设的效率,但CPU的高运算速度,以及外设的大容量,使得这样的改进成本极高。而且容量的增大,通常都会带来访问效率的降低,是很难在一个大容量的前提下,同时保证极高的访问效率的。
而冯诺依曼结构中内存的引入,就很好地解决了这个问题。内存充当外设与CPU之间的缓存,既提高了计算机的性能,又合理地限制了计算机的成本,这才使得计算机逐步走进千家万户,带来了互联网时代。
2. 操作系统
2.1 操作系统是什么
简单来说,操作系统是一款进行软硬件资源管理的软件。 实质上,我们使用计算机,就是通过软件去访问使用计算机的硬件资源,而硬件资源是有限的,因此需要把硬件资源管理好,因此就有了操作系统。所以,从这个角度来说,操作系统通过把软硬件资源管理好,进而会计算机用户提供一个安全、高效和稳定的使用环境。
操作系统有狭义与广义之分。狭义上的操作系统仅仅表示操作系统的kernel内核,如Linux内核,而内核主要进行线程管理,文件管理,内存管理和驱动管理;广义上的操作系统,包括内核的同时,还包括shell、原生库、一些预装的系统级软件和图形化界面等。
2.2 操作系统如何对硬件资源进行管理
操作系统本质也是软件,因此它也是用计算机语言编写而成的。具体来说,操作系统是用C语言构写的。
我们要想对硬件进行管理,首先就得对硬件的各种状态进行抽象描述,而操作系统中使用struct结构体来对硬件的各个属性进行描述。显卡、网卡、硬盘等等硬件都分别对应有一个结构体对象。操作系统驱动获取相应的硬件数据,然后再将这些数据存储到相应的结构体对象中,从而完成描述工作。
但仅仅是描述,并不能将硬件资源管理好,因为这就相当于获取了一堆数据,但是不将这些数据组织起来,是没有办法管理好这些数据的。因此,在操作系统中,还要将这一个个代表硬件的结构体对象,用一定的数据结构组织起来,这样操作系统对实际硬件的管理就变为了对相应数据结构的管理。所以,操作系统内部,一定是含有大量的结构体和数据结构的。
2.3 计算机的层状体系结构
计算机整体的软硬件体系结构是层状的。
计算机使用操作系统进行软硬件资源的管理,对于计算机的用户而言,是不允许直接通过操作系统去访问硬件资源的,要想访问硬件资源,必须使用相关的系统调用接口与操作系统进行交互。
但是系统调用接口的使用是高门槛的,为了计算机的普及,因此又提供了相关的用户操作接口,比如我们所熟知的C/C++标准库实质上就是用户操作接口。这样,就降低了计算机的使用门槛——只要学会命令行中的相关指令,懂得一门计算机语言和相关库的调用,就可以使用计算机,而不用去了解相关的系统调用接口。
但是,上述的用户,实质上针对的是计算机程序员等二次开发者,需要相关的计算机专业知识,但是不懂计算机的人并不会这些。因此,为了让不懂计算机语言和指令的人也能使用计算机,于是在用户操作接口上再封装一层图形化用户界面(GUI),而后就形成了我们现在普遍使用windows或mac的图形化界面。
所以,总的来说,计算机在用户层面的层层封装,上层调用下层,保证了用户对计算机安全与便捷的使用。
3. 进程
3.1 进程是什么
什么是进程呢?简单来说,运行起来,加载到内存中的可执行程序被称为进程。
在计算机运行过程中,内存中的进程数目是很多的,操作系统要对这些进程进行管理,与对硬件的管理相同,遵循先描述,再组织。
计算机中使用PCB(Process Control Block)对进程进行描述。PCB实质上就是一个结构体,里面包含进程的相关属性,以及相关代码和数据的地址等。实质上,内存中的进程:进程 = PCB + 相应的代码和数据。
但是在Linux中,相应描述进程的结构体并不叫PCB,而是名为task_struct。
已经有了对进程相应的描述,那么该如何组织呢?Linux中,使用双链表将各个task_struct对象串在一起进行管理——这样,操作系统对进程的管理,就转化成为对相应双链表的管理。
3.2 进程的相关属性
下面对进程的相关属性进行一个简单的介绍。
标识符:又称PID,每一个进程都有其唯一标识符,用以区别其它进程。
优先级:一个进程相较于其它进程的优先级,决定了进程执行的先后顺序。
程序计数器:英文简写为PC,存放即将要执行的下一条指令的地址。
内存指针:指向程序代码和进程相关数据的指针。
进程上下文:又称为进程的硬件上下文,是进程执行时,存储在CPU寄存器中的临时数据。
下面综合程序计数器,指令寄存器和进程上下文这三者,来简单解释一下内存中的进程是如何到CPU中运行的。
内存指针PC:存放即将执行的下一条指令的地址。
指令寄存器IR:存放当前正在执行的指令的地址。
一些寄存器:存放该进程相关指令被执行时,相关的一些临时数据,这些临时数据就是进程的硬件上下文。(包括PC和IR中的数据)
由上图可知,CPU执行内存中的进程,本质上就是一条条地执行该进程的相关指令,并在另外一些寄存器中存储指令执行时的相关数据。
简单了解进程是如何在CPU中执行后,我们来思考一个问题:一个进程可能被无限度地执行吗?显然,一个进程是不被允许无限度执行的,进程是需要被切换的,因此这又引出一个新的概念——进程的调度和切换。
进程的调度和切换体现出进程的动态属性。毫无疑问,当CPU中正在运行的一个进程被切换为另一个进程时,我们是需要此时CPU寄存器中的临时数据进行保存的,这样当进程再次被切换回时,可以从之前终止处继续往下执行,这就是进程硬件上下文的保存和恢复。在较老的Linux内核版本中,进程的硬件上下文是直接保存在相应的task_struct对象中的,但在之后的Liunx内核版本中,并未采取这样的做法。
3.3 在Linux中了解进程
3.3.1 查看进程
在Linux中,所有的进程都被放在根目录下的proc目录中,由于Linux中,一切皆文件,所以,每一个进程实质以目录的形式呈现。
上图中的各个数字,即对应进程的pid。
接下来,我们自己写一个程序用以执行,并对该进程进行观察。
上图中的getpid函数是用于获取相应进程的pid。
将该程序跑起来之后,我们通过ls /proc
命令查看是否出现相应的pid。
既然进程在这里体现为一个目录,我们可以去查看这个目录中的具体内容。
这个目录中的内容很多,我们现在主要看两个:cwd
和exe
exe
表示,当前进程所对应的正在运行的可执行文件是什么;cwd
表示的是当前进程的工作目录,这个工作目录可以修改,默认为相应的可执行文件所在的目录。
我们还可以通过ps ajx | head -1 && ps ajx | grep "myporcess"
命令进一步查看进程相关的信息。
其中,./myprocess
即对应我们运行起来的二进制程序,但是我们注意到还有一个grep
,这是为什么?
本质上,Linux命令行中的各个命令都是/usr/bin
目录中的二进制文件,执行相应的命令,实质就是运行相应的二进制程序,因此也会形成相应的进程。
3.3.2 子进程由父进程创建
在上图第一行的进程相关的属性栏中,我们发现,除了PID,还有一个PPID,这又是什么?
在Linux中,一个进程是由其父进程产生的,PPID即为该进程的父进程。
那么,父进程是如何创建子进程的呢?父进程通过一个系统调用接口fork来创建子进程。
这里顺带讲一下fork函数的返回值:如果子进程创建成功,在父进程中返回相应子进程的pid,子进程中返回0;如果子进程创建失败,在原本进程中返回-1。
接下来,我们就在程序中,主动使用fork,来创建子进程。
在程序运行起来之后,我们来查看进程的相关信息。
通过上述例子,我们可以看到,确实是由父进程来创建子进程。
我们还可以发现,./myprocess也有一个父进程,其pid为14006,这又是什么呢?
我们进一步对其查看发现,实质上14006对应的进程即为-bash
,即命令行解释器。
也就是说,Linux中,执行命令行中相应指令所形成的进程,都是由命令行解释器,即shell外壳生成的。
最后,我们来讲讲上述代码创建子进程后,为什么会有那样的运行结果。
本质上,父进程在创建子进程后,就会有两个执行流,一个是父进程的执行流,一个是子进程的执行流,这两个执行流在fork函数创建子进程的核心功能完成后,便会共享之后(包括主函数中的fork之后)的所有代码和数据(如果数据不被修改的话),因此就有了上述的输出结果。
所以,我们可以发现,在多进程的程序中,一个变量可以有两个值,一个函数可以有两个返回值,if-else语句中的 if-else if-else三者可以同时成立并执行。
4. 进程状态
什么是进程状态?简单来说,一个进程处在怎样的状态,就决定了这个进程接下来要做什么工作。
为什么要设计各种各样的进程状态呢?我觉得其中一个原因是:资源是有限的。对于计算机而言,资源分为两类:CPU资源和I/O资源。内存中的进程有很多,而CPU资源与I/O资源是有限的,所以必然存在进程竞争资源,而为了更好地进行资源分配,就需要对进程状态进行划分。
在操作系统相关的教科书上,应该都有这样一幅关于进程状态的图:
实际上,在具体的操作系统设计时,进程的状态不一定完全符合上图,更准确地说,上图仅为设计进程状态的指导思想,而并非实际的落地方案。
4.1 Linux中进程的双链表管理方式
在讲具体的进程状态前,我们要先弄清楚Linux中是如何进行双链表管理的。
与我们以往所认知的双链表不同,不同进程之间的链接,并不是直接在task_struct
这个结构体中添加task_struct* next 和 task_struct* prev
这两个指针,而是添入了一个list_node node
这个成员变量,其中list_node是一个结构体,其中包含两个成员:list_node* next,list_node* prev
。
这样,不同进程的task_struct
之间,就通过这个list_node
串在一起,构成了双链表,如下所示:
这样管理双链表有何好处呢?好处在于,一个双链表的结点,即一个进程,可以同时链入多个链表中,而不用破坏任何一个链表的结构,只需要在task_struct增添list_node类型的成员即可。
除此之外,还有一个问题:不同的task_struct
之间,通过list_node
链接在一起,我们在不同的task_struct
间切换时,实质上是在list_node
间切换,那么我们该如何拿到task_struct
中的其它数据呢?
实质上,我们有list_node
的地址,同时知道相应结构体的类型,即task_struct
,我们就可以拿到task_struct
的地址,具体代码是:task_struct* ptr = (task_struct*)(&node - &((task_struct*)0 -> node))
。这样,我们就拿到了相应进程task_struct
的地址,也就可以任意访问其中的成员了。
4. 2 进程具体状态
4.2.1 新建状态
我们知道,操作系统在内存中使用双链表来管理所有进程,单纯处在这个全局双链表中的进程就为新建状态(创建状态).
4.2.1 就绪状态和运行状态
前面讲过,不同进程之间会竞争CPU资源,那么对于CPU而言,如何调度这些进程,显然需要一种调度算法进行调度,比如说用一个FIFO结构的调度队列(当然,这种调度算法过于简单,一般是不会采用的)。
一个进程如果正在被CPU调度,那么这个进程就处在运行状态;一个进程已经准备就绪,即将被CPU调度,那么就处在就绪状态。但是,有时并不对二者做区分,而都称为运行状态。
4.2.2 阻塞状态
前面讲过,不同进程之间除了竞争CPU资源,还会竞争I/O资源,即相关外设硬件资源。但是一个硬件不可能任何时候都是就绪的,比如说,一个可执行程序要从键盘中读取数据,那么这个进程首先处在运行状态,然后由于要从键盘中读取数据,所以操作系统会将这个进程从CPU的调度队列中拿出,转而放入键盘的相关等待队列中来获取键盘资源,此时进程的状态就为阻塞状态。
如果键盘中一直不输入数据,那么该进程就会阻塞,即一直处在键盘资源的等待队列中,而无法回到CPU的调度队列中。
4.2.3 挂起状态
挂起状态是指在内存中,一个进程只有相关PCB结构,而没有相应的代码和数据(放至硬盘上的swap分区中),此时该进程就处在挂起状态。
挂起状态分为就绪挂起和阻塞挂起,但不管是哪种挂起状态,其本质上都是因为当内存资源不足时,操作系统主动将一些不是正在运行的进程的代码和数据从内存中拿出,换至硬盘上的swap分区中,进而缓解内存资源紧张。
如果一个进程处在阻塞状态,其代码和数据被挪出,那么就称为阻塞挂起状态;如果一个进程处在就绪状态,其代码和数据被挪出,那么就成为就绪挂起状态。
4.3 Linux中的进程状态
在Linux内核源码中,进程状态被定义如下:
static const char *const task_state_array[] = {"R (running)", /*0 */"S (sleeping)", /*1 */"D (disk sleep)", /*2 */"T (stopped)", /*4 */"t (tracing stop)", /*8 */"X (dead)", /*16 */"Z (zombie)", /*32 */
}
4.3.1 R状态
在Linux中,没有就绪状态,统一为R,即运行状态。
在程序运行起来,进入死循环后,我们可以发现,相关进程即为R运行状态。(这里对R后的+稍作解释,有+,表示该进程是前台进程,是可以用ctrl + c直接强行干掉的;没有+,表示是后台进程,是无法用ctrl + c直接干掉的)
4.3.2 S状态
S状态,是休眠状态,准确来说,是浅度休眠状态,或可中断睡眠状态,隶属于之前所讲的阻塞状态。
当一个进程在进行硬件资源的等待时,就会进入S状态。
上述进程在等待键盘资源时,经过查看,处于S休眠状态。
4.3.3 D状态
D状态,为磁盘休眠状态,或者称为深度休眠状态,或不可中断睡眠状态,这种状态同样属于阻塞状态。
D状态与上述的S状态有何区别呢?
处在S状态下的进程会对外界事件进行响应,可以被ctrl + c直接干掉,同时,在内存资源严重不足时,整个进程可以被操作系统直接干掉。
处在D状态下的进程不会对外界事件进行相应,在内存资源严重不足时,也不允许操作系统将该进程干掉。处在这个状态下的进程,除非进程自身主动脱离该状态,否则只有关闭或重启计算机,方能除去该进程。
那么,什么情况下,进程会进入D状态呢?一般的,一个进程在进行磁盘级I/O时,为了防止数据被丢失,这个进程就会进入D状态,不对外界事件作任何相应,只有当磁盘级I/O结束时,进程方会脱离D状态。
4.3.4 T状态
T状态,即停止状态。当一个进程进入停止状态时,便不再运行了。
我们可以使用kill命令来使一个进程进入停止状态。
使用kill -l
对相关kill
指令的编号进行查看。
注意,9编号用以杀死进程,19编号用以停止进程,18编号用以使停止进程继续运行。
现在myfork这个进程正处于运行状态,我们使用kill +进程PID -19
,来暂停这个进程。
不过有一点需要注意,使用kill暂停这个进程后,会使得进程变为后台进程,如上所示。
我们还可以进一步使用kill命令,将这个暂停的后台进程变为正在运行的后台进程。
4.3.5 t 状态
t 状态,又称为追溯停止状态。
我们可以通过gdb调试加上断点来实现这个状态。
本质上,gdb调试一个可执行程序就是在创建一个进程,而在gdb调试中,让被调试程序运行起来,本质上就是gdb再创建了一个子进程,然后让这个子进程去执行相应的代码。而由于在gdb调试中添加了断点,子进程执行到断点处便无法继续往下执行,此时该子进程便进入 t 状态。
4.3.6 x状态和Z状态
进程的创建是把相应的task_struct和代码数据加载到内存中的过程,进程的结束,则是进程的创建的反过程,即内存释放相应资源的过程。
x状态,即代表一个进程彻底结束时的状态,但是一个进程结束时,并不能立刻进入x状态,需要先进入Z状态,Z即Zombie,处于该状态的进程,又被我们称为僵尸进程。
要弄清除为什么要有僵尸进程,我们首先要明确创建进程的作用。创建一个进程,本质上是为了完成相应的任务,而进程都是由另一个进程创建的,那子进程执行任务结束了,需不需要向父进程汇报任务完成的情况呢?毫无疑问,是绝对需要的。
因此,一个进程结束,不能立刻标记这个进程死亡,即进入x状态,然后将其相应资源全部释放,而是先进入僵尸状态,等待父进程获取子进程的任务完成情况后,再进入x状态,释放资源。
需要说明的是,由于进程执行的情况依旧是保存在相应的task_struct中,不同的数字标明不同的状态。因此,进程进入僵尸状态时,会将相应的代码数据释放掉,但是保留内存中的task_struct,父进程要从子进程的task_struct中,获取退出码。
正常情况下,僵尸进程由父进程读取退出码后,便进入x状态,彻底释放资源;但是,如果一个僵尸进程的父进程不对该进程做任何处理,那么这个子进程就会一直处在僵尸进程的状态,这就意味着,这个进程的task_struct会一直在内存中,进而引起内存泄漏。
下面我们在Linux下,来模拟实现一个僵尸进程。
将进程执行起来之后,查看进程状态。
可以看到,PID为15507的子进程处于Z状态,为僵尸进程。处在僵尸状态下的进程,是无法直接被kill命令杀死的。
在上述程序中,子进程结束后,父进程的代码为一个死循环,一直无法结束。而我们所创建的父进程,是不会主动读取子进程的退出码的(如果要主动读取退出码,可以在父进程的代码中使用wait函数),因此子进程就进入了僵尸状态。但在父进程正常结束后,会由操作系统调用wait来释放子进程中的资源。
上面的情况是父进程晚于子进程结束,但如果父进程早于子进程结束,又会出现什么情况呢?
如果一个父进程早于子进程结束,即父进程结束后,子进程仍然在运行,此时我们称这个子进程为孤儿进程。
一个子进程成为孤儿进程后,会被1号进程领养,即1号进程成为该子进程的新父进程。
下面我们来模拟实现孤儿进程。
我们可以看到,在父进程结束后,相应的子进程便被1号进程领养,同时该子进程会转变为后台进程。
那1号进程又是谁呢?我们可以使用top命令,在Linux下查看。
1号进程在Linux中本质上就是一个systemd或init命令,我们可以将其理解为操作系统的一部分。
可是,为什么一个孤儿进程要被领养呢?因为孤儿进程也需要被读取退出码,也需要释放内存中的相应资源。1号进程领养该进程,是为了在该子进程结束后,能够彻底回收该子进程的相关资源,避免内存泄漏。
上面所讲的孤儿进程情况,是父进程先于子进程结束,父进程的这个结束,可以是正常结束,也可以是意外终止,比如使用kill命令杀掉进程。
5. 进程调度
5.1 进程优先级
什么是进程的优先级呢?进程的优先级,即进程得到某资源的先后顺序,这个资源可以是CPU资源,也可以是I/O资源,但讲进程的优先级时,一般都是指获得CPU资源。
为什么进程要有优先级呢?本质上是因为资源有限,资源少,进程多。比如说,总共只有1个CPU,却有100个进程,任意时刻,1个CPU只能由一个进程使用,那么,肯定就要确定这些进程被CPU调度的顺序,即进程的优先级。
那么在Linux中,是如何确定一个进程的优先级呢?
首先,我们要明确进程的优先级实际体现为task_struct中的一个数字,这个数字越小,代表进程的优先级越高,也就越先获得资源。
在Linux下,我们使用ps -l
来查看当前终端会话中的进程信息。
UID:表示该进程的执行者身份,即启动该进程的用户。
PRI:代表该进程的优先级。
NI:表示该进程的nice值。
实质上,在Linux中,优先级的确定并不是由单一变量决定的,实际的计算公式为:PRI = 80 + nice。也就是说,在Linux下,固定了一个80的基准值,通过改变nice值进而改变优先级。nice值的取值范围为[-20,19],相应地,优先级的范围为[60,99],共40个优先级。
在Linux中,我们可以使用top命令来更改一个进程的nice值。一般情况下,降低一个进程的优先级,即调高nice值是被允许的;但是,提高一个进程的优先级,即调低nice值,普通用户是无法实现的,需要超级用户的权限。
、
使用top,调出如下窗口。
按下r
,然后输入要更改nice值的进程的PID,再输入相应的nice值即可完成更改。
注意,nice的范围在[-20,19]之间,低于-20的值,会置为-20;高于19的值,会置为19。
下面解释一下关于nice值的两个问题。
为什么nice值要有范围?
nice的范围限定,实质上是限定了用户对优先级的调整,无论是超级用户,还是普通用户,都只能在一个优先级的范围内调整优先级,而不能任意调整优先级。这实际上是对系统的保护,如果用户能够任意调整进程的优先级,就可能导致一个资源被一个并不重要的进程长久占用,而其它重要的进程迟迟无法使用相应资源,造成进程饥饿问题**,严重时,甚至可能导致系统崩溃。**
为什么nice值的范围是[-20,19]?
这个问题,我们先抛出来,这个范围的确定与进程的调度算法有密切关系,等到讲进程调度算法时,再来解答。
5.2 进程切换
5.2.1 进程相关概念补充
竞争性:
进程之间具有竞争性,其具体表现为进程的优先级。
独立性:
进程之间具有独立性,一个进程的状态如何,并不影响另一个进程的状态。
并行:
并行是指,多个进程在多个CPU中,分别且同时地运行。
并发:并发是指,任意一个时刻,一个CPU中只能运行一个进程,但是多个进程在一段时间内,在同一个CPU上进行进程切换,那么在这段时间内,这多个进程都得以同时推进。
5.2.2 进程切换
我们前面提到,CPU内有一套寄存器,用以临时存储在CPU上运行的进程的硬件上下文。进程切换,实际上就是CPU寄存器中进程的硬件上下文的切换。
当代的计算机系统,基本都是分时操作系统,即每个进程都有自己的时间片,这个时间片决定了该进程在操作系统上能够运行的时间,时间一到,就要将该进程从CPU上剥离,进而切换其它进程在CPU上运行。
这个剥离和切换,就是将时间片用完的进程的硬件上下文进行存储(在老的Linux内核版本中,是存储在task_struct中的tss_struct,即任务状态段这个结构体中的),然后将CPU寄存器中存储的数据更新为下一个要运行进程的硬件上下文,这样就完成了进程的剥离和切换。
注意,剥离进程的硬件上下文一定要进行存储,这样当再次轮到该进程被CPU调用时,能够进行硬件上下文的恢复,以便从上次中断处,继续向后运行该进程。
下图所示,即为在较老的Linux内核版本中的,tss_struct 的结构。
5.3 进程调度
在上面的介绍中,我们知道分时操作系统,每个进程都有其相应的时间片,时间片的时间用完了,就要进行进程切换,可是,在实际中,我们如何知道切换到哪个进程呢?这就涉及到进程调度的问题了。
那么在Linux中,进程是如何调度的呢?
每一个CPU,都有一个对应的runqueue结构,用以确定进程的调度顺序。
- 在这个结构体中,有关于CPU要执行进程数量的负载因子,这个负载因子主要用于多CPU的情况,因为多个CPU要考虑进程的分配均衡问题。
- 与进程调度直接相关的是图中的
queue[140]
,其中存储值的类型为struct list_node*
,也就是说,通过这个数组,可以拿到相应进程的task_struct
,进而用以进程调度。这个数组共140个元素,下标为0~139,我们先不管0-99编号,而关注100-139。100-139刚好是40个编号,正好对应上文所讲的nice值跨度为40的范围。通过调整nice值,进而可调整进程优先级在**[60,99]**之间,然后再加上40,刚好是100-139,与数组的下标相对应。这样,进程的优先级就由其所处数组的位置来确定了。
进程进行调度时,就在queue中首先查找非空指针,找到非空指针后,按照其所指向的双链表顺序,依次调用相关进程。
但是,如果在queue中顺序遍历查找非空指针,效率就太低了,因此又引入了int bitmap[5]
这个结构。这是位图的数据结构,一个int
有32个比特位,5个int
,共有160个比特位,而我们只需要用到其中140个即可。按照比特位从低到高,分别对应数组的下标从0到139。比特位为0,代表下应的数组下标处为空;比特位为1,代表不为空。通过位运算的相关操作,我们可以用O(1)的时间复杂度,快速查到queue中的非空指针后进行进程调度,而这就是进程调度的O(1)算法。
但是,我们在上图中发现,有两个queue[140]
,这是怎么回事?这就涉及到活跃进程和过期进程的区分。
实质上,runqueue中所含的成员是struct q arr[2]
,而struct q
中就含有上图所标出的三个变量nr_active(该变量用于记录当前队列中可运行的进程数,用于计算负载因子),bitmap[5],queue[140]
,而这样的结构共有两个,一个对应过期进程,一个对应活跃进程。
似乎一个这样的结构就能实现进程的调度,那Linux下,为什么要给两个这样的结构呢?
我们先不着急解释这个问题,先来弄明白这两个结构是如何运作的。
在CPU进行进程调度时,会首先调用活跃进程。在活跃进程调度期间,新增的进程或时间片用完的活跃进程会被放入到过期队列中,变为过期进程。而当活跃队列中进程调度完毕后,Linux中的操作设计得很巧妙。
上图中,是两个指针,从变量名的命名上,一个是活跃,一个是过期,显然这两个指针就分别指向之前所讲的两个结构体struct q
。在活跃进程调度完毕后,接着要调用过期进程,在此之前先swap(active,expired),
使得原本的过期队列与原本的活跃队列完成交换,然后继续重复上述过程,周而复始。
这样,保证了CPU调度的进程,始终是active指针所指向内容中的活跃进程;而新增进程或时间片用完的活跃进程始终被放到expired指针所指向内容中的过期队列中。
现在,我们可以理解为什么要有两个队列了。如果只有一个队列,那么在调用进程的过程中,对于新增进程或时间片用完的进程,会频繁在进程调度的过程中插队,致使较低优先级的进程始终无法得到相应资源,进而引发进程饥饿问题,影响系统或相关任务的正常运行。
而使用两个队列,新增的进程或时间片用完的进程,始终是放到过期队列中的,而过期进程只有当当前活跃进程全部运行完后,才能转为活跃进程,进行运行,这样就避免了进程的频繁插队问题,从而避免了进程饥饿。
所以,从宏观上看,在这样的设计结构下,对于单个队列内部,优先级会影响获得资源顺序;而在两个队列之间,永远是先活跃进程,后过期进程,优先级并不会影响获得资源顺序。
5.4 分时操作系统与实时操作系统
操作系统有分时与实时之分,在当代计算机中,操作系统主要是分时操作系统;而在工业领域中,实时操作系统却应用甚多。
此处对于这两种操作系统不作详细介绍,简而言之,分时操作系统是基于时间片轮转来调度进程的;实时操作系统是基于优先级来调度进程的。
而Linux操作系统兼具实时和分时两种功能,上述用于进程调度的queue[140],100-139用于分时操作系统,而0-99则专门用于实时操作系统。
6. 命令行参数
对于Linux下的命令行命令,我们之前是理解为:具体命令加上命令选项。现在,我们来重新理解一下命令行命令。
首先,我们来思考一个问题:main函数可以有参数吗?
main函数是可以有参数的,但是一般情况下,main函数的参数,我们都将其省略不写。
int main(int argc,char* argv[])
main函数的这两个参数,argc
表示argv
数组的大小,argv
数组则是存储字符串。
我们看如下代码和其运行结果。
最终程序输出得到的结果是./test,正好是之前输入的命令,这是巧合吗?
实际上,命令行中的所有命令都是命令行参数,而命令行参数本质就是字符串,这些字符串可以被main函数获取,进而实现一定的功能。
那么,这些命令行参数是如何获取的,而这些命令行参数又有什么作用呢?
6.1 命令行参数如何获取
首先,我们一旦远程连接到相应的云服务器上,命令行解释器bash这个进程就会被运行起来,并且始终处在运行状态。
我们输入的命令,也就是命令行参数,即会在相应的显示器文件中,同时也会被bash获取,进而在内存中形成命令行参数表。
而我们又知道,命令行中的命令本质上都是/usr/bin
这个路径下的可执行文件,任何命令的运行本质上就是一个进程,一个由bash父进程创建的子进程。
所以,对于父进程bash的main函数,其相应的命令行参数是从用户输入中直接获得的,并且会在内存中创建命令行参数表,这是父进程的数据;而相应的命令本质上都是由bash父进程创建的子进程,而在不修改的情况下,子进程会共享父进程的代码和数据,这就意味着,父进程bash获得了命令行参数,相应的子进程也就获得了相应的命令行参数。
6.2 命令行参数的意义
我们来看如下代码和其运行情况。
上述程序和运行结果在一定程度上揭示了Linux中的命令以及各个命令运行是如何运行的。
首先,我们要明确char* argv[]
中的这些字符串是怎样的。对于输入的命令,以空格为间隔,以从左到右为数组下标增长顺序,空格前后各为一个字符串,这就是argv[ ]中的内容,argv[ ]以空指针作为最后一个元素(不计算在argc内,argc表示argc中有效字符串的个数)。
考虑到我们书写命令的形式,例如ls -l
,一般是先写对应命令,再明确相应选项。
而bash进程中的main函数,对其获得的命令行参数,即argv[ ]中的内容会进行判断:首先判断用户输入的是什么命令,根据具体的命令到相应的分支中,创建相应的子进程,并执行相应的功能;而在相应的子进程中,又会根据具体的命令选项,继续条件分支,进而执行不同的功能。
所以,我们所输入的命令,本质上都是命令行参数,本质上都是字符串,可以被相关进程的main函数获取,进而在main函数内部,根据字符串的大小以及不同内容,进行条件分支,进而实现不同的功能。
7. 环境变量
7.1 认识环境变量
环境变量是系统上的一些全局变量,不同的环境变量具备不同的用途。
那到底什么是环境变量呢?
我们知道,所有的命令都是/usr/bin
目录下的可执行文件,但是正常情况下,我们运行一个可执行文件,都要给出这个可执行文件的具体路径,例如我们在自己家目录下编译生成的可执行文件,运行前,都要加上./,表示该文件在家目录下,但是,我们使用其它linux中的命令时,并没有指明路径,命令却能正常运行,这是为什么?
在环境变量中,有名为PATH的环境变量。
我们观察其中的各路径(不同路径之间使用冒号分隔),发现其中有一个路径**/usr/bin**,恰好就是我们linux中的各命令对应的可执行文件所在的路径。
所以,linux下的命令,如果不指定具体路径的话,默认都是到PATH这个环境变量中的各个路径中依次去找,找到了,就运行相应的功能;找不到,就什么都不执行。
这也就是为什么linux中的内置命令可以不指定相应路径直接运行,而自身在非PATH路径下创建的可执行文件,一定要指定具体路径。
但是,如果我们作如下改变,将家目录的路径添加到PATH中,那么我们在家目录下创建的可执行文件,就可以不用指定路径。
小提示,$PATH是指获取PATH的具体内容。
7.2 多方法获取环境变量
在Linux中,我们可以使用多种方法获取环境变量。
7.2.1 main函数参数
main函数中还有一个参数,可以用以获取环境变量。
int main(int argc,char* argv[],char* env[])
我们看如下代码及其运行结果。
上图中密密麻麻的这些信息,便是获取到的当前系统中的环境变量。 但是,这种方法,会获取到所有的环境变量,有没有方法能够只获取想要的单个环境变量呢?
7.2.2 通过函数获取单个环境变量
使用getenv函数,来获取单个环境变量,形参为相应环境变量的名称,返回值为环境变量的具体内容。
7.2.3 使用外部变量environ
该外部变量的使用方式与main函数参数env的使用方式相同,实质上,二者在值上是完全相同的,一般都用于查看所有环境变量。
7.2.4 环境变量获取的本质
同命令行参数一样,bash进程在内存中创建一张环境变量表,此表是bash进程的数据,因此bash进程创建的子进程会继承这张环境变量表,因此可以获得环境变量。
那么,父进程bash环境变量表中的环境变量又从哪里来呢?
先说结论:环境变量表中的环境变量,主要从系统的配置文件中来。
我们来看一下系统中的配置文件。
重点关注其中的.bash_profile和.bashrc这两个文件。
我们来看一下bash_profile中的内容。
可以看到,在这个系统配置文件中,恰好有一个PATH环境变量。实质上,当bash进程启动后,bash进程会自动运行该文件中的内容,从而获取相应的环境变量,并放入相应的环境变量表中。
现在,我们可以来回答一个问题。为什么,我们手动修改PATH路径后,只要重新启动xhell,PATH路径会自动复原。
这是因为,我们使用 PATH= 手动修改PATH路径,本质上是修改环境变量表中的内容,而bash的环境变量是内存级的,是在bash进程创建后,加载到内存当中的,但是bash_profile这个文件中的内容并没有改变。因此,关掉xshell,再重新登陆后,bash进程被重新创建,因此便会重新运行bash_profile中的内容,而只要在这个文件中,PATH的内容没有被改变,重启bash进程后,PATH路径就会复原。
下面,我们来验证一下在bash进程启动后,会自动运行bash_profile中的内容。
7.3 环境变量的用途
不同的环境变量有不同的用途,一下举几个例子帮助大家理解。
pwd:
pwd指令的作用是打印当前的工作目录,但是pwd指令是如何知道当前的工作目录呢?
在环境变量中,有两个环境变量,一个是PWD,一个是OLDPWD。显然,前者用于记录当前工作目录,后者由于记录之前一次的工作目录。pwd指令的实质功能,其实就是PWD这个环境变量的内容给打印了出来。
而我们之前在学习linux指令的时候,提到过cd指令的两种快捷操作,一个是cd -
用于切换到前一个目录,另一个是cd ~
,用于切换到家目录。现在,我们明白,cd - 实际就是 cd $OLDPWD
,cd ~ 实际就是 cd $HOME
,本质上都使用了环境变量。
另外,如果我们想要实现一个只能由某个用户运行的程序,我们也可以借助环境变量USER。
首先,我们在用户名为wnf的账号下,运行该程序,得到结果如下:
然后,我们切换到超级用户root中,运行该程序(注意,切换用户的过程中,要么重新使用root账号登陆,要么使用su - root命令,直接su root,很多环境变量无法跟随改变,包括USER)
7.4 环境变量的属性
7.4.1 全局属性
之前在定义的时候说过,环境变量是系统级别的全局变量,因此,环境变量具有全局属性。
理解环境变量的全局属性,其实就是理解linux下全局变量和本地变量的区别。
Linux中,本地变量只在当前会话的bash进程中有效,在bash进程创建的子进程或是其他会话的bash进程中是无效的。
我们在当前bash进程中,创建一个本地变量。
这样创建的本地变量,不是环境变量,只能被bash识别,而不会被bash的子进程继承。
我们再使用export
命令创建一个环境变量。
这样,就成功将haha
添入了环境变量表中,而环境变量表本质上是父进程bash的数据,而这个数据是可以被bash进程创建的子进程继承的。
7.4.2 内建命令
但是,我们同样会发现一个非常奇怪的地方。
在上图中,我们明明将hello创建为一个本地变量,但是echo指令却能够打印出这个全局变量的具体内容,不是说本地变量只在bash中有效,不能被bash的子进程识别吗?
这就涉及到linux中内建命令的知识。
在linux中,有一部分命令是由bash进程直接执行的,而不是由bash进程创建的子进程完成的,而这样的命令,我们就称之为内建命令,而echo就是内建命令,因此上图中的现象就说得通了。
但是,我们查看echo命令的路径时,发现这样的情况,存储指令的目录中,也有这么一个echo,这是为什么?
本质上,我们在命令行中使用echo时,都会被bash识别为内建命令,并不会用到上述路径下的这个可执行文件,这个可执行文件是在别的场景下使用的。
相关文章:
Linux 进程
文章目录 1. 冯诺依曼体系结构1.1 什么是冯诺依曼体系结构1.2 为什么选择冯诺依曼结构 2. 操作系统2.1 操作系统是什么2.2 操作系统如何对硬件资源进行管理2.3 计算机的层状体系结构 3. 进程3.1 进程是什么3.2 进程的相关属性3.3 在Linux中了解进程3.3.1 查看进程3.3.2 子进程由…...
TVM计算图分割--Collage
1 背景 为满足高效部署的需要,整合大量优化的tensor代数库和运行时做为后端成为必要之举。现在的深度学习后端可以分为两类:1)算子库(operator kernel libraries),为每个DL算子单独提供高效地低阶kernel实现。这些库一般也支持算…...
Liunx知识点
1./dev:是系统设备文件存放位置 /home:是普通用户存放目录 /etc:大部分配置文件的存放目录 /mnt:挂载服务需要的目录 /tmp:存放临时文件 /boot:启动文件 /root:root用户存放目录 /var&am…...
全栈架构设计图
以下是针对Vue前端、服务端、管理后台及数据库的架构图和交互流程设计,采用分层结构和模块化设计思路: 一、系统整体架构图 #mermaid-svg-vAtZ3R6d5Ujm6lYT {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}…...
【SAP ME 42】SAP ME 性能改进
性能问题症状 可观察到以下症状:com.sap.me.production$SfcStartService#startSfc - 此 API 方法具有来自 RESOURCE_TYPE_RESOURCE 表的 SQL 查询的运行速度较慢。 com.sap.me.production$CreateSfcService#createSfc - 对于每个创建的车间作业控制,检查在计划标准配置中是否…...
《GPT-4.1深度解析:AI进化新标杆,如何重塑行业未来?》
一、GPT-4.1:AI 领域的 “全能战士” 降临 1.1 发布背景与战略意义 在 OpenAI 的技术迭代版图中,GPT-4.1 被赋予了 “承前启后” 的关键角色。它不仅是 GPT-4o 的全面升级版,更被视为向 GPT-5 过渡的重要桥梁。2025 年 4 月 15 日的发布会上,OpenAI 宣布 GPT-4.1 系列模型…...
node.js 基础
模块导入和导出 形式1 function get_jenkins(){return "jenkins....." }function test_cc(){return "4444444" }export {get_jenkins,test_cc}// 主函数 import { get_jenkins, test_cc } from ./module.js;console.log(get_jenkins()); console.log(tes…...
数据结构中的宝藏秘籍之广义表
广义表,也被称作列表(Lists),是一种递归的数据结构。它就像一个神秘的盒子,既可以装着单个元素(原子),也可以嵌套着其他的盒子(子列表)。比如广义表 (a (b c)…...
电流模式控制学习
电流模式控制 电流模式控制(CMC)是开关电源中广泛使用的一种控制策略,其核心思想是通过内环电流反馈和外环电压反馈共同调节占空比。相比电压模式控制,CMC具有更快的动态响应和更好的稳定性,但也存在一些固有缺点。 …...
汽车免拆诊断案例 | 2011款雪铁龙世嘉车刮水器偶尔自动工作
故障现象 一辆2011款雪铁龙世嘉车,搭载1.6 L 发动机,累计行驶里程约为19.8万km。车主反映,该车刮水器偶尔会自动工作,且前照灯偶尔会自动点亮。 故障诊断 接车后试车发现,除了上述故障现象以外,当用遥控器…...
#去除知乎中“盐选”付费故事
添加油猴脚本,去除知乎中“盐选”付费故事 // UserScript // name 盐选内容隐藏脚本 // namespace http://tampermonkey.net/ // version 0.2 // description 自动隐藏含有“盐选专栏”或“盐选”文字的回答卡片 // author YourName // mat…...
github 项目迁移到 gitee
1. 查看远程仓库地址 git remote -v 2. 修改远程仓库地址 确保 origin 指向你的 Gitee 仓库,如果不是,修改远程地址。 git remote set-url origin https://gitee.com/***/project.git 3. 查看本地分支 git branch 4. 推送所有本地分支 git p…...
2025年03月中国电子学会青少年软件编程(Python)等级考试试卷(五级)答案 + 解析
青少年软件编程(Python)等级考试试卷(五级) 分数:100 题数:38 一、单选题(共25题,共50分) 1. 以下哪个选项不是Python中的推导式?( ) A. 列表推导式 B. 字典推导式 C. 集合推导式 D. 元组推导式 正确答案:D 答案解析:Python中的推导式包括列表推导式、字典推导式…...
[Unity]-[UI]-[Prefab] 关于UGUI UI Prefab的制作技巧
从上到下,从外到里,多用空物体套壳 这个意思就是说在使用ugui时制作prefab时,要遵循“从上到下,从外到里,多用空物体套壳”的原则,好处就是后面好修改,并且可以复用不同的prefab子模块。且在布…...
【Reading Notes】(8.3)Favorite Articles from 2025 March
【March】 雷军一度登顶中国首富,太厉害了(2025年03月02日) 早盘,小米港股一路高歌猛进,暴涨4%,股价直接飙到52港元的历史新高。这一波猛如虎的操作,直接把雷军的身家拉到了2980亿元,…...
2021-11-09 C++倍数11各位和为13
缘由c函数题找数字的-编程语言-CSDN问答 void 倍数11各位和为13(int n, int& b, int* h) {//缘由https://ask.csdn.net/questions/7559803?spm1005.2025.3001.5141b !(n % 11);while(n)*h n % 10, n / 10; }int a 1, b 1, c 0, d 0;while (a < 100){倍数11各位和…...
2025年03月中国电子学会青少年软件编程(Python)等级考试试卷(六级)真题
青少年软件编程(Python)等级考试试卷(六级) 分数:100 题数:38 答案解析:https://blog.csdn.net/qq_33897084/article/details/147341458 一、单选题(共25题,共50分) 1. 在tkinter的…...
Linux简介
一、Linux 简介 Linux 内核最初只是由芬兰人林纳斯托瓦兹(Linus Torvalds)在赫尔辛基大学上学时出于个人爱好而编写的。 Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 UNIX 的多用户、多任务、支持多线程和多 CP…...
每天学一个 Linux 命令(22):pwd
可访问网站查看,视觉品味拉满: http://www.616vip.cn/22/index.html pwd 命令用于打印当前工作目录(Print Working Directory)的绝对路径,帮助用户快速确认自己在文件系统中的位置。虽然简单,但它是终端操作中不可或缺的基础命令,尤其在处理相对路径或脚本编写时尤为…...
Windows 11设置开机自动运行 .jar 文件
Windows 11设置开机自动运行 .jar 文件 打开启动文件夹: 按下 Win R,输入 shell:startup,回车。 此路径为当前用户的启动文件夹: C:\Users\<用户名>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup创…...
西门子 博途 软件 崩溃
西门子 博途 软件 编译/仿真 崩溃 是因为项目中OPC UA的接口问题 解决拌饭见 https://support.industry.siemens.com/cs/document/109971630/tia-portal-%E5%9C%A8%E7%BC%96%E8%AF%91-plc-%E6%97%B6%E5%B4%A9%E6%BA%83?dti0&dlzh&lcen-WW...
12芯束装光纤不同包层线颜色之间的排列顺序
为什么光纤线必须按照以下颜色顺序进行排序?这其实是为了防止光污染的问题,不同颜色在传递光时从包层表皮漏光传感到梳妆的其它纤芯上,会有光污染的问题,而为了减少并防止光污染的现象,所以在光通信之中,需…...
数据驱动、精准协同:高端装备制造业三位一体生产管控体系构建
开篇引入 鉴于集团全面推行生产运营体建设以及对二级单位生产过程管控力度逐步加强,某高端装备制造企业生产部长王总正在开展新的一年企业生产管控规划工作,为了能够更好地进行体系规划与建设应用,特邀请智能制造专家小智来进行讨论交流。 王…...
HAL详解
一、直通式HAL 这里使用一个案例来介绍直通式HAL,选择MTK的NFC HIDL 1.0为例,因为比较简单,代码量也比较小,其源码路径:vendor/hardware/interfaces/nfc/1.0/ 1、NFC HAL的定义 1)NFC HAL数据类型 通常定…...
RAG知识库中引入MCP
MCP(Memory, Context, Planning)是一种增强AI系统认知能力的框架。将MCP引入RAG知识库可以显著提升系统的性能和用户体验。下面我将详细介绍如何实现这一整合。 MCP框架概述 MCP框架包含三个核心组件: Memory(记忆):存储和管理历史交互和知识Context(上下文):理解当…...
再读bert(Bidirectional Encoder Representations from Transformers)
再读 BERT,仿佛在数字丛林中邂逅一位古老而智慧的先知。初次相见时,惊叹于它以 Transformer 架构为罗盘,在预训练与微调的星河中精准导航,打破 NLP 领域长久以来的迷雾。而如今,书页间跃动的不再仅是 Attention 机制精…...
C# 单例模式
创建一个类 在类中定义方法 internal class Config {// 实现单利模式private static Config instance null;private Config() { }private static object Locker new object(); // 定义lock锁// 通过公有的方法 返回实力public static Config GetInstance(){// 空的自己构造…...
mainwidget.cpp:1741:21: error: use of undeclared identifier ‘mainTab‘
这个错误表明在你的代码中,mainTab 这个变量没有被正确声明或定义。这通常是因为以下原因之一: 变量未声明:mainTab 可能没有在类的成员变量中声明。 变量未初始化:mainTab 可能没有在构造函数中正确初始化。 作用域问题&#x…...
OpenCV day6
函数内容接上文:OpenCV day4-CSDN博客 , OpenCV day5-CSDN博客 目录 平滑(模糊) 25.cv2.blur(): 26.cv2.boxFilter(): 27.cv2.GaussianBlur(): 28.cv2.medianBlur(): 29.cv2.bilateralFilter(): 锐…...
MySQL:Join连接的原理
连接查询的执行过程: 确定第一个需要查询的表【驱动表】 选取代价最小的访问方法去执行单表查询语句 从驱动表每获取到一条记录,都需要到t2表中查找匹配的记录 两表连接查询需要查询一次t1表,两次t2表,在两表的连接查询中&…...
Linux MySQL版本升级(rpm安装方式)
一、背景 近期生产环境扫描发现MySQL的多个安全漏洞。目前厂商已经发布了升级补丁以修复此安全问题,补丁获取链接:https://www.oracle.com/security-alerts/cpuoct2024.html 二、升级注意事项 备份数据:升级前务必备份数据库。检查兼容性&…...
数字图像处理(膨胀与腐蚀)
腐蚀 核心原理:结构元四肢运算结果全为1,则结构元中心为1,否则为0。 怎么计算是否为1还是为0 结构元的值与前景的值进行与运算,如果结构元四肢的与运算结果全为1,则结构元中心为1,否则为0。 假设下图为结构…...
J值即正义——Policy Gradient思想、REINFORCE算法,以及贪吃蛇小游戏(三)
文章目录 前情提要谁的J值大呢?那么 ∇ θ J ( θ ) \nabla_\theta J(\theta) ∇θJ(θ)要怎么求呢?构建loss函数**代码实现示例**(PyTorch伪代码):前情提要 上回咱说道,对于强化学习而言,J值即正义。 比如,你当了老板,你手下的两个高管,分别都为公司的发展提出了…...
pdfjs库使用3
.App { text-align: center; height: 100vh; display: flex; flex-direction: column; background-color: #f5f5f5; } /* PDF 查看器容器样式 */ .pdf-viewer { flex: 1; padding: 20px; max-width: 100%; margin: 0 auto; box-sizing: border-box; background-color: white;…...
transformer-词嵌入和位置嵌入详解
文章目录 1、介绍一下位置嵌入Positional Encoding、什么是Positional Encoding呢?为什么Transformer需要Positional Enconding? Transformer 的 Positional Encoding 是如何表达相对位置关系的?2、我来简单举个例子2.1 词向量:每个token都会…...
大模型本地部署之ollama安装及deepseek、qwen等模型下载操作
大模型本地部署之----ollama安装指南 最新版--下载方式 Download Ollama on macOS 因github下载较慢,可以网上搜索github加速工具下载 ----download后加版本号 例如: https://github.com/ollama/ollama/releases/download/v0.6.5/OllamaSetup.exe 通过网盘分享…...
ffprobe 输出 HEVC 码流 Level:标准的 “错位” 与分析的 “归位”
问题描述 最近用ffprobe分析HEVC/H265的码流,发现了与理论知识不符合的现象,比如针对一个H265的码流,用ffprobe输入命令 ffprobe -show_streams 1.h265 时,输入如下;可以看到 H265 的码流 Level 显示等于 93,打印值与标准理论值不符合,用其他工具分析此时 Level 等于 3.…...
线程池七个参数的含义
Java中的线程池里七个参数的以及其各自的含义 面试题:说一下线程池七个参数的含义? 所谓的线程池的 7 大参数是指,在使用 ThreadPoolExecutor 创建线程池时所设置的 7 个参数,如以下源码所示: public ThreadPoolExe…...
EnlightenGAN:低照度图像增强
简介 简介:记录如何使用EnlightenGAN来做低照度图像增强。该论文主要是提供了一个高效无监督的生成对抗网络,通过全球局部歧视器结构,一种自我调节的感知损失融合,以及注意机制来得到无需匹配的图像增强效果。 论文题目:EnlightenGAN: Deep Light Enhancement Without P…...
内存函数和动态内存管理
目录 一、memcpy库函数介绍 1. memcpy的使用 2. memcpy的模拟 二、memmove库函数介绍 1. memmove的使用 2. memmove的模拟 三、memset库函数介绍 四、memcmp库函数介绍 五、动态内存中malloc和free 1. malloc 2. free 六、动态内存中calloc和realloc 1. calloc 2. realloc 七、…...
计算机网络 - 在浏览器中输入 URL 地址到显示主页的过程?
第一步,浏览器通过 DNS 来解析 URL,得到相应的 ip 地址(到哪里找) 和 方法(做什么) 第二步,浏览器于服务器建立 TCP 三次握手连接 第三步,建立好连接后,浏览器会组装 HTTP 请求报文…...
Android Studio 常见报错
错误提示: Your build is currently configured to use incompatible Java 21.0.3 and Gradle 6.7.1. Cannot sync the project. 原因: Java JDK和gradle 版本不匹配 两个角度修改: 1.修改gradle 版本 2.修改JDK版本 Gradle 下载 https:…...
RT-DETR源码学习bug记录
事情是这样的,我最近想学习RT-DETR的源码,那就开始吧! 1. 找到官网,找到pytorch版本。 https://github.com/lyuwenyu/RT-DETR/tree/main 2.只想下载一个子目录,方法: https://download-directory.githu…...
数据仓库分层架构解析:从理论到实战的完整指南
数据仓库分层是构建高效数据体系的核心方法论。本文系统阐述ODS、DWD、DWS、ADS四层架构的设计原理,结合电商用户行为分析场景,详解各层功能及协作流程,并给出分层设计的原则与避坑指南,帮助读者掌握分层架构的落地方法。 一、为什…...
基于ubuntu24.10安装NACOS2.5.1的简介
基于ubuntu24.10安装NACOS2.5.1的简介 官方网站地址: https://nacos.io 可访问nacos站点 https://nacos.io/zh-cn/ 2025年04月记录发布 V2.5.1 版本 一、环境预准备 64 bit JDK 1.8; sudo apt update sudo apt install openjdk-8-jdk sudo apt upda…...
【Triton 教程】triton_language.full
Triton 是一种用于并行编程的语言和编译器。它旨在提供一个基于 Python 的编程环境,以高效编写自定义 DNN 计算内核,并能够在现代 GPU 硬件上以最大吞吐量运行。 更多 Triton 中文文档可访问 →https://triton.hyper.ai/ triton.language.full(shape, …...
MARA/MARC表 PSTAT字段
最近要开发一个维护物料视图的功能。其中PSTAT字段是来记录已经维护的视图的。这里记录一下视图和其对应的字母。 MARA还有个VPSTA(完整状态)字段,不过在我试的时候每次PSTAT出现一个它就增加一个,不知道具体是为什么。 最近一直…...
目标检测中的混淆矩阵
一直很疑惑YOLO的这个目标检测 混淆矩阵 🎯 假设任务:检测三种动物(猫、狗、羊) 我们使用一个目标检测模型对图像进行了预测,并收集了如下结果: ✅ 模型预测结果(带类别和框) vs 🟩真实框: 编号真实类别是否被检测到IOU是否合格预测类别备注1猫是✅猫✔️ 正确(…...
前端如何构建跨平台可复用的业务逻辑层(Web、App、小程序)
目录 第一章:跨平台开发的现状与技术选型分析 跨平台技术生态的全景概览 跨平台开发中业务逻辑层的共性需求 不同技术栈对业务逻辑复用的支持程度比较 技术选型中的权衡与思考 第二章:业务逻辑层的核心设计原则与架构理念 设计原则:构建高效业务逻辑层的基础 架构理念…...
day1-小白学习JAVA---JDK安装和环境变量配置(mac版)
JDK安装和环境变量配置 我的电脑系统一、下载JDK1、oracle官网下载适合的JDK安装包,选择Mac OS对应的版本。 二、安装三、配置环境变量1、终端输入/usr/libexec/java_home -V查询所在的路径,复制备用2、输入ls -a3、检查文件目录中是否有.bash_profile文…...