进程管理之基本概念
目录
关于进程的基本概念
进程描述符
查看进程
进程标识
进程的生命周期
僵尸进程、孤儿进程
写时拷贝技术
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){ //childint 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){ //fatherwhile(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数据结构。
相关文章:

进程管理之基本概念
目录 关于进程的基本概念 进程描述符 查看进程 进程标识 进程的生命周期 僵尸进程、孤儿进程 写时拷贝技术 fork()函数 vfork()函数 终止进程 进程优先级和权重 进程地址空间 关于进程的基本概念 进程和程序是操作系统领域的两个重要的概念,进程是执行…...

nginx安装部署实战手册
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录一、虚拟机安装nginx1.下载安装包2.安装编译工具和库文件3.编译安装4.启动nginx5.访问首页6.开机自启结尾一、虚拟机安装nginx 1.下载安装包 官网下载地址…...
XXL-JOB任务调度平台
什么是xxl-job? xxl-job是一个分布式的任务调度平台,其核心设计目标是:学习简单、开发迅速、轻量级、易扩展,现在已经开放源代码并接入多家公司的线上产品线,开箱即用。xxl是xxl-job的开发者大众点评的许雪里名称的拼…...

android UI优化的基本原理和实战方法
任何Android应用都需要UI跟用户交互.UI是否好坏更是直接影响到用户的体验.如今UI的优化视乎是应用开发中一个绕不过去的话题。所以本篇文章小编带大家全面了解Android ui优化的主要知识和优化方法。 一、UI优化 UI优化知识点主要分为三部分: 第一部分,…...

指针的进阶【中篇】
文章目录📀4.数组参数💿4.1.一维数组传参💿4.2.二维数组传参📀5.指针参数💿5.1.一级指针传参💿5.2.二级指针传参📀6.函数指针💿6.1. 代码1💿6.2. 代码2📀7.函…...

华为OD机试题,用 Java 解【删除字符串中出现次数最少的字符】问题
最近更新的博客 华为OD机试 - 猴子爬山 | 机试题算法思路 【2023】华为OD机试 - 分糖果(Java) | 机试题算法思路 【2023】华为OD机试 - 非严格递增连续数字序列 | 机试题算法思路 【2023】华为OD机试 - 消消乐游戏(Java) | 机试题算法思路 【2023】华为OD机试 - 组成最大数…...

【C语言每日一题】猜名次
【C语言每日一题】—— 猜名次😎😎😎 💡前言🌞: 💛猜名次题目💛 💪 解题思路的分享💪 😊题目源码的分享😊 👉 本菜鸡…...

89. 格雷编码
89. 格雷编码题目数学公式动态规划回溯题目 传送门:https://leetcode.cn/problems/gray-code/ 数学公式 int gray(int n) { // 计算第n位格雷码公式return n ^ (n >> 1); }然后你写一个for循环,计算从1到n的所有格雷码,添加到答…...

线性回归算法和逻辑斯谛回归算法详细介绍及其原理详解
相关文章 K近邻算法和KD树详细介绍及其原理详解朴素贝叶斯算法和拉普拉斯平滑详细介绍及其原理详解决策树算法和CART决策树算法详细介绍及其原理详解线性回归算法和逻辑斯谛回归算法详细介绍及其原理详解 文章目录相关文章前言一、线性回归二、逻辑斯谛回归总结前言 今天给大家…...

【网络原理8】HTTP请求篇
在上一篇文章当中,我们也提到了什么是HTTP。 每一个HTTP请求,都会对应一个HTTP响应。 下面这一篇文章,将聊一下HTTP请求的一些内容 目录 一、URL 第一部分:协议名称 第二部分:认证信息(新的版本已经没有了) 第三部分…...

Playbook的用法
目录 Playbook Playbook 与 Ad-Hoc 对比 YAML 语言特性 YAML语法简介 支持的数据类型 写法格式 1 scalar 标量 建议缩进两个空格,可多 2 Dictionary 字典 3 List 列表 三种常见的数据格式 Playbook 核心组件 不要用 tab 可以#注释 hosts remote_us…...

APP优化 —— MMAP内存映射
mmap 一种内存映射文件的方法 mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。mmap在用户空间映射调用系统中作用很大。 头文件 <sys/mman.h> 函数原型 v…...
paddle.vision 与 torchvision 中的box NMS使用方式
torchvision 中有多个用于计算 BBox NMS 的 API, 在本篇氵文中, 使用 torchvision.ops.boxes.batched_nmspaddle.vision 中通过 paddle.vision.ops.nms 来进行多个 Box 的 NMS 操作 1. torchvision 中 batched_nms 操作 torchvision batched_nms def batched_nms(boxes: to…...

php mysql校园帮忙领取快递平台
1、后台管理员用户名hsg 密码hsg 2、开发语言:PHP,数据库为MySql 3、数据库连接字符串在conn.php中修改 4、运行环境wamp5.1.7或者appserv2.5.9 5.程序编码gbk.不支持php5.3以上版本 6.本人发布的程序一律享有免费运行一次…...

C/C++开发,无可避免的内存管理(篇二)-约束好跳脱的内存
一、养成内存管理好习惯 1.1 养成动态对象创建、调用及释放好习惯 开发者手动接管内存分配时,必须处理这两个任务。分配原始内存时,必须在该内存中构造对象;在释放该内存之前,必须保证适当地撤销这些对象。如果你的项目是c项目&am…...

【Java】让我们对多态有深入的了解(九)
目录 (1)接口的基本介绍编辑 (2)接口的注意事项和细节 1.接口不能被实例化 2.接口中所有方法是public方法,接口中的抽象方法,可以不用abstract修饰 3.一个普通类实现接口,必须将接口所有…...

12 个适合做外包项目的开源后台管理系统
1.D2admin 开源地址:https://github.com/d2-projects/d2-admin 文档地址:https://d2.pub/zh/doc/d2-admin/ 效果预览:https://d2.pub/d2-admin/preview/#/index 开源协议:MIT 2.vue-element-admin 开源地址:https…...

鼠标更换指针图案和更改typora的主题
鼠标更换指针图案 由此偶然看见好几个朋友都使用了新的图案替换掉了原有的鼠标图案,今天寻思自己也换一个图案 主要是觉得鼠标大一点儿会好看一些,所以就找了一些教程 官方教程,小的变动 当然最多的是官方教程,如果你只是想要…...

【洛谷 P1563】[NOIP2016 提高组] 玩具谜题(模拟+结构体数组+指针)
[NOIP2016 提高组] 玩具谜题 题目背景 NOIP2016 提高组 D1T1 题目描述 小南有一套可爱的玩具小人, 它们各有不同的职业。 有一天, 这些玩具小人把小南的眼镜藏了起来。 小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的面朝圈外。如下图: 这时 singer 告诉小南一个谜…...

阿里测试经验7年,从功能测试到自动化测试,我整理的超全学习指南
做测试七年多,有不少人问过我下面问题: 现在的手工测试真的不行了吗? 测试工程师,三年多快四年的经验,入门自动化测试需要多久? 自学自动化测试到底需要学哪些东西? 不得不说,随着…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...

Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...

华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...