进程管理之基本概念

news2024/12/18 22:14:15

目录

关于进程的基本概念

进程描述符

查看进程

进程标识

进程的生命周期 

僵尸进程、孤儿进程

写时拷贝技术

fork()函数

vfork()函数

终止进程

进程优先级和权重

进程地址空间


关于进程的基本概念


进程和程序是操作系统领域的两个重要的概念,进程是执行中的程序,即一 个程序加到内存后变成了进程,公式表达如下:

进程=程序+执行

程序通常是指完成特定任务的一个有序指令集合或者一个可执行文件,包含可运行的CPU指令和相应的数据等信息,它不具有“生命力”。

进程是一段执行中的程序, 是一个有“生命力”的个体。一个进程除了包含可执行的代码(如代码段),还包含进程的一些活动信息和数据,如用来存放函数形参、局部变量以及返回值的用户栈,用于存放进程相关数据的数据段,用于切换内核中进程的内核栈,以及用于动态分配内存的堆等。进程是用于实现多进程并发执行的一个实体,实现对CPU的虚拟化,让每个进程都认为自已独立拥有一个CPU。实现这个CPU虚拟化的核心技术是上下文切换以及进程调度。

进程描述符

进程是操作系统中调度的一个实体,需要对进程所拥有的资源进行抽象,这个抽象形式称为进程控制块(PCB), 也称其为进程描述符。进程描述符需要描述如下几类信息。

  • 进程的运行状态: 包括就绪、运行、等待阻塞、僵尸等状态。
  • 程序计数器: 记录当前进程运行到哪条指令了。
  • CPU寄存器: 主要用于保存当前运行的上下文,记录CPU所有必须保存下来的寄存器信息,以便当前进程调度出去之后还能调度回来并接着运行。
  • CPU调度信息: 包括进程优先级、调度队列和调度等相关信息。
  • 内存管理信息: 进程使用的内存信息,如进程的页表等。
  • 统计信息:  包含进程运行时间等相关的统计信息。
  • 文件相关信息: 包括进程打开的文件等。


因此进程描述符是用于描述进程运行状况以及控制进程运行所需要的全部信息,是操作系统用来感知进程存在的一个 非常重要的数据结构。任何一个操作系统的实现都需要有一个数据结构来描述进程描述符,所以Linux内核采用一个名为task_struct 的结构体。task_struct 数据结构包含的内容很多,它包含进程所有相关的属性和信息。在进程的生命周期内,进程要和内核的很多模块进行交互,如内存管理模块、进程调度模块以及文件系统模块等。因此,它还包含了内存管理、进程调度、文件管理等方面的信息和状态。Linux 内核把所有进程的进程描述符task_struct数据结构链接成一个单链 表( task_struct->tasks), task_struct 数据结构定义在include/linux/sched.h文件中。

task_struct 数据结构包含的内容可以简单归纳成如下几类。

  • 进程属性相关信息。
  • 进程间的关系。
  • 进程调度相关信息。
  • 内存管理相关信息。
  • 文件管理相关信息。
  • 信号相关信息。
  • 资源限制相关信息。

进程间的关系

操作系统的第一个进程是空闲进程(或者叫作进程0)。此后,每个进程都有一个创建它的父进程,进程本身也可以创建其他的进程,父进程可以创建多个进程。进程类似于一个家族,有父进程、子进程,还有兄弟进程。task_struct数据结构中涉及进程间关系的重要成员如下。

  • real_parent成员: 指向当前进程的父进程的task_ struct数据结构。
  • children成员: 指向当前进程的子进程的链表。
  • sibling成员: 指向当前进程的兄弟进程的链表。
  • group_leader成员: 进程组的组长。

进程调度相关信息

进程一个很重要的角色是作为一个调度实体参与操作系统中的调度,这样就可以实现CPU的虚拟化,即每个进程都感觉直接拥有了CPU。宏观上看,各个进程都是并行执行的,但是微观上看每个进程都是串行执行的。进程调度是操作系统中的个核心功能。

内存管理和文件管理相关信息

进程在运行之前需要先加载到内存,因此进程描述符里必须有抽象描述内存管理的相关信息,必须有一个指向mm_struct数据结构的指针mm。此外,进程在生命周期内总是需要通过打开文件、读写文件等操作来完成一些任务,这就和文件系统密切相关了。task_struct数据结构中与内存管理和文件管理相关的重要成员如下。

  • mm成员:指向进程所管理的内存中总的抽象数据结构mm_struct.
  • fs成员:保存一个指向文件系统信息的指针。
  • files成员:保存一个指向进程的文件描述符表的指针。

查看进程

1. ps命令

ps命令是最基本的进程查看命令,可确定有哪些进程正在运行、进程的状态、进程是否结束、 进程是否僵死、哪些进程占用了过多的资源等等。ps命令最常用的还是监控后台进程的工作情况,因为后台进程是不与屏幕键盘这些标准输入进行通信的。其基本用法为

ps   [选项] 

常用的选项有:  

  • a: 显示系统中所有用户的进程; 
  • x: 显示没有控制终端的进程及后台进程;
  • -e: 显示所有进程
  • r: 只显示正在运行的进程;
  • u: 显示进程所有者的信息;
  • -f: 按全格式显示(列出进程间父子关系); 
  • -l:按长格式显示。

注意有些选项之前没有连字符(-)。如果不带任何选项,则仅显示当前控制台的进程。

最常用的是使用aux选项组合。例如:

 USER表示进程的所有者; PID 是进程号;%CPU表示占用CPU的百分比;%MEM表示占用内存的百分比; VSZ表示占用虚拟内存的数量: RSS表示驻留内存的数量;TTY表示进程的控制终端(值 “?”  说明该进程与控制终端没有关联); STAT表示进程的运行状态(R代表准备就绪状态,S是可中断的休眠状态,D是不可中断的休眠状态,T是暂停执行,Z表示不存在但暂时无法消除,w表示无足够内存页面可分配,<表示高优先级,N表示低优先级,L表示内存页面被锁定,S表示创建会话的进程,1表示多线程进程,+表示是一个前台进程组 ); START是进程开始的时间; TIME是进程已经执行的时间; COMMAND是进程对应的程序名称和运行参数。

通常情况下系统中运行的进程很多,可使用管道操作符和less (或more )命令来查看:

ps aux | less

还可使用grep命令查找特定进程。若要查看各进程的继承关系,使用pstree命令。 

2. top命令
ps命令仅能静态地输出进程信息,而top命令用于动态显示系统进程信息,可以每隔一短时间刷新当前状态,还提供一组交互式命令用于进程的监控。基本用法为:

top [选项]

选项

  • -d指定每两次屏幕信息刷新之间的时间间隔,默认为5s; 
  • -s表示top命令在安全模式中运行,不能使用交互命令; 
  • -c表示显示整个命令行而不只是显示命令名。如果在前台执行该命令,它将独占前台,直到用户终止该程序为止。


在top命令执行过程中可以使用一些交互命令。 例如:按空格将立即刷新显示;按<Ctrl>+<L>键擦除并且重写。

这里给出一个简单的例子:

首先显示的是当前进程的统计信息,包括用户(进程所有者)数、负载平均值、任务数、CPU占用、内存和交换空间的已用和空闲情况。然后逐条显示各个进程的信息,其中进程指的是PID; USER表示进程的所有者; PR表示优先级: NI表示nice值(负值表示高优先级,正值表示低优先级); VIRT 表示进程使用的虚拟内存总量(单位kb ); RES表示进程使用的、未被换出的物理内存大小(单位kb); SHR表示共享内存大小; S表示进程状态(参见ps命令显示的STAT ); %CPU和%MEM分别表示CPU和内存占用的百分比; TIME+ 表示进程使用的CPU时间总计(单位1/100秒); COMMAND是进程对应的程序名称和运行参数。

进程标识

在创建时会分配唯一的号码来标识进程, 这个号码就是进程标识符(PID)。PID存放在进程描述符的pid字段中,PID是整数类型。为了循环使用PID,内核bitmap机制来管理当前已经分配的PID和空闲的PID, bitmap 机制可以保证每个进程仓创建时都能分配到唯一的 PID。

通过使用系统调用函数,getpid和getppid即可分别获取进程的PID和PPID。

 

进程的生命周期 

虽然每个进程都是一个独立的个体,但是进程间需要相互沟通和交流,如文本进程需要等待键盘的输入。

  • TASK_RUNNING (可运行态或者就绪态或者正在运行态---R): 这个状态的名字是正在运行的意思,可是在Linux内核里不一定是指进程正在运行,所以很容易让人混淆。它是指进程处于可运行的状态,或许正在运行,或许在就绪队列(也称为调度队列)中等待运行。因此Linux内核对当前正在运行的进程没有给出一个明确的状态,不像典型操作系统中给出两个很明确的状态,如就绪态和运行态。它是运行态和就绪态的一个集合,需要特别注意。
  • TASK_INTERRUPTIBLE (可中断睡眠态---S): 进程进入睡眠状态(被阻塞)来等待某些条件的达成或者某些资源的就位,一旦条件达成或者资源就位,内核就可以把进程的状态设置成TASK_RUNNING并将其加入就绪队列中。这个状态也称为浅睡眠状态。
  • TASK_UNINTERRUPTIBLE (不可中断态---D):这个状态和上面的TASK _NTERRUPTIBLE状态类似,唯一不同的是,进程在睡眠等待时不受干扰,对信号不做任何反应,所以这个状态称为不可中断态。通常使用ps命令看到的被标记为D状态的进程,就是处于不可中断态的进程,不可以发送SIGKILL信号使它们终止,因为它们不响应信号。这个状态也称为深度睡眠状态。
  • _ TASK_STOPPED (终止态---X): 进程停止运行。
  • EXIT_ZOMBIE (僵尸态---Z): 进程已经消亡,但是task_struct 数据结构还没有释放,这个状态叫作僵尸态,每个进程在它的生命周期中都要经历这个状态。子进程退出时,父进程可以通过wait()或者waitpid()来获取子进程消亡的原因。

上述5种状态在某种条件下是可以相互转换的。也就是说,进程可以从一状态转换到另一种状态, 如进程在等待某些条件或者资源时从可运行态转换到可中断睡眠状态。

僵尸进程、孤儿进程

一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程就会进入僵尸状态。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
	printf("I am running...\n");
	pid_t id = fork();
	if(id == 0){ //child
		int count = 5;
		while(count){
			printf("I am child...PID:%d, PPID:%d, count:%d\n", getpid(), getppid(), count);
			sleep(1);
			count--;
		}
		printf("child quit...\n");
		exit(1);
	}
	else if(id > 0){ //father
		while(1){
			printf("I am father...PID:%d, PPID:%d\n", getpid(), getppid());
			sleep(1);
		}
	}
	else{ //fork error
	}
	return 0;
} 

 运行该代码后,我们可以通过以下监控脚本,每隔一秒对该进程的信息进行检测。

while :; do ps axj | head -1 && ps axj | grep proc | grep -v grep;echo "----------";sleep 1;done

 僵尸进程危害

 进程的退出状态必须被维持下去,因为他要告诉父进程,你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态。维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,Z状态一直不退出,PCB一直都要维护。如果一个父进程创建了很多子进程,就是不回收,就会造成内存资源的浪费。因为数据结构对象本身就要占用内存,C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间。僵尸进程会产生资源泄露。

孤儿进程:子进程先于父进程退出,运行在后台,父进程成为1号进程,退出后由1号进程回收资源,直接释放所有资源。

孤儿进程的产生一般都会带有目的性,比如我们需要一个程序运行在后台,或者我们不想一个进程退出后成为僵尸进程之类的需要。

写时拷贝技术

在传统的UNIX操作系统中,创建新进程时会拷贝父进程所拥有的所有资源,这样进程的创建变得很低效。每次创建子进程时都要把父进程的进程地址空间中的内容拷贝到子进程,但是子进程还不一定全盘接收,甚至完全不用父进程的资源。子进程调用execve()系统调用之后可能和父进程分道扬镳。

现代的操作系统都采用写时复制(Copy On Write, COW)的技术进行优化。写时拷贝就是父进程在创建子进程时不需要拷贝进程地址空间的内容到子进程,只需要拷贝父进程的进程地址空间的页表到子进程,这样父、子进程就共享了相同的物理内存。当父、子进程中有一方需要修改某个物理页面的内容时,触发写保护的缺页异常,然后才拷贝共享页面的内容,从而让父、子进程拥有各自的副本。也就是说,进程地址空间以只读的方式共享,当需要写入时才发生拷贝。写时复制是一种可以推迟甚至避免拷贝数据的技术,它在现代操作系统中有广泛的应用。

在采用了写时复制技术的Linux内核中,用frk0函数创建一个新进程的开销变得很小,免去了拷贝父进程整个进程地址空间中的内容的巨大开销,现在只需要拷贝父进程贯我的一点开销。

 

 

fork()函数

fork()是POSIX标准中定义的基本的进程创建函数。

使用fork()函数来创建子进程时,子进程和父进程有各自独立的进程地址空间,但是共享物理内存资源,包括进程上下文、进程堆栈、内存信息、打开的文件描述符、进程优先级、根目录、资源限制、控制终端等。在fork()创建期间,子进程和父进程共享物理内存空间,当它们开始执行各自程序时,它们的进程地址空间开始分道扬镳,这得益于写时复制技术。

子进程和父进程也有如下一些区别。

  • 子进程和父进程的ID不一样。子进程不会继承父进程的内存方面的锁,如mlock()。
  • 子进程不会继承父进程的一些定时器,如setitimer()、alarm()、 timer_create()。
  • 子进程不会继承父进程的信号量,如semop()。

对于fork()函数,在用户空间中的C库函数的定义如下。

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


pid_ t  fork(void) ;

fork()函数会有两次返回,一次在父进程中,另一次在子进程中。如果返回值为0,说明这是子进程;如果返回值为正数,说明这是父进程,父进程会返回子进程的ID;如果返回一1,表示创建失败。


fork()函数通过系统调用进入Linux内核,然后通过do_ fork()函数来实现。

SYSCALL_DEFINEO (fork)

{
        return   _ do_ fork (SIGCHLD, 0,0, NULL, NULL, 0) ;
}

fork()函数只使用SIGCHLD标志位,在子进程终止后发送SIGCHLD信号通知父进程。fork()是重量级调用,为子进程建立了一个基于父进程的完整副本,然后子进程基于此执行。为了减少工作量,子进程采用写时拷贝技术,只拷贝父进程的页表,不会复制页面内容。当子进程需要写入新内容时才触发写时拷贝机制,并为子进程创建一个副本。
fork()函数也有一些缺点, 尽管使用了写时拷贝机制技术,但是它还需要拷贝父进程的页表,在某些场景下会比较慢,所以有了后来的vfork)和clone()。 

vfork()函数

vfork()函数和fork(函数类似,但是vfork()的父进程会一直阻塞, 直到子进程调用exit()或者execve()为止。在fork()实现写时拷贝之前,fork()之后马上执行execve()会造成的地址空间浪费和效率低下,因此设计了vfork()系统调用。

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


 pid t vfork(void) ;

vfork()函数通过系统调用进入Linux内核,然后通过do_ fork()函 数来实现。
 

SYSCALL DEFINEO (vfork)

{

        return  _do_ fork (CLONE_ VFORK   |  CLONE_VM   |  SIGCHLD,0,                                        0, NULL, NULL, 0) ;

}

 vfork(的实现比fork()的实现多了两个标志位,分别是CI ONE_VFORK和CLONE_VM。CLONE_VFORK表示父进程会被挂起,直至子进程释放虚拟内存资源。CLONE_VM表示父、子进程执行在相同的进程地址空间中。另外,通过vfork()可以避免拷贝父进程的页表项。

终止进程

系统中有源源不断的进程诞生,当然,也有进程会终止。进程的终止有两种方式:一种是自愿地终止,包括显式地调用exit()系统调用或者从某个程序的主函数返回;另一种是被动地收到终止信号或者异常终止。

进程主动终止主要有如下两个途径。

  • 从main()函数返回,链接程序会自动添加exit()系统调用。
  • 主动调用 exit系统调用。

进程被动终止主要有如下3个途径。

  • 进程收到一个自己不能处理的信号。
  • 进程在内核态执行时产生 了一个异常。
  • 进程收到 SIGKILL等终止信号。


当一个进程终止时,Linux 内核会释放它所占有的资源,并把这个消息告知父进程,而个进程终止时可能有两种情况。

  • 若它先于父进程终止,那么子进程会变成一个僵尸进程,直到父进程调用wait()才算最终消亡。
  • 若它也在父进程之后终止, 那么init 进程将成为子进程的新父进程。

kill 命令是通过向进程发送指定的信号来结束进程的,基本用法为

kill [-s,--信号 | -p] [-a] 进程号...

选项-s指定需要送出的信号,既可以是信号名也可以是对应数字。默认为TERM信号(值15)。

选项-p指定kill命令只是显示进程的pid,并不真正送出结束信号。

信号SIGKILL (值为9)用于强行结束指定进程的运行,适合于结束已经挂死而没有能力自动结束的进程,这属于非正常结束进程。

假设某进程(PID为3456 )占用过多CPU资源,使用命令kill 3456并没有结束该进程,这就需要执行命令kill -9 3456强行将其终止。

Linux下还提供一个kll命令,能直接使用进程的名字而不是进程号作为参数,例如:

killall xineta

如果系统存在同名的多个进程,则这些进程将全部结束运行。 

进程优先级和权重

操作系统中经典的进程调度算法是基于优先级调度的。优先级调度的核心思想是把进程按照优先级进行分类,紧急的进程优先级高,不紧急、不重要的进程优先级低。调度器总是从就绪队列中选择优先级高的进程进行调度,而且优先级高的进程分配的时间片会比优先级低的进程长,这体现了一种等级制度。
Linux操作系统最早采用nice值来调整进程的优先级。nice值的思想是要对其他进程友好,降低优先级来支持其他进程消耗更多的处理器时间。它的范围-20~+19,默认值是0。nice值越大,优先级反而越低;nice值越低,优先级越高。nice 值-20表示这个进程是非常重要的,优先级最高;而nice值19则表示允许其他进程比这个线程优先享有宝贵的CPU时间,这也是nice值的由来。

内核使用0~ 139的数值表示进程的优先级,数值越小,优先级越高。优先级0~99给给实时进程使用,100~139 给普通进程使用。另外,在用户空间中有一个传统的变量nice,它用于映射普通进程的优先级,即100~ 139。

优先级在Linux内核中的划分方式如下。

  • 普通进程的优先级: 100~ 139。
  • 实时进程的优先级: 0~99。
  • deadline进程的优先级: - 1。

进程地址空间

进程地址空间是指进程可寻址的虚拟地址空间。在64位系统的处理器中,进程可以寻址256TB的用户态地址空间,但是进程没有权限去寻址内核空间的虚拟地址,只能通过系统调用的方式间接访问。而用户空间的进程地址空间则可以被合法访间,地址空间称为内存区域。进程可以通过内核的内存管理机制动态地添加和删除这些内存区域,这些内存区域在Linux内核采用VMA数据结构来抽象描述。

每个内存区域具有相关的权限,如可读、可写或者可执行权限。若一个进程访问了不在有效范围的内存区域,或者非法访问了内存区域,或者以不正确的方式访问了内存区域,那么处理器会报告缺页异常。在Linux内核的缺页异常处理中会处理这些情况,严重的会报告“SegmentFault”并终止该进程。

内存区域包含内容如下:

  • 代码段映射,可执行文件中包含只读并可执行的程序头,如代码段和init段等。
  • 数据段映射,可执行文件中包含可读/可写的程序头,如数据段和未初始化数据段等。
  • 用户进程栈。通常位于用户空间的最高地址,从上往下延伸。它包含栈帧,里面包含了局部变量和函数调用参数等。注意,不要和内核栈混淆,进程的内核栈独立存在并由内核维护,主要用于上下文切换。
  • mmap映射区域。位于用户进程栈下面,主要用于mmap系统调用,如映射一个文件的内容到进程地址空间等。
  • 堆映射区域。malloc()函数分配的进程虚拟地址就是这段区域。


进程地址空间里的每个内存区域相互不能重叠。如果两个进程都使用malloc()函数来分配内存,分配的虚拟内存的地址是一样的,那是不是说明这两个内存区域重叠了呢?

如果理解了进程地址空间的本质就不难回答这个问题了。进程地址空间是每个进程可以寻址的虚拟地址空间,每个进程在执行时都仿佛拥有了整个CPU资源,这就是所谓的“CPU虚拟化”。因此,每个进程都有一套页表,这样每个进程地址空间就是相互隔离的。即使它们的进程地址空间的虚拟地址是相同的,但是经过两套不同页表的转换之后,它们也会对应不同的物理地址。

 mm_struct 数据结构
Linux内核需要管理每个进程所有的内存区域以及它们对应的页表映射,所以必须拍象出一个数据结结构,这就是mm_ struct数据结构。进程控制块( PCB)——数据结构task_struct中有一个指针mm,该指针指向这个task_struct数据结构。

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

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

相关文章

LeetCode 145. 二叉树的中序遍历

LeetCode 145. 二叉树的中序遍历 难度&#xff1a;easy\color{Green}{easy}easy 题目描述 给你一棵二叉树的根节点 rootrootroot &#xff0c;返回其节点值的 后序遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[3,2,1]示例 2&#xff1a…

mitmproxy使用总结

mitmproxy is a free and open source interactive HTTPS proxy. 这官网上的一句话说明mitmproxy的身份&#xff0c;MITM 即中间人攻击&#xff08;Man-in-the-middle attack&#xff09;&#xff0c;与charles、fidder之类的抓包工具不同的是可以增加一些自定义处理的扩展脚本…

aws appmesh 在ec2上部署和使用appmesh

参考资料 Getting started with AWS App Mesh and Amazon EC2 之前的文章中我们已经介绍了aws的服务网格场频appmesh&#xff0c;并且在eks环境中进行了部署和简单功能的测试。由于eks环境较为复杂&#xff0c;本文在ec2环境下手动配置appmesh网格环境 需求&#xff1a; 两个…

【Spring 基础】

【Spring 基础】 一、 Spring 介绍 1. 简述 Spring 技术是 JavaEE 开发必备技能&#xff0c;企业开发技术选型专业角度 简化开发&#xff0c;降低企业级开发的复杂性 IoCAOP 事务处理 框架整合&#xff0c;高效整合其他技术&#xff0c;提高企业级应用开发与运行效率 MyBat…

Linux内核中的软中断、tasklet和工作队列

软中断、tasklet和工作队列并不是Linux内核中一直存在的机制&#xff0c;而是由更早版本的内核中的“下半部”&#xff08;bottom half&#xff09;演变而来。下半部的机制实际上包括五种&#xff0c;但2.6版本的内核中&#xff0c;下半部和任务队列的函数都消失了&#xff0c;…

5M240ZT144C5N【CPLD】5M240ZT144I5N,5M570ZT100I5N满足低功耗设计

MAX V设备系列的特点&#xff1a;低成本、低功耗、非易失性CPLD架构即时启动(0.5 ms或更短)配置时间待机电流低至25A&#xff0c;快速下电/复位操作快速传播延迟和时钟到输出时间内部振荡器模拟RSDS输出支持&#xff0c;数据速率高达200 Mbps模拟LVDS输出支持&#xff0c;数据速…

手把手教你做微信公众号

手把手教你做微信公众号 微信公众号可以通过注册的方式来建立。 1.进入微信公众平台 首先&#xff0c;在浏览器中搜索微信公众号&#xff0c;网页第一个就是&#xff0c;如下图所示&#xff0c;我们点进去。 2.注册微信平台账号 进入官网之后&#xff0c;如下图所示&#…

day53【代码随想录】单调栈之每日温度、下一个更大元素 I、下一个更大元素 II

文章目录前言一、每日温度&#xff08;力扣739&#xff09;二、下一个更大元素 I&#xff08;力扣496&#xff09;三、下一个更大元素 II&#xff08;力扣503&#xff09;【环形数组】思路一思路二前言 单调栈&#xff1a;栈内元素保证递增或递减的 1、每日温度 2、下一个更大…

“AI板块凉了”说法有失公允?AI板块CNTM其发展的关键!

今年区块链所有的建设都围绕着以太坊&#xff0c;存储板块开年也是火爆了一把&#xff0c;龙头FIL更是一路前行&#xff0c;短期虽有回落但热度依然在&#xff0c;后期市场热度还是会给到存储&#xff0c;未来可期。目前市场上新出一个区块链覆盖多个赛道的项目——Filswan和AI…

Hive的视图与索引

Hive的视图其实是一个虚表&#xff0c;视图可以允许保存一个查询&#xff0c;并像对待表一样对这个查询进行操作&#xff0c;视图是一个逻辑结构&#xff0c;并不会存储数据。 Hive中的索引只有有限的功能&#xff0c;Hive中没有主键和外键的概念&#xff0c;可以通过对一些字段…

【CS224W】(task6)Google的PageRank算法

note 求解pagerank&#xff1a;用power iteration&#xff08;幂迭代&#xff09;方法求解 rM⋅r\mathbf{r}\mathbf{M} \cdot \mathbf{r}rM⋅r ( MMM 是重要度矩阵)用random uniform teleporation解决dead-ends&#xff08;自己指向自己&#xff09;和spider-traps&#xff08…

Linear()全连接层+矩阵原理

Linear()全连接层矩阵原理) Linear()全连接层矩阵原理 Linear&#xff08;&#xff09;参数 原文地址&#xff1a;https://blog.csdn.net/horizonwys/article/details/125933921 。 矩阵原理 在 NLP中 x 一般为一行 故 *linear()中输出为 x W x的维度为 &#xff08;tok…

二叉树—— 二叉搜索树中的搜索

二叉搜索树中的搜索 链接 给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和一个整数值 val。 你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在&#xff0c;则返回 null 。 示例 1: 输入&#xff1a;root [4,2,7,1,3], val…

jsp图书借阅管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 图书借阅管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统采用serlvetdaobean&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.…

[oeasy]python0093_电子游戏起源_视频游戏_达特茅斯_Basic_家酿俱乐部

编码进化 回忆上次内容 Ed Robert 的 创业之路 从 售卖 diy 组装配件到进军 计算器市场最后 发布 牛郎星8800 intel 8080 的出现 让 人人都有 自己的 个人电脑 Bill Gate 和 Paul Allen 要去 新墨西哥州 朝圣这场 奥德赛 会发生什么呢&#xff1f;&#x1f914; 奥德赛 当…

【并发编程学习篇】ReentrantLock设计思想剖析

一、AQS原理剖析 什么是AQS java.util.concurrent包中的大多数同步器实现都是围绕着共同的基础行为&#xff0c;比如 等待队列、条件队列、独占获取、共享获取等而这些行为的抽象就是基于AbstractQueuedSynchronizer&#xff08;简称AQS&#xff09;实现的&#xff0c;AQS是一…

【python】函数详解

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录基本函数-function模块的引用模块搜索路径不定长参数参数传递传递元组传递字典缺陷&#xff0c;容易改了原始数据&#xff0c;可以用copy()方法避免变量作用域全局变量闭包closurenonlocal 用了这个声明闭包…

C语言基础相关内容

文章目录前言1. 关键字2. C语言数据类型3. 标志符4. 常量类型5. 内存模型变量内存分析数组6. printf & scanfpuchar&getchar7 main函数8 字面值常量前言 本文简明扼要的介绍了部分C语言的一些基本内容。 1. 关键字 12345678charshortintlongfloatdoubleifelsereturnd…

【人脸识别】CurricularFace:自适应课程学习人脸识别损失函数

论文题目&#xff1a;《CurricularFace: Adaptive Curriculum Learning Loss for Deep Face Recognition》 论文地址&#xff1a;https://arxiv.org/pdf/2004.00288v1.pdf 代码地址&#xff1a;https://github.com/HuangYG123/CurricularFace 建议先了解下这篇文章&#xff1a…

电子技术——频率补偿

电子技术——频率补偿 在本节我们介绍修改三极点或多极点放大器的开环增益函数 A(s)A(s)A(s) 的方法&#xff0c;使得闭环增益在我们希望的值上放大器是稳定的。这个过程称为频率补偿。 理论 最简单的频率补偿方法是引入新的极点&#xff0c;如图下面是一个放大器的伯德图&am…