操作系统核心知识点整理--进程篇

news2024/9/30 9:33:24

操作系统核心知识点整理--进程篇

  • 什么是系统调用
  • 进程篇
    • 什么是进程
    • 什么是线程
    • 从一次fork调用看linux进程和线程的本质区别
      • 小结
    • 用户级线程和内核级线程的区别
    • 进程的状态
    • 进程的切换
    • 进程调度
    • 并发问题
      • 死锁
  • 参考


本文主要面向应用层软件开发人员整理一篇必须了解的操作系统核心知识图谱,每小节参考文章链接都已经在小节末尾给出,如果大家有疑问,可以评论区留言,或者直接去阅读原文。


什么是系统调用

操作系统对内存的使用是按段的,例如: 我们编写的一个程序被操作系统加载到内存是按照数据段,代码段等形式分段载入。而操作系统自身的代码也是按段载入的,为了确保安全性,我们用户编写的程序是不能直接访问操作系统的相关段的,因此需要给不同段赋予不同的特权级。

特权级高段可以访问特权级低的段,反之则不能。因此,操作系统的相关段具有更高的特权级,用户程序的相关段具有更低的优先级,操作系统相关段也被称为内核态,用户程序相关段被称为用户态。用户态无法直接访问内核态,内核态可以访问用户态。

linux 0.11中每个进程都关联一个LDT表,该表中记录了当前进程执行的程序对应的各个段信息,如: 段的起始地址,段限长,段的一个特权级等。

linux 0.11中当前访问者的特权级由CS寄存器低两位表示,cs寄存器保存当前执行进程代码段基址,而ip保存当前执行指令在代码段中的偏移地址

用户态无法直接访问内核态,但又需要去操作设备,进行文件管理等需要和硬件打交道的活。因此,操作系统必须开放出来一批调用接口,让用户程序可以调用接口,完成对底层硬件资源的使用,这些接口被称为系统调用。

系统调用通过中断实现,会提升当前访问者的特权级,在中断返回时,再将特权级恢复。

操作系统接口和调用–02


进程篇

什么是进程

操作系统为正在运行的程序提供的抽象,就是所谓的进程。当然,进程本身还记录了当前程序运行的一些状态,如: 进程可以访问的内存地址空间范围, 使用到的相关寄存器,如: 程序计数器,栈指针和帧指针等。

一个CPU核同时可以执行一个进程,一个CPU核通常搭配一套寄存器使用,也就是说当发生进程切换时,需要将相关寄存器状态保存到要切换进程的PCB中,然后再将新进程的PCB记录的寄存器状态拍到寄存器上,从而完成进程的切换。

Linux 中使用task_struct 结构体作为PCB的实现:
在这里插入图片描述
Linux中所有进程都是通过一颗进程树来管理的,操作系统启动时会创建一个init进程,接下来所有进程都由该进程之间或者间接创建:
在这里插入图片描述
Linux中通过一个mm_struct结构体记录当前进程虚拟地址空间的分配和使用情况,包括程序各种分段信息:
在这里插入图片描述
在内核内存区域,可以通过直接计算得出物理内存地址,并不需要复杂的页表计算。而且最重要的是所有内核进程、以及用户进程的内核态,这部分内存都是共享的。

files_struct 结构体记录当前进程打开的文件有哪些:
在这里插入图片描述
namespaces结构体是用来隔离内核资源的方式,通过namespaces可以让一些进程只能看到与自己相关的一部分资源:
在这里插入图片描述

docker底层容器间资源隔离的核心实现思路就是使用namespace完成多个进程间对内核资源的隔离,在创建进程或线程的时候,可以让内核帮我们创建独立的命名空间。在默认情况下,创建进程没有指定命名空间相关的标记,因此也不会创建。新旧进程仍然复用同一套命名空间对象。

Linux中使用fork创建进程的时候,地址空间mm_struct,打开文件列表files_struct都是需要独立拥有的,这样才能完成进程间的资源隔离,但是对于命名空间而言,如果不特殊指定,子进程会复用父进程的命名空间:
在这里插入图片描述

Linux进程是如何创建出来的?

CPU篇—理清“核“概念-01


什么是线程

进程记录了当前程序的运行状态,并管理着当前程序运行所需要的各种资源,如果频繁对进程进行切换,显然代价还是比较大的。

其实我们可以将进程看做是资源+指令序列,如果我们把资源和指令序列分开的话,我们可以让一个进程内存在多套指令序列,但是资源还是只有一份,相当于多个指令序列执行过程中共享当前进程的内存资源。

那么这些正在运行的指令序列,其实就是我们说的线程,一个进程内可以存在多个线程,多个线程在执行过程中不断切换执行,并且切换只需要保存和PC相关寄存器状态,不需要切换页表等重量级资源,因此效率更高。

线程本质是指令之间的切换,一个进程中有代码片段,而多个指令序列会存在在这个代码片段中,每个指令序列一旦运行起来了,就是一个线程,当存在多个线程时,对于线程的切换,也只需要切换指令序列即可,不需要涉及到映射表和内存段的改变。

在Linux中,线程的表示依然使用task_struct表示:
在这里插入图片描述
无论是进程还是线程,都需要有一个唯一标识符号,这个符号就是pid,也就是我们常说的进程ID,线程ID。

如果一个进程下创建了多个线程,那么每个线程的pid都是不同的,但是我们一般又需要记录线程属于哪个进程,这时候,tgid就派上用场了,通过tgid字段来表示自己所归属的进程ID。
在这里插入图片描述


从一次fork调用看linux进程和线程的本质区别

Nginx服务采用多进程方式进行工作,它启动的时候会创建若干个worker进程,来响应和处理用户请求。

Redis 6.0以上版本,也开始支持使用多线程来提供核心服务,redis服务启动后,会调用initThreadIo来创建多个IO线程。

在这里插入图片描述
左边是nginx创建进程的核心调用链,右边是redis通过glibc函数库提供的pthread_create函数创建线程的核心调用过程。

选择创建进程还是线程,核心在于do_fork函数,我们来看看do_fork函数具体干了啥:

//file:kernel/fork.c
long do_fork(unsigned long clone_flags,
    unsigned long stack_start,
    unsigned long stack_size,
    int __user *parent_tidptr,
    int __user *child_tidptr)
{
 //复制一个 task_struct 出来 ————> 复制父进程的task_struct,具体复制哪些部分,由clone_flags决定
 struct task_struct *p;
 p = copy_process(clone_flags, stack_start, stack_size,
    child_tidptr, NULL, trace);

 //子任务加入到就绪队列中去,等待调度器调度
 wake_up_new_task(p);
 ...
}

do_fork做的事情: copy一份父进程的task_struct结构体数据给子类,具体copy过程由clone_flags决定。 子进程task_struct结构体准备好了以后,将子任务放入就绪队列,等待被调度。

//file:kernel/fork.c
static struct task_struct *copy_process(...)
{
 //3.1 复制进程 task_struct 结构体
 struct task_struct *p;
 p = dup_task_struct(current);
 ...

 //3.2 拷贝 files_struct
 retval = copy_files(clone_flags, p);

 //3.3 拷贝 fs_struct
 retval = copy_fs(clone_flags, p);

 //3.4 拷贝 mm_struct
 retval = copy_mm(clone_flags, p);

 //3.5 拷贝进程的命名空间 nsproxy
 retval = copy_namespaces(clone_flags, p);

 //3.6 申请 pid && 设置进程号
 pid = alloc_pid(p->nsproxy->pid_ns);
 p->pid = pid_nr(pid);
 p->tgid = p->pid;
 if (clone_flags & CLONE_THREAD)
  p->tgid = current->tgid;

 ......
}

copy_process先是完全复制了一份父进程的task_struct(浅拷贝),复制完进行子进程基本信息覆盖后,父子进程状态如下:
在这里插入图片描述
下面开始通过clone_flags标志判断哪一部分子进程需要和父进程共享,即子进程无需对父进程指定资源进行深拷贝,这边我简单列举copy_xxx函数过程中的几个例子:

  • copy_files: 子进程是否需要对父进程的打开文件列表进行深拷贝
static int copy_files(unsigned long clone_flags, struct task_struct *tsk)
{
 struct files_struct *oldf, *newf;
 oldf = current->files;
 //如果是clone_flag标记中CLONE_FILES位被设置为了true,那么引用计数加一,然后返回--子进程共享父进程文件打开列表
 if (clone_flags & CLONE_FILES) {
  atomic_inc(&oldf->count);
  goto out;
 }
 //否则子进程单独申请一块内存,用于files_struct对象存储---对父进程的file_strcut进行深拷贝
 newf = dup_fd(oldf, &error);
 tsk->files = newf;
 ...
}

如果此时创建的是进程,例如: nginx,那么do_fork函数中传入的clone_flags标志位的CLONE_FILES就为0,即子进程对父进程的打开文件列表采用的是深拷贝方式:
在这里插入图片描述
对于redis来说创建的线程来说,会将clone_flags中的CLONE_FILES标记位设置为1,即子进程共享父进程的打开文件列表资源:
在这里插入图片描述

  • copy_mm: 子进程是否需要对父进程的地址空间进行深拷贝
static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
{
 struct mm_struct *mm, *oldmm;
 oldmm = current->mm;
 //判断clone_flags中的CLONE_VM标记位是否被设置为1,如果为1,则子进程共享父进程地址空间 
 if (clone_flags & CLONE_VM) {
  atomic_inc(&oldmm->mm_users);
  mm = oldmm;
  goto good_mm;
 }
 //否则子进程对父进程地址空间进行深拷贝
 mm = dup_mm(tsk);
  good_mm:
 return 0; 
}

nginx中创建进程时,不会将CLONE_VM标记设置为1,因此进行的是深拷贝:
在这里插入图片描述

地址空间是进程线程最核心的东西,每个进程都有独立的地址空间

redis中创建线程时,会将CLONE_VM标记设置为1,因此子进程共享父进程的地址空间:
在这里插入图片描述
在创建进程或线程的时候,还可以让内核帮我们创建独立的命名空间。在默认情况下,创建进程没有指定命名空间相关的标记,因此也不会创建。新旧进程仍然复用同一套命名空间对象。
在这里插入图片描述


小结

在Linux中,进程和线程都是用task_struct来表示的,只不过线程和进程的区别在于: 是否和创建它的父进程共享打开文件列表,目录信息,虚拟地址空间等数据结构。

在上述共享信息中。内存虚拟地址空间是最重要的,因此,区分一个任务是叫线程还是进程,一般习惯上就看它是否有独立的地址空间,如果有,就叫做进程,没有,就叫做线程。

对于内核任务来说,无论有多少个任务,其使用的地址空间都是同一个,所以一般叫做内核线程,而不是内核进程。

对于内核线程来讲,不需要虚拟地址空间,所以 mm 成员的值为 null。

  • 对于Linux中的线程而言,其地址空间mm_struct,目录信息fs_struct,打开文件列表files_struct都是和创建它的任务共享的:

在这里插入图片描述

  • 对于进程而言,地址空间mm_struct,挂载点fs_struct,打开文件列表files_struct都是要独立拥有的,都需要申请内存并初始化它们:
    在这里插入图片描述

总之,在Linux内核中并没有对线程做特殊处理,还是由task_struct进行管理,从内核角度看,用户天的线程本质还是一个进程,只不过和普通进程比,稍微轻量了那么一点。

Linux进程是如何创建出来的?

聊聊Linux中线程和进程的联系与区别!


用户级线程和内核级线程的区别

操作系统内存整体可以划分为用户区和内核区两部分,如果用户区的函数需要调用内核区相关函数,需要通过系统调用切换到内核区执行。

由于用户区和内核区是分开的,因此对应的函数栈也是不同的,因此如果我们需要从用户态切换到内核态执行,需要准备两套栈,一套用户栈,一套内核栈:
在这里插入图片描述
用户态切换到内核态执行的过程大致如下:

  • 当用户态程序进行系统调用时,发生中断,此时需要定位到当前用户栈关联的内核栈地址,然后将用户栈执行状态,压入内核栈保存。
  • 当内核函数执行完毕后,中断返回时,弹出内核栈保存的用户栈状态,恢复用户态先前执行状态。

关键点: 操作系统只能看见内核栈,我们可以将一套内核栈看做是一个内核线程,而一套用户栈,可以看做是一个用户线程

用户线程内核线程的关系可以是1:1或者n:1或者n:n。

在这里插入图片描述

  • 如果是n:1的情况,我们称此时为用户级线程实现
  • 如果是1:1的情况,我们称此时为核心级线程实现
  • 如果是n:n的情况,我们称此时为混合实现

用户级线程:

  • 用户级线程的切换只在用户态完成,并且线程调度算法由用户自己编写,因此即使一个进程中存在多个用户级线程,其实也可以看做是只存在一个核心级线程。
  • 可以在不支持线程的操作系统中实现。
  • 当用户级线程发生IO或页面故障引起的阻塞时,由于操作系统无法感知用户级线程存在,所以会直接进行进程切换,而不是切换到进程中另一个线程继续执行。
  • 一个单独的进程内部没有时钟中断,所以无法采用轮转调度的方式来调度用户级线程,所以对于用户级线程的调度只能采用分时复用机制。

核心级线程:

  • 核心级线程的切换在内核态完成,并且线程调度算法由操作系统完成,因此一个进程如果存在多个内核级线程,那么每个内核级线程都可以利用一个CPU,这样就可以完成多线程执行。但是由于内核级线程线程切换需要进入内核态完成,因此切换代价大。
  • 进程中某一线程的阻塞不会影响当前进程中其他线程的调度执行。

用户级线程和内核级线程—04

linux:线程的3种实现方式(内核级,用户级和混合型)


进程的状态

  • 创建状态(new) :进程正在被创建,尚未到就绪状态。
  • 就绪状态(ready) :进程已处于准备运行状态,即进程获得了除了处理器之外的一切所需资源,一旦得到处理器资源(处理器分配的时间片)即可运行。
  • 运行状态(running) :进程正在处理器上上运行(单核 CPU 下任意时刻只有一个进程处于运行状态)。
  • 阻塞状态(waiting) :又称为等待状态,进程正在等待某一事件而暂停运行如等待某资源为可用或等待 IO 操作完成。即使处理器空闲,该进程也不能运行。
  • 结束状态(terminated) :进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行。

进程的切换

我们知道进程是对运行程序的抽象表示,主要负责记录当前程序运行状态,管理当前程序运行需要的资源,进程在Linux中使用PCB表示,线程在Linux中使用TCB表示。

那我们考虑一下进程的切换需要做哪些事情:

  • 切换PCB
  • 保存当前CPU各种寄存器的状态,寄存器状态就当前当前程序运行的状态,核心的寄存器有: 程序计数器,栈顶指针等

Linux 0.11是只支持单核,进程实现的操作系统,因此Linux 0.11中的进程切换采用的是TSS方式完成的:
在这里插入图片描述

Linux 0.11只支持进程实现,所以Linux 0.11将进程直接叫做线程也是可以的。

使用TSS完成内核线程的切换的过程大致为: 通过一条长跳转指令,将当前CPU状态,拍到老进程的TSS上,而将新进程TSS拍到当前CPU上,TSS中保存了当前CPU各种寄存器状态值,这个切换过程代价还是比较大的。

Linux 0.11不支持内核栈的实现内核线程切换的方式,所以采用的是TSS,如果采用内核栈方式实现内核线程的切换,那么只需要切换TCB,因为在进入中断的时候,已经将各种寄存器状态压入内核栈保存了,相当于与内核栈保存CPU当前状态,从而替换了TSS。

在这里插入图片描述

esp栈顶指针很关键,由于一个CPU中只存在一个esp栈顶寄存器,那么内核线程切换的本质,其实是将esp指向另一个内核栈栈顶罢了,中断返回时,弹出先前内核栈中压入的用户栈状态,就可以恢复之前程序运行的样子,完美!
在这里插入图片描述
在这里插入图片描述

内核级线程源码实现—05


进程调度

进程调度需要关注的点:

  • 响应时间和吞吐量
  • 前后台任务异同:前台任务希望更短的响应时间,后台任务希望更短的周转时间
  • IO约束型任务和CPU约束型任务: 我们应该让IO约束型任务优先执行,充分利用IO约束型任务IO阻塞时间,让CPU约束型任务继续执行。

常见的进程调度算法:

  • 先到先服务(FCFS): 不考虑短作业的感受,平均周转时间长
  • 短作业优先(SJF): 平均周转时间短,但是不考虑长作业感受,容易产生饥饿
  • 最短完成时间优先(STCF): 在SJF基础上,增加了抢占,当新任务进行了系统,会查看新任务剩余工作时间是否为最短的,如果是就抢占CPU让自己先执行。缺点还是容易产生饥饿,不考虑长作业感受。
  • 时间片轮转调度(RR):交替运行所有工作,从而优化响应时间。

时间片过小,会导致频繁的上下文切换,上下文切换的成本不仅包括相关寄存器状态的保存和恢复,还包括程序运行时,他们在CPU高速缓存,TLB,分支预测期和其他硬件上建立的大量状态,一旦进行了线程切换,这些状态都会被刷新,这会导致显著的性能成本。


上面列举的进程调度算法并没有考虑到IO,并且由于作业的执行时长通常是无法确定的,所以类似于SJF这种算法就难以实现,现代操作通常既需要考虑响应时间,如: 前台交互式任务。又需要考虑周转时间,如: 后台任务。 如何设计一种调度算法能够同时兼顾这两者,是一个技术活!

  • 多级反馈队列(MLQF): 设置多个独立的队列,每个队列有不同的优先级,一个工作只能存在于一个队列中。调度程序总是会选择执行较高优先级队列中的任务。对于同一个队列中的多个任务,调度程序采取轮转调度。

MLQF主要由如下几个规则构成:

  1. 如果任务A的优先级大于任务B,运行A
  2. 如果A的优先级等于任务B,轮转运行A和B
  3. 任务进入系统时,放在最高优先级队列中
  4. 任务用完整个时间片后,降低其优先级,移动到下一层优先级更低的队列
  5. 任务在时间片内主动放弃CPU,则保持优先级不变 — > 照顾了交互性任务的响应时间
  6. 经过一段时间S,就将系统中所有工作重新加入最高优先级队列 —> 防止饥饿
  7. 为了防止恶意任务总是在时间片以内释放CPU,从而一直持有CPU,我们又提出了一个规则: 一旦工作用完了其在某一层的时间配额,无论中间主动放弃了多少次CPU,还是会降低其优先级(移动到低一级队列)

注意: 高优先级队列通常分配较短的时间片,因此这一层交互工作可以更快地切换,相反,低优先级队列中更多的是CPU密集型工作,配置更长的时间片会取得更好的效果。

在这里插入图片描述


这里额外再多说一点关于多处理器调度的注意点:

多处理器调度和单处理器调度区别的核心在于硬件缓存的使用,以及多处理器之间共享数据的方式。

在单cpu系统中,存在多级硬件缓存,缓存是很小但很快的存储设备,通常拥有内存中最热的数据的备份,相比之下,内存很大且拥有所有的数据,但访问速度较慢,通过将频繁访问的数据放在缓存中,系统似乎拥有又大又快的内存。

缓存的高效是基于程序运行的时间局部性和空间局部性。时间局部性: 一个数据被访问后,它很可能在不久又被访问到。空间局部性: 当程序访问地址为X的数时,很可能会紧接着访问x周围的数据。

在这里插入图片描述
对于多处理器而言,通常会存在缓存一致性问题,即各个CPU修改了高速缓存数据后,不直接同步回主存,各个CPU查询数据时,只查询自己的高速缓存,而不检查数据是否过期。

解决缓存一致性通常采用总线嗅探技术,每个缓存都通过监听所有缓存和内存的总线,来发现内存的访问,如果CPU发现对它放在缓存中的数据的更新,会作废本地副本,从主存同步最新结果。

设计多处理器调度时,我们需要考虑: 缓存亲和度。因为一个进程在某个CPU上运行时,会在该CPU的缓存中维护许多状态,下次该进程在相同的CPU上运行时,由于缓存中的数据而执行的很快。相反,在不同的CPU上执行,会由于重新加载数据到缓存而变慢。因此多处理器调度需要考虑缓存亲和性,尽可能让进程保持在同一个CPU上运行。


并发问题

对临界区资源进行保护通常有几种解决方法:

  • 信号量: 可以简单理解为剩余资源数量,>0时表示还剩多少资源,=0时表示无剩余资源,<0时表示有几个线程正在等待当前资源
  • 自旋锁: 当一个线程无法获取临界区资源时,不是挂起等待,而是保持运行,反复轮询资源是否空闲,以此减少挂起等待导致的线程上下文开销。
  • 关中断(适合单CPU场景)

这块介绍的比较简单

常见的并发问题通常分为两类: 非死锁问题,死锁问题。

非死锁问题通常由两种原因导致:

  • 违反原子性缺陷: 代码本意是原子的,但是执行过程中并没有强制实现原子性
  • 违反顺序缺陷: 内存的访问顺序与预期不符
    在这里插入图片描述

死锁

死锁产生的四个条件:

  • 互斥: 资源必须处于非共享模式
  • 持有并等待: 线程持有了某个资源,同时又在等待其他资源
  • 非抢占: 线程获得了资源,不能被抢占,例如: 锁
  • 循环等待: 线程之间的资源请求存在环路

死锁的解决办法:

  • 预防: 可以考虑让进程在执行前就必须获取到它所需要的所有资源,否则就不能执行;
  • 避免: 采用银行家算法,进程申请资源时,通过银行家算法先试探分配给进程资源,然后通过安全性算法判断分配后系统是否处于安全状态,如果不安全则试探分配作废,让进程继续等待,如果能够进入安全状态,则分配资源给进程使用。
  • 检测: 通过进程资源分配图,检测是否产生了死锁
  • 解除: 当检测到死锁后,可以采用重启大法,或者回滚死锁涉及到的进程,或者逐个回滚,直到死锁解除,或者从涉及死锁的一个或几个进程中抢占资源,把夺得的资源再分配给涉及死锁的进程直到死锁解除。

参考

操作系统导论

哈工大李老师操作系统课程

张彦飞大佬公众号

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

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

相关文章

maya多边形顶点变形批量传递方法

一、问题描述 做项目时&#xff0c;对于重复更改相同模型的顶点位置需要大量重复操作&#xff0c;maya默认提供了多边形属性传递的方法&#xff0c;如下图&#xff1a; 但一次只能执行一次&#xff0c;并且带有大量历史节点&#xff0c;此方式的好处是&#xff0c;可以实现实…

《零成本实现Web自动化测试--基于Selenium》 Selenium-RC

一. 简介 Selenium-RC可以适应更复杂的自动化测试需求&#xff0c;而不仅仅是简单的浏览器操作和线性执行。Selenium-RC能够充分利用编程语言来构建更复杂的自动化测试案例&#xff0c;例如读写文件、查询数据库和E-mail邮寄测试报告。 当测试案例遇到selenium-IDE不支持的逻辑…

python的所有知识点+代码+注释,不看就亏死了

目录 简介 特点 搭建开发环境 版本 hello world 注释 文件类型 变量 常量 数据类型 运算符和表达式 控制语句 数组相关 函数相关 字符串相关 文件处理 对象和类&#xff0c;注&#xff1a;不是那个对象&#xff01;&#xff01;&#xff01;&#xff01;&…

HTML创意动画代码

目录1、动态气泡背景2、创意文字3、旋转立方体1、动态气泡背景 <!DOCTYPE html> <html> <head><title>Bubble Background</title><style>body {margin: 0;padding: 0;height: 100vh;background: #222;display: flex;flex-direction: colum…

SpringCloud————Eureka概述及单机注册中心搭建

Spring Cloud Eureka是Netflix开发的注册发现组件&#xff0c;本身是一个基于REST的服务。提供注册与发现&#xff0c;同时还提供了负载均衡、故障转移等能力。 Eureka组件的三个角色 服务中心服务提供者服务消费者 Eureka Server&#xff1a;服务器端。提供服务的注册和发现…

kubernetes 1.26.1 Etcd部署(外接)保姆级教程

目录 部署etcd前期准备 机器信息 升级内核 系统配置 部署容器运行时Containerd 安装crictl客户端命令 配置服务器支持开启ipvs的前提条件 安装 kubeadm、kubelet 和 kubectl 安装部署etcd 1.将 kubelet 配置为 etcd 的服务管理器 2.为 kubeadm 创建配置文件 3. 生成…

2023年网络安全某市赛竞赛任务书

竞赛任务书 一、竞赛时间 共计3小时。 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第一阶段单兵模式系统渗透测试 任务一 数据库服务渗透测试 100分钟 150 任务二 Windows操作系统渗透测试 200 任务三 Linux操作系统渗透测试 150 任务四 Web安…

【人工智能 AI】什么是人工智能? What is Artificial Intelligence

目录 Introduction to Artificial Intelligence人工智能概论 What is Artificial Intelligence? 什么是人工智能?

ProtoBuf介绍

1 编码和解码编写网络应用程序时&#xff0c;因为数据在网络传输的都是二进制字节码数据&#xff0c;在发送数据时进行编码&#xff0c;在接受数据时进行解码codec&#xff08;编码器&#xff09;的组成部分有2个&#xff1a;decoder&#xff08;解码器&#xff09;和encoder&a…

回归预测 | MATLAB实现BO-CNN-BiLSTM贝叶斯优化卷积双向长短期记忆网络数据回归预测

回归预测 | MATLAB实现BO-CNN-BiLSTM贝叶斯优化卷积双向长短期记忆网络数据回归预测 目录回归预测 | MATLAB实现BO-CNN-BiLSTM贝叶斯优化卷积双向长短期记忆网络数据回归预测效果一览基本介绍模型搭建程序设计参考资料效果一览 基本介绍 基于贝叶斯优化卷积双向长短期记忆网络(…

自动化测试整理 --- STAF/STAX Robot Framework

题记:上周花了点时间学习开源的自动化测试框架Robot Framework,结合自己之前的自动化经验&#xff0c;就想周末写篇文章整理下。 目前&#xff0c;所在项目的自动化测试框架是基于STAF/STAX的拓展&#xff0c;围绕STAX执行引擎&#xff0c;扩展了测试用例的创建、管理&#xf…

验证功能覆盖率收集时per_instance=1可能导致覆盖率线性增长

验证覆盖率收集时&#xff0c;发现coverage database达到了惊人的256G&#xff0c;如下&#xff1a; 进入database中的testdata目录下的用例定位发现&#xff0c;问题出在这个文件&#xff1a; testbench.inst.xml其大小基本等同于验证用例覆盖率的大小。 这个文件时怎么产…

C++函数新思想和标准的输入和输出

欢迎来观看温柔了岁月.c的博客目前设有C学习专栏C语言项目专栏数据结构与算法专栏目前主要更新C学习专栏&#xff0c;C语言项目专栏不定时更新待C专栏完毕&#xff0c;会陆续更新C项目专栏和数据结构与算法专栏一周主要三更&#xff0c;星期三&#xff0c;星期五&#xff0c;星…

【决策树】一文看懂图解决策树原理:信息熵、条件熵与信息增益

本文用过图解的方式并结合实际案例的方式讲述了决策树的基本原理&#xff0c;主要包含信息熵、条件熵与信息增益的概念与计算方式&#xff0c;以及如何选择各个决策节点&#xff08;即&#xff1a;选择信息增益最大的特征&#xff09;。 想要PDF文档的小伙伴&#xff0c;通过关…

[SCOI2005]骑士精神(C++,启发式搜索)

题目描述 输入格式 第一行有一个正整数 TTT&#xff08;T≤10T \le 10T≤10)&#xff0c;表示一共有 TTT 组数据。 接下来有 TTT 个 555 \times 555 的矩阵&#xff0c;0 表示白色骑士&#xff0c;1 表示黑色骑士&#xff0c;* 表示空位。两组数据之间没有空行。 输出格式 …

【Node.js】详细记录express路由和中间件

Node.js路由的概念什么是路由Express中的路由路由的匹配过程路由的使用创建路由模块文件注册路由模块文件为路由模块添加前缀Express中间件Express中间件格式定义中间件函数定义全局生效的中间件函数中间件的作用定义多个全局中间件局部生效的中间件定义多个局部生效的中间件了…

Ae:使用代理

如果希望加快合成的预览或渲染速度&#xff0c;可考虑对素材使用代理 Proxy。虽然在 Ae 中&#xff0c;可以指定任何的静止图像或视频为代理&#xff0c;但一般情况下还是建议创建源素材的低分辨率版本来作为代理。对素材创建或指定代理后&#xff0c;可随意切换是否使用代理来…

物联网在医疗保健领域的5大创新应用

如今&#xff0c;物联网的发展越来越迅速&#xff0c;我们无法低估物联网在当今世界的重要性。大多数人每天都会使用到物联网设备。例如&#xff0c;当你使用智能手表来跟踪你的锻炼时&#xff0c;你就间接地使用了物联网的功能。由于物联网为世界带来了很多有效的帮助&#xf…

【涨薪技术】0到1学会性能测试 —— LR录制回放事务检查点

前言 上一次推文我们分享了性能测试分类和应用领域&#xff0c;今天带大家学习性能测试工作原理、事务、检查点&#xff01;后续文章都会系统分享干货&#xff0c;带大家从0到1学会性能测试&#xff0c;另外还有教程等同步资料&#xff0c;文末免费获取~ 01、LR工作原理 通常…

【原创】java+swing+mysql生肖星座查询系统设计与实现

今天我们来开发一个比较有趣的系统&#xff0c;根据生日查询生肖星座&#xff0c;输入生日&#xff0c;系统根据这个日期自动计算出生肖和星座信息反馈到界面。我们还是使用javaswingmysql去实现这样的一个系统。 功能分析&#xff1a; 生肖星座查询系统&#xff0c;顾名思义…