【Linux】进程控制(详细解析)

news2024/11/19 13:20:59

文章目录

  • 一.进程创建
    • 初识fork函数
    • fork函数返回值
    • 写时拷贝
    • fork常规用法
    • fork调用失败的原因
  • 二.进程终止
    • 进程退出场景
    • 进程退出码
    • 进程常见退出方法
      • 1.return
      • 2.exit
      • 3._exit
  • 三.进程等待
    • 进程等待的必要性
    • 获取子进程状态status
    • 进程等待的方法
      • wait方法
      • waitpid方法
    • 基于非阻塞接口的轮询检测方案
  • 四.进程程序替换
    • 替换原理
    • 替换函数
    • 函数解释
    • 命名理解
  • 五.做一个简易shell(命令行解释器)


一.进程创建

初识fork函数

在Linux中fork函数时非常重要的函数,它作用是从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

fork函数的返回值:子进程返回0,父进程返回的是子进程的pid,子进程创建失败返回1。

#include <unistd.h>
pid_t id = fork(void);

进程调用fork函数,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构(主要有:链表、队列、映射和红黑树。)给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 将子进程添加到系统进程列表中
  • fork返回,开始调度器调度
    在这里插入图片描述
    运行结果:可以看到fork前只输出了一次,fork之后输出了两次,那是因为fork前的由父进程打印,fork之后的分别由父进程和子进程打印。也就是fork之前父进程独立运行,fork之后变成父进程和子进程两个指向执行流在执行。可得知当一个进程调用fork之后,就有两个二进制代码相同的进程,相当于子进程将父进程拷贝了一份,而且它们都运行到相同的地方,每个进程都将开始它们自己的旅程。(注意:fork之后,谁先执行完全由调度器决定)
    在这里插入图片描述

fork函数返回值

我们前面直到了fork函数子进程返回0,父进程返回的是子进程的pid,子进程创建失败返回1。那么我有以下问题:

fork函数为什么给子进程返回0,给父进程返回的是子进程的PID?

一个父进程可以创建多个子进程,而一个子进程只能有一个父进程,因此对于子进程来说父进程是不需要被标识的;而对于父进程来说,子进程是需要标识的,因为1父进程创建子进程的目的是让其指向对于的任务,只有直到了各个子进程的PID,才能更有效率的工作。

为什么fork函数有两个返回值?

父进程调用fork函数后,为了创建子进程,fork函数内部会进行一系列复杂的操作,包括创建子进程PCB,虚拟地址空间,创建子进程对应的页表等等。子进程创建完毕后,操作系统还需要将子进程的进程控制块添加到系统进程列表中。
在这里插入图片描述
也就是说fork函数return之前,子进程就已经创建好了,这时由父子进程两个执行流,所以fork函数有两个返回值。

写时拷贝

当子进程刚刚被创建时,子进程和父进程共享数据代码,即父子进程的代码和数据通过页表映射到物理内存的同一块空间。当任意一方尝试写入的时候就会发生写时拷贝。
在这里插入图片描述

为什么数据要进行写时拷贝?

因为进程具有独立性,多进程在运行时,需要独享各种资源,多进程运行期间互不干扰,不能让子进程的修改影响到父进程的数据

怎么理解虚拟内存地址空间?

1.因为早期的计算机运行方式是直接将程序运行在物理内存上。这就存在三个问题:
问题1. 地址空间不隔离
所有程序都直接访问物理内存,程序使用的物理空间不是相互隔离的。万一进程越界进行非法操作,这样是非常不安全的。

问题2. 内存使用效率低
没有有效的内存管理机制,通常执行一个程序时,监控程序需要将其整个程序装入内存然后开始执行。

问题3. 程序运行的地址不确定
因为每次需要装入运行时,我们都需要给他分配一块足够大的物理空间,而这个物理空间是不确定的。

解决方法:增加中间层,即虚拟内存地址空间
思想: 把程序给出的地址看作是一个虚拟地址,然后通过某种映射关系/方法,将这个虚拟地址转换成实际的物理地址。
这样,只要我们能够处理好虚拟地址和物理地址的映射过程,我们就可以保证任意一个程序所能访问的物理内存空间是互不相同的,以达到地址空间隔离的效果。虚拟内存地址空间的存在,可以更方便的进行进程和进程数据代码的解耦,保证了进程独立性这样的特征,并且可以让进程以统一的视角,来看待进程对应的代码和数据等各个区域,方便编译器也以统一的视角编译代码。(一套是物理内存地址,一套是程序之间实现跳转的虚拟地址)

fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

fork调用失败的原因

返回-1

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

二.进程终止

进程退出场景

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

进程退出码

我们都知道main函数是代码的入口,但实际上main函数只是用户级别代码的入口,main函数也是被其他函数调用的,例如在VS2013当中main函数就是被一个名为__tmainCRTStartup的函数所调用,而__tmainCRTStartup函数又是通过加载器被操作系统所调用的,也就是说main函数是间接性被操作系统所调用的。
既然main函数是间接性被操作系统所调用的,那么当main函数调用结束后就应该给操作系统返回相应的退出信息,而这个所谓的退出信息就是以退出码的形式作为main函数的返回值返回,我们一般以0表示代码成功执行完毕,以非0表示代码执行过程中出现错误,这就是为什么我们都在main函数的最后返回0的原因。
当我们的代码运行起来就变成了进程,当进程结束后main函数的返回值实际上就是该进程的进程退出码

观察这份代码,最后执行的结果是不等于5050的,所以main函数返回1,代表代买运行完毕,结果不正确。
在这里插入图片描述
我们可以使用echo $?命令查看最近一次进程退出的退出码信息
在这里插入图片描述

那么为什么要用0表示代码执行成功,以非0表示代码执行错误?

因为代码执行成功只有一种情况,成功了就是成功了,而代码执行错误却有多种原因,例如内存空间不足、非法访问以及栈溢出等等,我们就可以用这些非0的数字分别表示代码执行错误的原因。

C语言当中的strerror函数可以通过错误码,获取该错误码在C语言当中对应的错误信息:
在这里插入图片描述

在这里插入图片描述
实际上Linux中的ls、pwd等命令都是可执行程序,使用这些命令后我们也可以查看其对应的退出码。
可以看到,这些命令成功执行后,其退出码也是0。
在这里插入图片描述

进程常见退出方法

正常终止(可以通过 echo $? 查看进程退出码):

  1. 从main返回,return退出
  2. 调用exit
  3. _exit

异常退出:

  1. ctrl+c,信号终止

1.return

return是一种常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数,会将main的返回值当做 exit的参数。

2.exit

exit最后也会调用_exit, 但在调用exit之前,还做了其他工作:

  1. 执行用户通过 atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit
    下列代码会刷新缓冲区,将缓冲区的数据输出,即能够输出hello world
    在这里插入图片描述

3._exit

使用_exit函数退出进程的方法我们并不经常使用,_exit函数也可以在代码中的任何地方退出进程,但是_exit函数会直接终止进程,并不会在退出进程前会做任何收尾工作。

例如,上面的代码中使用_exit终止进程,则缓冲区当中的数据将不会被输出。
在这里插入图片描述

三.进程等待

进程等待的必要性

  • 子进程退出,父进程如果不读取子进程的退出信息,子进程就会变成僵尸进程,进而造成内存泄漏。
  • 进程一旦变成僵尸进程,那么就算是kill -9命令也无法将其杀死,因为谁也无法杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程需要通过进程等待的方式,回收子进程资源,获取子进程的退出信息。

获取子进程状态status

下面会介绍有关进程等待的两个函数wait和waitpid,它们都有一个status参数,改参数是一个输出型参数,由操作系统进行填充。如果对该参数传入NULL,表示不关心子进程的退出状态信息。反之,操作系统会通过该参数,将子进程的退出状态信息反馈给父进程。

如何理解参数status?

status实际上是一个整型变量,但是status不能当作整型里看待,因为status的不同的比特位所代表的信息不同(只研究低16位比特位)。在status的低16位比特位中,高8位表示进程的退出状态,即退出码。进程若是被信号所杀,低7位表示终止信号,而第8位是core dump标志。需要注意的是,当一个进程非正常退出时,说明该进程是被信号所杀,那么该进程的退出码也就没有意义了
在这里插入图片描述

什么是终止信号?怎么理解?

当终止信号为0,说明程序正常终止,有对应的退出码。如果进程是被信号所杀,那么就有对应的终止信号(异常原因)。如下面讲解的wait和waitpid函数的代码,属于正常退出,所以打印出来的终止信号为0。那么如果我们在代码中设置一个除0(报浮点数错误)或者空指针(报段错误)的错误,程序就会异常退出,这时打印出来的终止信号就会对应错误打印如下表的数字。注意这时退出码无意义。
在这里插入图片描述

如何通过参数status获取进程的退出码和终止信号?

1 exitCode = (status>>8) & 0xFF;//退出码
2 exitSignal = status & 0x7F;//退出信号

进程等待的方法

wait方法

1 头文件
#include<sys/types.h>
#include<sys/wait.h>

2 函数原型
pid_t wait(int* status);

3 返回值
成功返回被等待进程pid,失败返回-1。

4 参数
输出型参数,获取子进程退出状态(退出码),不关心可以设置为NULL

我们来看以下代码:
在这里插入图片描述

我们用这个命令行脚本对进程状态信息进行监控:

[nan@VM-8-10-centos 进程等待]$ while :;do ps ajx | head -1 && ps axj | grep |grep -v grep; sleep 1;done

这时我们看到,监控进程得到的结果与预期相符,fork先创建子进程,在开始的10s内,子进程正常运行(S+状态),随后子进程退出,变成僵尸状态(Z),接下来10s,子进程都处于僵尸状态,然后父进程调用wait函数,将子进程回收,并且wait函数返回了被回收的子进程的PID,最后只剩下父进程为S+状态。
在这里插入图片描述

waitpid方法

1 函数原型:
pid_t waitpid(pid_t pid, int* status, int options);

2 返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

3 参数:
pid:
pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。

status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进
程的ID。

我们来看以下代码:

  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<sys/wait.h>
  4 #include<unistd.h>
  5 #include<stdlib.h>
  6 int main()
  7 {
  8     pid_t id = fork();
  9     if (id == 0)
 10     {
 11         int cnt = 10;
 12         while (cnt--)
 13         {
 14           printf("I am child...PID:%d, PPID:%d,cnt:%d\n", getpid(), getppid(),cnt);
 15           sleep(1);
 16         }
 17         exit(10);//自己设置了一个退出码为10
 18     }
 19     //parent
 20     //sleep(20);//父进程先不回收子进程,先让我们观察一下子进程的僵尸状态,再回收
 21     //pid_t ret = wait(NULL);//等待成功返回子进程PID
 22     int status=0;
 23     pid_t ret = waitpid(id,&status,0);//这里0表示阻塞
 24     if(id>0)
 25     {
 26         printf("wait success: %d,sig number: %d, child exit code: %d\n",ret,(status & 0x7F),(status>>8)&0xFF);//拿到终止信号 和 子进程的退出码                        
 27     }
 28     sleep(5);
 29     return 0;
 31 }

执行结果:进程正常退出,终止信号为0,子进程的退出码也如我们在代码中所设置的一样。
在这里插入图片描述

基于非阻塞接口的轮询检测方案

首先我们的明确什么是阻塞状态,什么是非阻塞状态?

在前面讲的代码中,如果子进程未退出,父进程就一直等待子进程退出,在父进程等待期间,父进程不能做其它事,这种等待叫做阻塞等待。

父进程不用等到子进程退出,父进程才退出,而是每过一段时间去检测一下子进程的状态,那么在子进程未退出时父进程就可以做其它事,当子进程退出时再读取子进程的退出信息,这种等待叫做非阻塞等待。

如何实现非阻塞等待?

实现方法很简单,前面我们学习了waitpid函数,waitpid函数是这样使用的:pid_t waitpid(pid_t pid, int* status, int options);
实现非阻塞等待,我们只需要向waitpid函数的第三个参数传入WNOHANG,这样的话,等待的子进程如果没有结束,waitpid函数将直接返回0,不予以等待。如果等待的子进程正常退出,则返回该子进程的pid。

代码实例:

  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<sys/wait.h>
  4 #include<unistd.h>
  5 #include<stdlib.h>
  6 int main()
  7 {
  8     pid_t id = fork();
  9     if (id == 0)
 10     {
 11         //child
 12         int cnt = 10;
 13         while (cnt--)
 14         {
 15           printf("I am child...PID:%d, PPID:%d,cnt:%d\n", getpid(), getppid(),cnt);
 16           sleep(1);
 17         }
 18         exit(10);//自己设置了一个退出码
 19     }
 20     //parent
 21     //sleep(20);//父进程先不回收子进程,先让我们观察一下子进程的僵尸状态,再回收
 22     //pid_t ret = wait(NULL);//等待成功返回子进程PID
 23     int status=0;
 24     while(1)
 25     {
 26         pid_t ret=waitpid(id,&status,WNOHANG);
 27         if(ret==0)
 28         {
 29             //表示waitpid调用成功,但是子进程还没退出,父进程没有一直在等待,处于非阻塞状态
 30             //子进程没有退出,waitpid没有等待失败,仅仅是检测到了子进程没有退出,可以多给李四打几次电话,检测他的状态准备好了没有
 31             printf("wait done, but child is still running......\n");//这里父进程可以做其它事
 32         }
 33         else if(ret>0)
 34         {
 35             //waitpid调用成功&&子进程退出了
 36             printf("wait success, exit code:%d, sig: %d\n",(status>>8)&0xFF,status&0x7F);
 37             break;
 38         }
 39         else
 40         {                                                                                                                                                              
 41             //waitpid调用失败
 42             printf("waitpid call failed\n");
 43             break;
 44         }
 45     }
 46     return 0;

四.进程程序替换

替换原理

1.创建子进程的目的?

执行父进程磁盘代码的一部分;
让子进程加载磁盘上指定的程序到内存中,执行新程序的的代码和数据。

2.程序替换的本质

将指定程序的代码和数据加载到指定的位置
在这里插入图片描述

3.进程替换的时候,有没有创建新的进程?

我们知道一个进程被创建出来,OS会给它分配进程PCB,mm_struct,页表等信息,同时会将程序的代码和数据加载到物理内存。要知道进程程序替换之后,该进程的PCB,进程地址空间,页表等信息都不会发生改变,仅仅是把一个新的程序的数据和代码替换了原来进程的代码和数据,只是物理内存当中的数据和代码发生了改变,所以并没有创建新的进程,而且进程程序替换前后该进程的pid也没有改变。

4.子进程进行进程程序替换后,会影响父进程的代码和数据吗?

子进程刚被创建时,与父进程共享代码和数据,但当子进程需要进行进程程序替换(调用exec函数)时,也就意味着子进程需要对其数据和代码进行写入操作,这时便需要将父子进程共享的代码和数据进行写时拷贝,此后父子进程的代码和数据分离,因此子进程进行进程程序替换后不会影响父进程的代码和数据。

替换函数

(1)exec函数说明
fork函数是用于创建一个子进程,该进程几乎是父进程的副本,而有时我们希望子进程去执行另外的程序,exec函数族就提供了一个在进程中启动另一个新的程序的执行方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段,代码段和堆栈段,在执行完后,原调用进程的内容除了进程号外,其它全部的代码数据都被新程序替换了(注意PCB,页表,mm_struct等不被改变,只是被替换,不是创建新的进程),另外这里的可执行文件可以是二进制文件,也可以是Linux下任何可执行脚本文件。

(2)在Linux中使用exec函数族主要有以下两种情况:
当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用exec函数族让自己重生;如果一个进程想执行另一个程序,那么它就可以调用fork函数新建一个进程,任何调用exec函数使子进程重生。

(3)替换函数有六种以exec开头的函数,它们统称为exec函数:

头文件:unistd.h

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

第一个参数是要执行程序的路径(表示你要执行的程序在哪里?),第二个参数是可变参数列表(表示你要如何执行这个程序),你在命令行上怎么执行,就怎么传参,所以exe函数必须以NULL结尾
使用实例:在linux下ls也是一个程序
在这里插入图片描述
运行结果:
在这里插入图片描述

问:为什么没有打印最后一句?
因为exec执行完后,代码已经全部被我们调用的新程序覆盖,开始执行新的程序的代码了,所以printf就无法执行了。

示范了一个例子,剩下5个exec函数的说明如下:

int execlp(const char *file, const char *arg, ...);
//带p的,可以使用环境变量PATH,无需写全路径
execlp("ls", "ls", "-a", "-i", "-l", NULL);

//带e的,需要自己组装环境变量
int execle(const char *path, const char *arg, ..., char *const envp[]);
char* myenvp[] = { "MYVAL=2021", NULL };//自己设置的环境变量
execle("./mycmd", "mycmd", NULL, myenvp);

int execv(const char *path, char *const argv[]);
char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execv("/usr/bin/ls", myargv);

int execvp(const char *file, char *const argv[]);
char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execvp("ls", myargv);

int execve(const char *path, char *const argv[], char *const envp[]);
char* myargv[] = { "mycmd", NULL };
char* myenvp[] = { "MYVAL=2021", NULL };
execve("./mycmd", myargv, myenvp);//设置了MYVAL环境变量,在mycmd程序内部就可以使用该环境变量。

函数解释

  • exec函数族调用成功则加载新的程序从启动代码开始执行,不再返回。
  • 调用出错,返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值。

为什么exec调用成功不做任何返回,只有调用错误的时候才返回-1?

因为如果调用成功,新的程序会将原来的代码和数据覆盖,就和接下来的代码无关了,所以调用成功的返回值,用不上,毫无意义。即如果exec函数只要返回了,就一定出错。

命名理解

这六个exec系列函数的函数名都以exec开头,其后缀的含义如下:

  • l(list):表示参数采用列表的形式,一一列出。
  • v(vector):表示参数采用数组的形式。
  • p(path):表示能自动搜索环境变量PATH,进行程序查找。
  • e(env):表示可以传入自己设置的环境变量。
    在这里插入图片描述
    事实上,只有execve才是真正的系统调用,其它五个函数最终都是调用的execve,所以execve在man手册的第2节,而其它五个函数在man手册的第3节,也就是说其他五个函数实际上是对系统调用execve进行了封装,以满足不同用户的不同调用场景的。
    在这里插入图片描述

五.做一个简易shell(命令行解释器)

原理:shell是命令行解释器,当有命令需要执行时,shell创建子进程,让子进程执行命令,而shell只需等待子进程退出即可。

实现思路:
获取命令行(fgets函数)。
解析命令行(strtok分割字符串)。
创建子进程(fork函数)。
替换子进程(exec函数族)。
等待子进程退出(waitpid函数)。

    1 #include<stdio.h>
    2 #include<stdlib.h>
    3 #include<unistd.h>
    4 #include<assert.h>
    5 #include<string.h>
    6 #include<sys/types.h>
    7 #include<sys/wait.h>
    8 
    9 #define LEN 1024//命令行可输入的最大字符串长度
   10 #define NUM 64//命令行字符串分割后,命令行参数的最大个数
   11 
   12 char Command[LEN];//存储我们输入的命令(本质是字符串)"ls -a -l"
   13 char* myargv[NUM];//指针数组,存的是分割后的每个命令行参数的首元素地址 "ls""-a""-l"
   14 
   15 int main()
   16 {
   17     //命令行解释器需要一直输出
   18     while(1)
   19     {
   20         //输出命令行解释器
   21         printf("用户名@主机名 当前路径# ");
   22         fflush(stdout);
   23 
   24         //获取用户输入,我们最后都会输入'\n'【注意】
   25         //char *fgets(char *s, int size, FILE *stream);
   26         char* s=fgets(Command,sizeof(Command)-1,stdin);
   27         assert(s!=NULL);//输入不能为空
   28         (void)s;
   29         //清除最后一个'\n',避免输出再换行
   30         //ls -a -l\n'\0'   abc\n\0
   31         Command[strlen(Command)-1]=0;
   32         //printf("test: %s\n",Command);
   33         
   34         //字符串切割
   35         myargv[0]=strtok(Command," ");
   36         int i=1;
   37         while(myargv[i++] = strtok(NULL, " "));
   38 
   39         //测试是否成功:条件编译
   40 #ifdef DEBUG                                                                                                                                                                   
   41         for(i=0;myargv[i];i++)
   42         {
   43             printf("myargv[%d]: %s\n",i,myargv[i]);
   44         }
   45 #endif
   46         pid_t id=fork();//让子进程执行我们自己的程序
   47         assert(id!=-1);
   48         if(id==0)
   49         {
   50             //child
   51             execvp(myargv[0],myargv);//child进行程序替换
   52             exit(1);
   53         }
   54         //shell
   55         int status=0;
   56         pid_t ret = waitpid(id,&status,0);//shell等待子进程退出,进行回收
   57         if(ret>0)
   58         {
   59             printf("exit code:%d\n",WEXITSTATUS(status));//打印子进程退出码
   60         }
   61         
   62     }
   63     return 0;
   64 }                

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

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

相关文章

python操作redis

目录 python操作redis 安装redis模块 基本链接 连接池连接 redis字符串操作 redis hash操作 redis 列表操作 redis 其它操作 redis管道 django中集成redis python操作redis 安装redis模块 pip install redis基本链接 # 第一步&#xff1a;导入Redis类 from redis …

Linux网络原理及编程(7)——第十七节 网络层

目录 IP报头 网段划分 私有IP地址和公网IP地址 补充一下路由器 的有关知识&#xff1a; 路由 各位好&#xff0c;博主新建了个公众号《自学编程村》&#xff0c;拉到底部即可看到&#xff0c;有情趣可以关注看看哈哈&#xff0c;关注后还可以加博主wx呦~~~&#xff08;公众…

【玩转c++】c++ 中 STL 简介

本期主题&#xff1a;介绍cSTL&#xff08;标准模板库&#xff09;博客主页&#xff1a;小峰同学分享小编的在Linux中学习到的知识和遇到的问题小编的能力有限&#xff0c;出现错误希望大家不吝赐身为程序员 &#xff0c;不会有人没有女朋友吧。 &#x1f341;1.什么是STL&…

[趣味][人工智能生成文字]chatGPT使用教程

ChatGPT 首先点击sign up注册&#xff0c;注册需要非中国手机号获取验证码&#xff0c;这里感谢一下alice的支援&#xff0c;没有好朋友的各位看到这里可以先去逛逛淘宝 注册后点击log in登录 然后直接输入想要生成的内容&#xff0c;点击右侧的小箭头 注意&#xff1a;根据Op…

cef浏览器加载过程实测ILoadHandler和IRequestHandler

针对方法GetResourceRequestHandler获取资源请求过程中,会多次发生请求,不知道何时加载完的问题,IRequestHandler没有了OnResourceLoadComplete和OnBeforeResourceLoad方法,如何判断是否加载完。使用browser.isLoading并不能真正的判断。所以想到了 OnFrameLoadEnd OnFram…

【STM32】详解独立看门狗的本质和使用步骤代码

一、看门狗 1、介绍 作为一个检测装置&#xff0c;发生意外情况能够报告并处理突发意外——复位。 复位中断属于不可屏蔽中断&#xff0c;属于优先级最高的中断 2、作用 两个看门狗&#xff08;独立看门狗和窗口看门狗&#xff09;均可用于检测并解决由软件错误导致的故障&…

设计没灵感,一定要逛这5个网站。

本期给大家分享几个设计灵感网站&#xff0c;希望对设计师们有所帮助&#xff0c;话不多说直接上内容。 1、dribbble Dribbble - Discover the World’s Top Designers & Creative Professionals Dribble是一个很大的设计作品共享网站&#xff0c;也涵盖了很丰富的设计作…

HTTP 和 HTTPS 它们之间的区别在哪里?

您可能已经听说过很多有关互联网术语 HTTP 和 HTTPS 的信息。您知道两者之间的区别是什么吗&#xff1f;HTTP 在随着技术的不断更新已经慢慢开始消失在互联网之中。在浏览器的地址栏中&#xff0c;您访问的每个网站的 URL 始终以 HTTP 或 HTTPS 开头&#xff0c;而目前 HTTPS 协…

SSM校园网报修系统计算机毕业论文java毕业设计选题源代码

&#x1f496;&#x1f496;更多项目资源&#xff0c;最下方联系我们✨✨✨✨✨✨ 目录 Java项目介绍 资料获取 Java项目介绍 《SSM校园网报修系统》该项目采用技术&#xff1a;jsp springmvcspringmybatis cssjs等相关技术&#xff0c;项目含有源码、文档、配套开发软件、…

02源码分析-ThreadLocal详解-并发编程(Java)

文章目录1 ThreadLocal内部结构2 主要方法源码分析2.1 set()方法2.1.1 getMap()2.1.2 createMap()2.1.3 ThreadLocalMap.set()2.1.4 replaceStaleEntry()2.1.5 expungeStaleEntry()2.1.6 cleanSomeSlots()2.1.7 rehash()2.1.8 expungeStaleEntries()2.1.9 resize()2.2 get()方法…

Minitab使用图形渲染和数据描述

Minitab使用图形渲染和数据描述 Minitab是最流行的质量、分发和分析程序之一&#xff0c;实际上是OMNITAB软件的一个较小版本。六西格玛的软件是开发组织质量开发和改进的合适工具&#xff0c;具有处理、计算、分析、报告和其他数据工具的强大能力。的确如此。在本软件的上下文…

HTML列表与表格详解_高效学习攻略

HTML列表与表格HTML篇_第六章、HTML列表与表格一、列表1.1定义1.2列表的分类1.3列表的对比二、表格2.1表格的定义2.2表格的边框2.3表格的表头单元格2.4表格标题 <caption>2.5表格的高度和宽度2.6表格背景2.7表格空间2.8合并单元格2.9表格头部、主题和页脚2.10表格的嵌套H…

《可解释机器学习公开课》来了!

Datawhale开源 联合发布&#xff1a;同济子豪兄、Datawhale文章目录1.什么是机器学习的可解释性分析。2.学可解释机器学习有什么用&#xff1f;3.可解释机器学习开源学习计划&#xff0c;同济子豪兄和 Datawhale 联合发布。什么是可解释AI现代的机器学习和人工智能&#xff0c;…

redis 的雪崩、穿透和击穿

面试题 了解什么是 redis 的雪崩、穿透和击穿&#xff1f;redis 崩溃之后会怎么样&#xff1f;系统该如何应对这种情况&#xff1f;如何处理 redis 的穿透&#xff1f; 面试官心理分析 其实这是问到缓存必问的&#xff0c;因为缓存雪崩和穿透&#xff0c;是缓存最大的两个问…

【C++】list的模拟实现来咯

文章目录一、整体框架二、迭代器1、list迭代器的引入2、迭代器的区分3、list迭代器的实现4、模板三、增删查改1、insert和erase2、push_back和push_front3、pop_back和pop_front四、list的接口1、构造2、析构3、赋值重载五、list和vector的对比一、整体框架 list的本质就是带头…

中润光学通过科创板注册:预计年营收4亿 拟募资4亿

雷递网 雷建平 12月9日嘉兴中润光学科技股份有限公司&#xff08;简称&#xff1a;“中润光学”&#xff09;日前通过注册&#xff0c;准备在科创板上市。中润光学计划募资4.05亿&#xff0c;其中&#xff0c;2.7亿元用于高端光学镜头智能制造项目&#xff0c;5629万元用于高端…

【HDU No. 2243】单词情结 考研路茫茫——单词情结

【HDU No. 2243】单词情结 考研路茫茫——单词情结 杭电OJ 题目地址 【题意】 单词和词根仅由小写字母组成。给定N个词根&#xff0c;求长度不超过L 且至少包含一个词根的单词可能有多少个&#xff1f; 若有两个词根aa和ab&#xff0c;则长度不超过3且至少包含一个词根的单词…

IO流,,

标题1. 文件基础知识1.1 文件基础知识-文件流1.2 创建文件的3种方式(不是目录)3. 获取文件的相关信息4. 删除文件及创建多级(一极)目录2. IO流原理及流的分类2.1 InputStream 字节输入流2.1.1 FileInputStream2.1.2 FileOutStream2.1.3 拷贝文件2.2 FileReader2.3 FileWriter2.…

MyCat教程【mysql主从复制实现】

单个mysql数据库在处理业务的时候肯定是有限的&#xff0c;这时我们扩展数据库的第一种方式就是对数据库做读写分离&#xff08;主从复制&#xff09;,本文我们就先来介绍下怎么来实现mysql的主从复制操作。 1. 读写分离 原理&#xff1a;需要搭建主从模式&#xff0c;让主数…

Python实现房产数据分析与可视化 数据分析 实战

Python库的选择 话说&#xff0c;工欲善其事&#xff0c;必先利其器&#xff0c;虽然我们已经选择Python来完成剩余的工作&#xff0c;但是我们需要考虑具体选择使用Pytho的哪些利器来帮助我们更快更好地完成剩余的工作。 我们可以看一下&#xff0c;在这个任务中&#xff0c…