进程控制(详解)

news2024/11/15 8:26:01
进程控制

上篇文章介绍了进程的相关概念,形如进程的内核数据结构task_struct 、进程是如何被操作系统管理的、进程的查看、进程标识符、进程状态、进程优先级、已经环境变量和进程地址空间等知识点;

本篇文章接着上篇文章继续对进程的控制进行展开,主要包括进程的创建fork,进程的退出和终止、写时拷贝、进程等待(防止僵尸进程的产生使得内存泄漏),进程替换的相关知识!

文章目录

      • 进程创建
        • fork()的使用场景
        • fork()调用失败的原因
        • 子进程的数据和代码默认是与父进程共享的
        • 创建子进程的操作系统需要干什么
        • 父子进程是具有独立性的
        • 父子进程既然共享代码和数据如何实现独立性
      • 写时拷贝详解
      • 进程终止
        • 进程退出的场景有哪几种?
        • 进程退出的方式
        • exit()和_exit()的区别
        • 如何获取一个进程的退出码或者退出信号
        • 方法一:通过位运算来获取
        • 方法二:通过宏来获取
      • 进程等待
        • 进程等待的用处是什么?
        • 进程等待的方式
        • wait()函数
        • waitpid()函数
        • 阻塞等待
        • 非阻塞等待
      • 进程替换
        • 为什么要有程序替换呢?
        • 原理是什么?
      • 六种程序替换函数
        • execl()
        • execlp
        • execle
        • execv()
        • execvp
        • execvpe
        • execve
        • exec系列函数用法总结:

进程创建

进程的创建在上篇文章中也有介绍过,进程创建的方式有两种,一种是我们将一个程序跑起来它就会变成一个进程,还有一种就是通过fork()函数创建子进程,主要的创建方式就是通过fork函数,所以我们再来回顾一下fork()函数吧.

fork()函数是一个系统调用接口

//fork()
pid_t fork(void);
返回值: pid_t 类型(实际上是无符号整数) 如果创建子进程成功,返回新创建的子进程的pid(大于0的)给它的父进程,返回0给它自己,如果创建失败就返回-1

注意:fork()成功创建了子进程后就会有父子两个进程,那么也就是说会有两个执行流,fork()会返回两次,分别对父进程和创建出来的子进程进行返回,给父进程返回子进程的pid,因为父进程和子进程的关系是一对多的,所以父进程需要去唯一标识子进程,而进程的pid是天然的标识一个进程的标志,所以fork()返回给父进程的是新创建出来的子进程的pid!而子进程他自己是被创建的时候就知道了自己的pid和其父进程的pid的,所以它不需要fork()函数对他返回任何值,所以fork()就默认给它返回0意思意思一下。上面是创建成功的情况,如果创建失败就会返回-1给当前进程!!!

在这里插入图片描述

fork()的使用场景

  • 一个进程希望将自己分身,可以一个人做多份工作,那么一个进程就可以通过创建子进程的方式来实现这个目的,可以将自己要干的事分担给子进程,让子进程去帮自己完成。
  • 一个进程需要执行别的可执行程序,就可以通过创建子进程的方式,让子进程通过程序替换去帮自己完成程序的执行,我们使用的shell就是这样的,我们执行命令(命令也是可执行程序)的时候,bash就会创建子进程,然后通过程序替换去执行可执行程序。

fork()调用失败的原因

原因非常简单,类比你的手机下来太多东西,容量不够了就不能在下载东西了!

所以fork()创建子进程失败的原因无非就是操作系统中存在太多的进程,内存不够了。

子进程的数据和代码默认是与父进程共享的

我们学习语言的时候了解过继承,子类会继承父类的成员变量,这个理念在进程中同样被使用!

一个进程如果通过fork()函数创建子进程成功,那么它的子进程就会以它的父进程为模板,拷贝它的代码和数据。

创建子进程的操作系统需要干什么

fork()是系统调用,那么就必须要由操作系统来完成子进程的创建工作,那么os会做什么事情呢?

  • 根据上篇文章的知识,进程是被操作系统管理起来的,一个进程就是一个task_struct结构体,该结构体包括所有的进程的属性,比如进程的pid、进程的代码对应的地址,数据对应的地址,进程的退出状态,退出信号,进程地址空间等等;

  • 操作系统用task_struct 结构体 将进程描述好,再对这些结构体管理,实现对进程的管理工作;

  • 知道了上面这些,那么我们就很容易猜到 创建一个进程操作系统就肯定会先创建一个新的task_struct 结构体,该结构体就是那个新新创建的子进程,创建好结构体还没完,还需要对其进行初始化,根据继承的理念,子进程会默认继承父进程的代码和数据,那么操作系统就会把父进程的task_struct中的代码和数据的地址拷贝给子进程;

父子进程是具有独立性的

进程之间是具有独立性的,即使是父子进程也是如此,各个进程之间都是独立运行的!

父子进程既然共享代码和数据如何实现独立性

既然子进程创建出来后是和父进程共享的代码和数据,那么子进程对继承自父进程的数据或代码进行修改,岂不是会对父进程造成影响吗?那怎么还说进程是具有独立性的呢?

  • 子进程是和父进程共享的代码和数据没错,进程间具有独立也没有错,错的是子进程修改和父进程共享的数据和代码是不可能会影响到父进程的!因为操作系统考虑到了这个问题,并且让父子进程之间具有写时拷贝的机制,使得在父子一方对共享的资源进行修改是就会将修改的资源分离使得父子各有一份,从而实现进程的独立性!!!

下面具体介绍写时拷贝~

写时拷贝详解

未发生写时拷贝:

未发生写时拷贝的时候,父子是共享这数据和代码的。 它们通过各自的页表将相同的虚拟地址映射到相同的物理地址。

在这里插入图片描述

发生写时拷贝后:

当子进程对父子共享的数据或者代码进行修改时,因为进程之间要有独立性,操作不可能直接让子进程将共享的数据给改掉,那样会直接影响到父进程!

所以操作系统会在子进程对父子共享的代码或者数据进行修改的时候,会开辟出一块新的物理空间,将要被修改的代码或者数据拷贝一份之后,让父子中先对共享资源修改的一方去修改新拷贝出来的数据,再让先修改共享资源的一方的页表去重新映射新开辟出来的物理空间实现父子进程的资源分离,互不影响!这就是写时拷贝的基本原理~ (当有一方要对共享资源修改时,为其开要修改的资源开辟新空间,再对该空间的值进行修改,使得二者都有对应的资源,但是被修改的资源不是同一块物理空间了!)

请添加图片描述

注:写时拷贝的相关操作是由操作系统的内存管理模块完成的!

为什么要有写时拷贝?直接在创建子进程的时候就为其数据和代码开辟新的空间,将其和父进程的数据和代码分离开不好么?

  • 1.父子进程的数据,子进程不一定全用,即使使用,也不一定全都会写入(修改)----------存在空间浪费
  • 2.最理想的情况是,只有会被父子修改的数据,才会进行分离拷贝,不会修改修改的共享即可 ------理论可以技术角度上无法实现,父子对数据的修改是不可提前预测的
  • 3.如果fork的时候就无脑的将父进程的数据拷贝给子进程,就会增加fork的成本(内存和时间层面上)

所以既然写时拷贝存在就有它存在的原因,存在即合理!

写时拷贝是解决上述问题的较为合理的方法,所以才会被采用。写时拷贝是一种演示拷贝的策略,只有当你会对数据进行修改的时候才会给你开辟新的空间,将数据分离,当你不修改的时候,就不会给你新开辟空间,这样省下来的空间就可以被其他进程使用了!体现了os良好的内存管理方案

进程终止

进程退出的场景有哪几种?

  • 第一种:代码运行完毕,结果正确
  • 第二种:代码运行完毕,结果不正确
  • 第三种:代码都没执行完,发生了异常,操作系统直接终止进程

进程退出的方式

  • 第一种:通过main函数返回
  • 第二种:通过调用exit()函数或系统调用_exit()退出进程
  • 第三种:给进程发信号,将进程终止(kill)

上面提到了exit()和_exit(),它们的功能都是让进程退出,两者之间有什么区别呢?

exit()和_exit()的区别

exit()是封装_exit()的函数, _exit()是系统调用。exit() 最终也还是会去调用 _exit() , exit()在 _exit()的基础上增加了新的功能,就是:

  • 执行用户通过atexit或on_exit定义的清理函数
  • 关闭所有打开的流,所有的缓存数据都会被写入(刷新)
    请添加图片描述

如何获取一个进程的退出码或者退出信号

  • exit(int status)和 _exit(int status)中的参数status就是进程的退出状态码

  • status只有低16位才有价值,它的0-7位是存储着进程的退出信号,8-15位是存的进程的退出码

  • 进程正常终止时的退出信号是为0的,退出码为进程设置的退出码

  • 进程被信号所终止时,退出码为0,退出信号为进程所收到的信号

在这里插入图片描述

方法一:通过位运算来获取

//获取退出码
status>>8&0xFF
//获取退出信号
status&0x7F
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
int main()
{

  pid_t id=fork();
  if(id==0)
  {
    //child
    int n=10;
    while(n--)
    {
      printf("i am child! i am running pid:%d   ppid:%d\n",getpid(),getppid());
      sleep(1);
    }
    exit(110);
  }
  else if(id>0)
  {
    sleep(10);
  int status=0;
    waitpid(id,&status,0);
    if(id>0)
    {
      sleep(3);
      printf("等待子进程成功\n");
      printf("子进程退出码:%d   退出信号:%d  子进程pid:%d \n",status>>8&0xFF,status&0x7f,id);//位运算获取退出码和退出信号的方式
    }
    printf("父进程退出!\n");
  }
  else 
  {
    printf("fork error!\n");
  }
  return 0;
}

运行结果:

在这里插入图片描述

方法二:通过宏来获取

操作系统提供对应的宏来协助我们获取对应的退出信息

说明
WIFEXITED(int status)子进程正常终止则返回真,可以通过WEXITSTATUS(int status)获取子进程退出码
WIFSIGNALED(int status)子进程异常终止返回真,若为真可通过WTERMSIG(int status)获取子进程的终止信号
WIFSTOPPED(int status)子进程若为暂停状态返回真
WIFCONTINUED(int status)子进程被暂停后将其继续的状态,返回真
WEXITSTATUS(int status)获取子进程退出码 status的次低8位
WTERMSIG(int status)获取子进程终止信号 stauts的低7位
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<unistd.h>
int main()
{
  pid_t id=fork();
  if(id==0)
  {
    while(1)
    {
    printf("i am child pid:%d \n",getpid());
    sleep(1);
  //exit(110);
  }
  }
  else 
  {
    int status=0;
    int ret=waitpid(id,&status,0);//阻塞式等待子进程
   if(ret>0)
   {
   // if(WIFEXITED(status)||WIFSIGNALED(status))
       if(WIFEXITED(status))
       {
            printf("子进程退出! 退出码:%d\n",WEXITSTATUS(status));
       }
     if(WIFSIGNALED(status))
     {
      printf("子进程退出!退出信号:%d\n",WTERMSIG(status));
     }
   }
  }
  return 0;
}

在这里插入图片描述

进程等待

进程等待的用处是什么?

我们知道子进程退出时如果父进程未读取其相关的退出信息那么该子进程就会变成僵尸进程,僵尸进程会有内存泄漏浪费系统资源,且操作系统无法回收,就算是kill 也不能将它怎么样,因为它已经僵尸了,kill可以杀死在运行的进程,但是它做不到杀死一个已经死去的进程!

  • 所以进程等待就是防止产生僵尸进程的处理方式。通过让父进程等待子进程退出,然后再读取它的退出信息,那么子进程就不再僵尸,会变成终止状态,随时等待被系统回收资源!
  • 创建子进程一般都是让子进程去完成父进程给它分配的任务,那么父进程就需要知道子进程最终完成的如何,所以父进程有必要等待子进程退出,读取它的退出状态!

进程等待的方式

wait()函数

pid_t wait(int *stauts)

wait会等待任意一个子进程

参数:

status是输出型参数,可以通过status获取退出子进程的退出状态(退出码或终止信号)

返回值:

成功返回对应子进程pid,失败返回-1

waitpid()函数

pid_t waitpid(pid_t pid,int* status,int options)

waitpid相对wait的可选性更多

参数:

pid : 为-1时,代表着等待任意一个子进程;大于0时,代表等待指定pid的子进程

status:输出型参数,可以通过它获取子进程的退出状态(退出码或终止信号)

options:等待方式,当options为0 时代表父进程阻塞式的等待子进程退出;当options为WNHANG时代表着非阻塞等待,当waitpid返回值为0时,说明子进程还未退出,父进程不会一直在那等待,而是会去干别的事,父进程会以轮询的方式来获取子进程是否退出。

返回值:等待成功返回等待的子进程的pid 失败返回-1

阻塞等待

笼统的理解

所谓的阻塞式等待就是将waitpid中的参数options设置为0,那么父进程就会一直停留在waitpid()这条语句这里,什么也不干就是干等着子进程退出,之后再往下执行后续代码。

系统的理解

父进程阻塞式等待子进程就是当子进程未退出时,操作系统会将父进程的PCB(task_sttuct)放到子进程的等待队列当中,根据前面的进程状态知识可以知道,一个进程等待着某种资源就绪的状态叫做阻塞状态,子进程在等待着父进程退出就是父进程等待着资源就绪,只要子进程不退出,那么父进程就会一直再其等待队列中,直到子进程退出,操作系统才会将父进程继续放回运行队列往下运行后续代码!!!
在这里插入图片描述

非阻塞等待

设置非阻塞等待的方式就是将waitpid()中的options设置为WNOHANG即可,那么父进程就会去询问子进程是否退出,如果退出了就返回子进程的pid,未退出返回0,出错返回-1;

通过这种返回就可以让父进程去以轮询的方式去询问子进程是否退出,如果退出就将其回收,否则父进程就可以去干其他事情,而不是一直在原地阻塞着硬等着子进程退出,非阻塞等待的方式可以解放父进程的时间!!!

进程替换

为什么要有程序替换呢?

子进程被创建出来是共享着父进程的代码的,并且会从fork()之后的代码处开始往后执行,那么执行的是和父进程一样的代码,这是没有什么意义的! 我们通常是创建子进程去让子进程去完成其他的工作,比如让子进程去执行其他的可执行程序,那么这里就需要用到进程的替换。

原理是什么?

进程替换的原理就是当进程调用exec系列的进程替换函数后,当前进程的用户空间的代码和数据就会全部被新的程序所替换,接下来就会执行的是新的程序的代码!

注意:进程替换只是将一个进程的用户空间代码和数据用新的程序的代码及数据来替换,整个过程中是没有创建新的进程的,原进程的pid是不变的,变的只有代码和数据!

请添加图片描述

六种程序替换函数

在这里插入图片描述

execl()

int execl(const char *path, const char *arg, ...);

函数名中的l代表list,指代传执行程序的方式是按列表的方式传参的

参数列表

path:代表着要执行的程序的绝对路径

arg:执行该程序的方式,这里的arg是一个可变参数列表。(执行指令方式的多个字符串都可以被arg接收),但是传给arg的最后一个字符串必须是NULL

例如:执行ls 命令时 我们敲的是 ls -a -l

那么让ls去替换子进程时,首先path 就是传的ls的绝对路径(/usr/bin/ls),剩下的就是我们的执行方式 ls -a -l 那么arg就得是“ls" “-a” “-l”,当然最后得串一个空代表执行命令结束了,所以传给arg的是"ls",“-a”,“-l”,NULL

在这里插入图片描述

执行结果:子进程执行了ls(ls是一个命令 ,也是一个程序!)
在这里插入图片描述

execlp

int execlp(const char *file, const char *arg, ...);

解释

函数名中的p代表着PATH的意思,带p的替换函数就会自动去搜索环境变量PATH;

所以在带p的替换函数中执行某个程序的时候就可以不用传某个程序的绝对路径,只需传其程序名即可,程序替换函数会自己去环境变量中去找这个程序。

参数列表

第一个就是替换的程序路径(不用写全),第二个就是可变参数列表,接收的是执行的方式,同execl.

在这里插入图片描述

execle

int execle(const char *path, const char *arg,..., char * const envp[]);

解释

  • 函数名带l,说明传参方式是列表;

  • 函数名中的e代表着environment的意思,也就是环境变量了。execle函数可以自己组装环境变量。

在这里插入图片描述

在这里插入图片描述

execv()

int execv(const char *path, char *const argv[]);

解释

path代表着执行的程序的绝对路径;

argv代表着执行替换程序的方式,用数组的方式传参。

在这里插入图片描述

execvp

解释

execvp和execlp只相差了一个字母,一个是l,一个是v,l代表list(列表),v代表vecor(数组)

也就是带l的执行程序的方式是以列表的方式传递的,带v的则以数组的方式传递!

在这里插入图片描述

execvpe

解释

类比execle和execlp,带v可知execvp的程序的执行方式以数组形式传递,带p可知其会自己搜索环境变量PATH,带e可知该函数可以自己组装环境变量。

在这里插入图片描述

execve

上面的六个函数都是语言封装的execve函数 execve是系统调用

注意:

  • exec系列的函数的参数args,是接收的程序执行的方式,其必须以NULL结尾!

exec系列函数用法总结:

函数名args(执行方式)的传参形式是否带路径(PATH)是否使用当前环境变量
execl(list)列表
execlp列表
execle列表自己组装环境变量
execv(vector)数组
execvp数组
execvpe数组自己组装环境变量
execve数组自己组装环境变量

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

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

相关文章

Spark 内存运用

RDD Cache 当同一个 RDD 被引用多次时&#xff0c;就可以考虑进行 Cache&#xff0c;从而提升作业的执行效率 // 用 cache 对 wordCounts 加缓存 wordCounts.cache // cache 后要用 action 才能触发 RDD 内存物化 wordCounts.count// 自定义 Cache 的存储介质、存储形式、副本…

【OJ比赛日历】快周末了,不来一场比赛吗? #03.04-03.10 #12场

CompHub 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号同时会推送最新的比赛消息&#xff0c;欢迎关注&#xff01;更多比赛信息见 CompHub主页 或 点击文末阅读原文以下信息仅供参考&#xff0c;以比赛官网为准目录2023-03-04&…

从100%进口到自主可控,从600块降到10块,中科院攻克重要芯片

前言 2月28日&#xff0c;“20多位中科院专家把芯片价格打到10块”冲上微博热搜&#xff0c;据河南省官媒大象新闻报道&#xff0c;热搜中提到的中科院专家所在企业为全球最大的PLC分路器芯片制造商仕佳光子&#xff0c;坐落于河南鹤壁。 为实现芯片技术自主可控自立自强&#…

根据栅格数据的范围和像元大小生成等比例的矢量数据

为啥会有这么一个需求呢&#xff0c;后面要是继续写的话会详细说&#xff0c;首先是有一个栅格数据&#xff0c;比如这样的&#xff1a;我的目标是这样的&#xff08;矢量&#xff09;&#xff0c;就是这样&#xff0c;上面的栅格数据像大小是2*2的&#xff0c;直接上代码&…

【JAVA程序设计】【C00109】基于SSM(非maven)的员工工资管理系统

基于SSM&#xff08;非maven&#xff09;的员工工资管理系统项目简介项目获取开发环境项目技术运行截图项目简介 基于ssm框架非maven开发的企业工资管理系统共分为二个角色&#xff1a;系统管理员、员工 管理员角色包含以下功能&#xff1a; 系统后台登陆、管理员管理、员工信…

Linux基础命令-nice调整进程的优先级

文章目录 Nice 命令介绍 语法格式 常用参数 参考实例 1 调整bash的优先级为-10 2 调整脚本的优先级为6 3 调整指令的优先级 4 默认使用nice命令调整优先级 命令总结 Nice 命令介绍 nice命令的主要功能是用于调整进程的优先级&#xff0c;合理分配系统资源。Linux系…

torchaudio的I/O函数

info、load、save1.1 infotorchaudio.info(filepath: str, ...)Fetch meta data of an audio file. Refer to torchaudio.backend for the detail.返回音频文件的meta信息这里的meta元信息包括采样率、帧数、通道数、量化位数、音频格式info torchaudio.info(rE:\adins\data\2…

Java使用DFA算法实现敏感词过滤

1 前言敏感词过滤就是你在项目中输入某些字&#xff08;比如输入xxoo相关的文字时&#xff09;时要能检测出来&#xff0c;很多项目中都会有一个敏感词管理模块&#xff0c;在敏感词管理模块中你可以加入敏感词&#xff0c;然后根据加入的敏感词去过滤输入内容中的敏感词并进行…

测试软件5

一 css基础 css定义&#xff1a;可以设置网页中的样式&#xff0c;外观&#xff0c;美化 css中文名字&#xff1a;级联样式表&#xff0c;层叠样式表&#xff0c;样式表 二 css基础语法 1.style标签写在title标签后面 2.选择器{属性名1&#xff1a;属性值1&#xff1b;属性名…

ChatGPT最牛应用,让它帮你更新网站新闻吧!

谁能想到&#xff0c;ChatGPT火了&#xff01;既能对话入流&#xff0c;又能写诗歌论文、出面试题、编代码&#xff0c;甚至还通过了谷歌面试拿到L3工程师offer&#xff0c;放在一年之前&#xff0c;没人相信这是当前AI能够达到的水平。ChatGPT自面世以来&#xff0c;凭借其极为…

【数据结构初阶】手把手带你实现栈

前言 在进入数据结构初阶的学习之后&#xff0c;我们学习了顺序表和链表&#xff0c;当然栈也是一种特殊的数据结构&#xff0c;他的特点是后进先出。 栈的概念及结构 栈&#xff08;stack&#xff09;又名堆栈&#xff0c;它是一种运算受限的线性表。限定仅在表尾进行插入和删…

iptables的介绍

iptables简介 1、 什么是iptables&#xff1f; iptables是linux防火墙工作在用户空间的管理工具&#xff0c;是 netfilter/iptables IP信息包过滤系统的一部分&#xff0c;用来设置、维护和检查Linux内核的IP数据包过滤规则 2、 iptables特点 iptables是基于内核的防火墙&…

【pytorch 入门系列】02 手把手多分类从0到1

温故而知新&#xff0c;通过手把手写一个多分类任务来复习之前所学过的知识。 前置知识 factorize的妙用&#xff1a;把文本数据枚举化 labels, uniques pd.factorize([b, b, a, c, b]) labels,uniques(array([0, 0, 1, 2, 0]), array([‘b’, ‘a’, ‘c’], dtypeobject))…

【C++】-- 特殊类设计

对于类的思维境界提升&#xff0c;没有太大的实际意义&#xff0c;但是锻炼思想。 目录 单例模式 饿汉模式 懒汉模式 #&#xff1a;请设计一个类&#xff0c;不能被拷贝。 拷贝只会发生在两个场景中&#xff1a;拷贝构造函数赋值运算符重载因此想要让一个类禁止拷贝&#xf…

对称锥规划:对称锥的增广拉格朗日乘子法(Semi-Smooth Newton Method解无约束优化子问题)

文章目录对称锥规划&#xff1a;对称锥的增广拉格朗日乘子法&#xff08;Semi-Smooth Newton Method解无约束优化子问题&#xff09;对称锥的增广拉格朗日函数Semi-Smooth Newton Method半光滑牛顿法广义雅可比半光滑性半光滑牛顿算法参考文献对称锥规划&#xff1a;对称锥的增…

2023年最新阿里云服务器价格表出炉(精准收费标准及配置价格表)

阿里云在全球率先宣布了基于 Intel Ice Lake 处理器的第七代云服务器ECS&#xff0c;性能提升的同时降低了报价&#xff0c;性价比更高了。进入2023年阿里云服务器价格依然是大家关心的问题&#xff0c;事实上阿里云服务器租用价格和最新收费标准都可以通过官方云服务器计算器来…

【IoT】智能烟雾报警器

设计简介 硬件设计由AT89C51单片机、DS18B20温度传感器、4位共阳数码管、电源模块、报警模块、按键模块、MQ-2烟雾检测模块和ADC0832模数转换模块组成。 烟雾传感器MQ-2检测空气中的烟雾气体&#xff0c;通过ADC0832进行数据转换&#xff0c;经过单片机的运算处理后在数码管上…

【WEB前端进阶之路】 HTML 全路线学习知识点梳理(上)

前言 HTML 是一切Web开发的基础&#xff0c;本文专门为小白整理&#xff0c;针对前端零基础的朋友们&#xff0c;手把手教你学习HTML&#xff0c;让你轻松迈入WEB开发的行列。 首先&#xff0c;感谢 橙子_ 在HTML学习以及本文编写过程中对我的帮助。 文章目录前言一.HTML简介1.…

Java使用不同方式获取两个集合List的交集、补集、并集(相加)、差集(相减)

1 明确概念首先知道几个单词的意思&#xff1a;并集 union交集 intersection补集 complement析取 disjunction减去 subtract1.1 并集对于两个给定集合A、B&#xff0c;由两个集合所有元素构成的集合&#xff0c;叫做A和B的并集。记作&#xff1a;AUB 读作“A并B”例&#…

微纳制造技术——基础知识

1.什么是直接带隙半导体和间接带隙半导体 导带底和价带顶处以同一K值&#xff0c;称为直接带隙半导体 导带底和价带顶不处在同一K值&#xff0c;称为间接带隙半导体 2.扩散和漂移的公式 3.三五族半导体的性质 1.high mobility 2.wide bandgap 3.direct bandgap 4.三五族…