Linux——进程控制

 

目录

1. 进程创建

1.1 fork函数

1.2 fork系统调用内部宏观流程

 1.3 fork后子进程执行位置分析

1.4 fork后共享代码分析

1.5 fork返回值

1.6 写时拷贝

1.7 fork常规用法

1.8 fork调用失败的原因

2.进程终止

2.1 进程退出场景

2.2 strerror函数—返回描述错误号的字符串

2.3 进程常见退出方法

2.4 _exit函数和exit函数

 2.5 return退出

3. 进程等待

3.1 进程等待必要性

3.2 进程等待方法

3.3 获取子进程status

3.4 进程阻塞等待和非阻塞等待

3.5 waitpid系统调用接口分析

3.6 阻塞等待代码和基于非阻塞调用的轮询检测方案

4. 进程程序替换

4.1 替换原理

4.2 替换函数

4.3 函数解释

4.4 命名理解

4.5 实现简易shell

5. 函数和进程之间的相似性:


1. 进程创建

1.1 fork函数

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

#include <unistd.h>
pid_t fork(void);
返回值:子进程中返回0,父进程返回子进程id,出错返回-1

1.2 fork系统调用内部宏观流程

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

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

fork创建子进程,系统中多了一个进程,及包含进程对应的PCB结构体以及对应的地址空间及页表映射关系,并将代码和数据加载到内存中,并将该进程加载到运行队列,等待操作系统调度器的调度!一旦该进程被调度起来,CPU就可以通过代码和数据及地址空间和页表映射,在物理内存找到对应的代码,进行运行!

 当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以 开始它们自己的旅程,看如下程序。

int main( void )
{pid_t pid;printf("Before: pid is %d\n", getpid());if ( (pid=fork()) == -1 )perror("fork()"),exit(1);printf("After:pid is %d, fork return %d\n", getpid(), pid);sleep(1);return 0;
} 运行结果:
[root@localhost linux]# ./a.out
Before: pid is 43676
After:pid is 43676, fork return 43677
After:pid is 43677, fork return 0

这里看到了三行输出,一行before,两行after。进程43676先打印before消息,然后它有打印after。另一个after 消息有43677打印的。注意到进程43677没有打印before,为什么呢?如下图所示

 所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。

 1.3 fork后子进程执行位置分析

 fork前后,父子进程所有代码共享!

  • 代码汇编后,加载到内存,都有与之对应的地址
  • 因为进程随时可能被中断(可能并没执行结束),下次回来,还必须从之前的位置继续执行(不是最开始位置),就需要要求CPU必须随时记录当前进程执行的位置,所以,CPU有对应的寄存器数据EIP(PC指针,也叫做程序计数器,用于记录当前正在执行代码的下一行代码的地址),因此在进程切换时,需要将寄存器信息带走(上下文数据)。


寄存器在CPU内,只有一份(切换进程必须带走数据),寄存器内的数据,是可以有多份的(寄存器上下文数据

创建子进程的时候,也会将上下文数据给子进程(解释了fork之后,子进程不会从before处运行),虽然父子进程各自调度,各自会修改EIP,但是不重要了,子进程已经认为自己的EIP起始值,是fork之后的代码

1.4 fork后共享代码分析

创建子进程,给予进程分配对应的内核结构,必须子进程自己独有了,因为进程具有独立性!理论上,子进程也要有自己的代码和数据!可是一般而言,我们没有加载的过程,也就是说,子进程没有自己的代码和数据!!所以,子进程只能”使用“父进程的代码和数据!

代码:都是不可被写的,只能读取,所以父子共享,没有问题!

数据:可能被修改的,所以,必须分离!

对于数据而言:

  1. 创建进程的时候,就直接拷贝分离吗?(导致可能拷贝子进程根本就不会用到的数据空间,即便是用到,也可能只是只读)
  2. 编译器在编译时,尚且知道节省空间,操作系统同样如此,如分批加载挂起状态
  3. 因此创建子进程,不需要将不会被访问的或者只读取的数据,再去拷贝一份,浪费空间

但是,还有必须拷贝的数据,什么样的数据值得拷贝?将来会被父子进程写入的数据!!如上图g_val或者接收fork返回值的变量

  • 一般而言,即便是操作系统,也无法知道那些空间可能被写入!
  • 即便是提前拷贝了,也可能不会立马使用,因此造成空间浪费!
  • 所以操作系统提供了写时拷贝技术,来对父子进程的数据进行分离!

1.5 fork返回值

  • 子进程返回0,
  • 父进程返回的是子进程的pid

1.6 写时拷贝

通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:

 因为有写时拷贝技术的存在,所以,父子进程得以彻底分离!完成了进程的独立性的技术保证!(写时拷贝的好处)写时拷贝只在写入数据时发生拷贝,且对只读数据和代码不发生拷贝,节省空间!写时拷贝,是一种延时申请技术,可以提高整机内存的使用效率!

1.7 fork常规用法

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

1.8 fork调用失败的原因

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

2.进程终止

进程终止时,操作系统本质上释放进程申请的相关内核数据结构和对应的代码和数据(本质上是释放资源)

2.1 进程退出场景

进程终止的常见方式?

        a. 代码跑完,结果正确

        b. 代码跑完,结果错误

        c. 代码没有跑完,进程崩溃(信号部分)

指针a和b方式,涉及问题:

main函数的返回值?main函数返回值的意义是什么?return 0; 含义是什么?为什么总是0?其他值是否可以?

main函数的返回值并不是总是0,返回0表示程序sucess,非0表示运行结果不正确。

意义一:main函数的返回值适用于返回上一级进程,用来评判该进程执行结果用的,为进程的退出码。

意义二:非零值有无数个,不同的非零值就可以标识不同的错误原因!在我们程序运行结束之后,结果不正确时,根据返回码,方便定位错误的原因细节!

2.2 strerror函数—返回描述错误号的字符串

#include <string.h>char *strerror(int errnum);int strerror_r(int errnum, char *buf, size_t buflen);/* XSI-compliant */char *strerror_r(int errnum, char *buf, size_t buflen);/* GNU-specific */
#include<unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include<string.h>
int main(){printf("Hello world! pid = %d, ppid = %d\n", getpid(), getppid());for(int i = 0; i < 150; ++i){printf("%d: %s\n", i, strerror(i));}return 0;
}

2.3 进程常见退出方法

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

1. 从main返回

2. 调用exit

3. _exit

异常退出:

ctrl + c,信号终止

2.4 _exit函数和exit函数

_exit函数是系统提供的系统调用接口:

#include <unistd.h>
void _exit(int status);参数:status 定义了进程的终止状态,父进程通过wait来获取该值

说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。

exit函数是C语言库提供的接口:

#include <unistd.h>
void exit(int status);

exit 或者 _exit 在任何地方调用,都表示直接终止进程!!!

Linux提供了系统调用接口_exit()函数在unistd.h头文件中,而C的库函数exit底层实则封装了系统调用接口_exit:

  1. 执行用户通过 atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit

测试代码:

int main()
{printf("hello");exit(0);
}
运行结果:
[root@localhost linux]# ./a.out
hello[root@localhost linux]#
int main()
{printf("hello");_exit(0);
}
运行结果:
[root@localhost linux]# ./a.out
[root@localhost linux]#

分析图:

 

 2.5 return退出

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

有且只有在main函数内,return语句,就是终止进程的!return退出码!

3. 进程等待

3.1 进程等待必要性

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对, 或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

3.2 进程等待方法

wait方法:

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:成功返回被等待进程pid,失败返回-1。
参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:当正常返回的时候waitpid返回收集到的子进程的进程ID;如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:pid:Pid=-1,等待任一个子进程。与wait等效。Pid>0.等待其进程ID与pid相等的子进程。status:WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)options:WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进
程的ID。
  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退 出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。
  • option默认为0,表示阻塞等待,status为输出型参数
  • waitpid(pid, NULL, 0) == wait(NULL)

3.3 获取子进程status

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特 位):

 测试代码:

#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main()
{pid_t pid;if ( (pid=fork()) == -1 )perror("fork");exit(1);if ( pid == 0 ){//子进程休眠20秒,会变成僵尸进程//需要父进程等待回收sleep(20);exit(10);} else {//父进程int st;int ret = wait(&st);//阻塞等待if ( ret > 0 && ( st & 0X7F ) == 0 ){ // 正常退出//子进程退出码printf("child exit code:%d\n", (st>>8)&0XFF);} else if( ret > 0 ) { // 异常退出//子进程退出信号printf("sig code : %d\n", st&0X7F );}}return 0;
}

测试结果:

[root@localhost linux]# ./a.out #等20秒退出child exit code:10 [root@localhost linux]# ./a.out #在其他终端kill掉sig code : 9

进程异常退出,或者崩溃,本质是操作系统杀掉了你的进程!!!

操作系统如何杀掉进程呢!本质是通过发送信号的方式!

 父进程通过wait或者waitpid可以拿到进程的退出结果,为什么要用wait/waitpid函数呢!直接全局变量不行吗??

答案是不行,因为进程具有独立性,其创建子进程虽然具有相同虚拟内存地址,但是数据会发生写时拷贝,父进程无法获取到,况且如果是信号,更是无法获取!

既然进程具有独立性,进程退出码,不也是子进程的数据吗??父进程又凭什么拿到呢??wait/waitpid究竟干了什么呢??

因为wait、waitpid是系统调用接口,是系统创建的结构,可以访问内核,本质便是读取子进程的task_struct结构,此结构肯定包含了对应的信号码和退出码!

3.4 进程阻塞等待和非阻塞等待

options:WNOHANG选项,代表父进程非阻塞等待!本质为了避免魔术数字#define WNOHANG 1;

Linux C语言写的 -> 系统调用接口 -> OS自己提供的接口 ->就是C语言函数 -> 系统提供的一般大写的标记位 WNOHANG,其实就是宏定义!

WNOHANG —— Wait No Hang(等待过程中没夯住),夯住本质就是指这个进程没有被CPU调度,要么是在阻塞队列,要么是在等待被调度!

3.5 waitpid系统调用接口分析

阻塞等待和非阻塞等待,一般都是在内核中阻塞,等待被唤醒,如scanf和cin,底层必定封装了系统调用(阻塞等待),父进程通过调用waitpid来进行等待,如果子进程没有退出,waitpid这个系统调用,立马返回(非阻塞)

3.6 阻塞等待代码和基于非阻塞调用的轮询检测方案

阻塞等待:

#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
int main(){pid_t id = fork();if(id < 0){perror("fork");exit(1);//表示进程运行完毕,结果不正确}else if(id == 0){//子进程int cnt = 5;while(cnt){printf("cnt: %d, 我是子进程, pid : %d, ppid : %d\n", cnt, getpid(), getppid());sleep(1);cnt--;}exit(111);}else{//父进程printf("我是父进程, pid = %d, ppid = %d \n",getpid(), getppid());int status = 0;pid_t ret = waitpid(id, &status, 0);//printf("获取子进程退出信号: %d \n获取子进程退出码 %d\n", status & 0x7F,(status >> 8) & 0xFF);//pid_t ret = wait(NULL);//阻塞方式进行等待if(WIFEXITED(status)){printf("等待进程成功, ret = %d, 子进程退出码:%d\n", ret, WEXITSTATUS(status));}}return 0;
}

基于非阻塞调用的轮询检测方案:

#include<iostream>
#include<vector>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>typedef void(*handler_t)();
std::vector<handler_t> handlers;void fun_one(){printf("这是一个临时任务1\n");
}
void fun_two(){printf("这是一个临时任务2\n");
}void Load(){handlers.push_back(fun_one);handlers.push_back(fun_two);
}int main(){pid_t id = fork();if(id == 0){//子进程int cnt = 5;while(cnt--){printf("我是子进程:%d\n", cnt);sleep(1);}exit(11);}else{//父进程int quit = 0;while(!quit){int status = 0;int res = waitpid(id, &status, WNOHANG);if(res > 0){printf("进程等待成功,退出状态码:%d\n", WEXITSTATUS(status));quit = 1;}else if(res == 0){printf("等待子进程退出,处理其他事情中......\n");if(handlers.empty()){Load();}for(auto e : handlers){e();}}else{printf("进程等待错误\n");quit = 1;}sleep(1);}}return 0;
}

4. 进程程序替换

4.1 替换原理

fork之后,父子进程各自执行代码的一部分—如果子进程就想执行一个全新的程序呢?

进程的程序替换,来完成这个功能!

程序替换,是通过特定的接口,加载磁盘上的一个全新的程序(代码和数据),加载到调用进程的地址空间中!使得子进程拥有自己的代码!

 

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数 以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动 例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

进程替换的本质是加载新的代码和数据到内存,改变子进程页表的映射关系,完成替换

4.2 替换函数

其实有六种以exec开头的函数,统称exec函数:

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
//使用可变参数列表

实际Linux提供了七个替换函数!额外一个

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

4.3 函数解释

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

4.4 命名理解

这些函数原型看起来很容易混,但只要掌握了规律就很好记。

l(list) : 表示参数采用列表

v(vector) : 参数用数组

p(path) : 有p自动搜索环境变量PATH

e(env) : 表示自己维护环境变量

 exec调用举例如下:

#include <unistd.h>
int main()
{char* const argv[] = { "ps", "-ef", NULL };char* const envp[] = { "PATH=/bin:/usr/bin", "TERM=console", NULL };execl("/bin/ps", "ps", "-ef", NULL);// 带p的,可以使用环境变量PATH,无需写全路径execlp("ps", "ps", "-ef", NULL);// 带e的,需要自己组装环境变量execle("ps", "ps", "-ef", NULL, envp);execv("/bin/ps", argv);// 带p的,可以使用环境变量PATH,无需写全路径execvp("ps", argv);// 带e的,需要自己组装环境变量execve("/bin/ps", argv, envp);exit(0);
}

事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在 man手册第3节。这些函数之间的关系如下图所示。

下图exec函数族 一个完整的例子:

 

4.5 实现简易shell

用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左 向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结 束。

 然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。 所以要写一个shell,需要循环以下过程:

  1. 获取命令行
  2. 解析命令行
  3. 建立一个子进程(fork)
  4. 替换子进程(execvp)
  5. 父进程等待子进程退出(wait)

 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>#define NUM 1024
#define SIZE 32
char cmd_line[NUM];void dealStr(char* str, char** argv) {char* dealstr = NULL;const char* sep = " ";size_t i = 0;for (dealstr = strtok(str, sep); dealstr != NULL; dealstr = strtok(NULL, sep)) {if (i < SIZE) {argv[i] = dealstr;i++;}}if(strcmp(argv[0], "ls") == 0){argv[i] = (char*)"--color=auto";}argv[++i] = NULL;
}//shell运行原理:通过让子进程执行命令,父进程等待&&解析命令
int main(){while(1){//1.打印提示信息printf("[root@localhost myshell]#");fflush(stdout);//2.获取用户输入memset(cmd_line, '\0', sizeof cmd_line);char *g_argv[SIZE] = { NULL };if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL){continue;}cmd_line[strlen(cmd_line) - 1] = '\0';//printf("echo:%s\n", cmd_line);//3.命令行字符串分割dealStr(cmd_line, g_argv);     //4.TODO 内置命令,让父进程(shell)自己执行的命令,叫做内置命令,内建命令// 内建命令本质就是shell中的一个函数调用if(strcmp("cd", g_argv[0]) == 0){//not child execute, father executeif(g_argv[1] != NULL)chdir(g_argv[1]);continue;}//5.forkpid_t id = fork();if(id < 0){perror("fork");exit(1);}else if(id == 0){execvp(g_argv[0], g_argv);exit(1);}int status = 0;pid_t ret = waitpid(id, &status, 0);if(ret > 0){printf("exit code:%d -> result:%s\n",WEXITSTATUS(status), strerror(WEXITSTATUS(status)));}else{printf("wait fail!\n");exit(1);}}//end whilereturn 0;
}

5. 函数和进程之间的相似性:

exec/exit就像call/return

一个C程序有很多函数组成。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信。 这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux鼓励将这种应用于程 之内的模式扩展到程序之间。

一个C程序可以fork/exec另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过exit(n)来 返回值。调用它的进程可以通过wait(&ret)来获取exit的返回值。

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

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

相关文章

解决问题:python PermissionError: [WinError 5]拒绝访问

重要&#xff1a;关闭PyCharm Community Edition 2022.3等与python相关的编程程序找到按照python解释器的位置python->右键>属性>安全->点击组或用户名"中的Users->编辑点击"组或用户名"中的Users->把"完全控制"打钩->应用->…

Servlet文件的下载

第一种方法直接在前端使用超链接&#xff0c;也就是a标签 浏览器不能识别会直接下载&#xff08;像压缩文件不能直接下载&#xff09;&#xff0c;浏览器能识别&#xff0c;想要下载加一个download属性。download可以不写任何信息。 首先在web下建一个文件&#xff0c;放需要…

在Windows 10和11中恢复已删除的照片

可以在Windows 10或11上恢复已删除的照片吗&#xff1f; 随着技术的发展&#xff0c;越来越多的用户习惯在电子设备上存储照片。如果这些照片被删除&#xff0c;可能会给用户带来重大损失。当照片丢失时&#xff0c;您可能会想是否可以恢复已删除的照片&#xff1f; …

Kafka原理剖析

一、简介 Kafka是一个分布式的、分区的、多副本的消息发布-订阅系统&#xff0c;它提供了类似于JMS的特性&#xff0c;但在设计上完全不同&#xff0c;它具有消息持久化、高吞吐、分布式、多客户端支持、实时等特性&#xff0c;适用于离线和在线的消息消费&#xff0c;如常规的…

内网隧道代理技术(十五)之 Earthworm的使用(二级代理)

Earthworm的使用(二级代理) 本文紧接着上一篇文章继续讲解Earthworm工具的使用 (二级代理)正向连接 二级正向代理发生在如下的情况: 1、Web服务器在公网,黑客可以直接访问 2、B机器在内网,黑客不能直接访问 3、Web服务器可以访问内网机器B 4、内网机器B可以访问公司…

ARM将常数加载到寄存器方法之LDR伪指令

一、是什么&#xff1f; LDR Rd,const伪指令可在单个指令中构造任何32位数字常数,使用伪指令可以生成超过MOV和MVN指令 允许范围的常数. 实现原理: (1)如果可以用MOV或MVN指令构造该常数,则汇编程序会生成适当的指令 (2)如果不能用MOV或MVN指令构造该常数,则汇编程序会执行下列…

【UE5】快速认识入门

目录 &#x1f31f;1. 快速安装&#x1f31f;2. 简单快捷键操作&#x1f31f;3. 切换默认的打开场景&#x1f31f;4. 虚幻引擎术语 &#x1f31f;1. 快速安装 进入Unreal Engine 5官网进行下载即可&#xff1a;UE5 &#x1f4dd;官方帮助文档 打开后在启动器里创建5.2.1引擎…

Vue2 第七节 Vue监测数据更新原理

&#xff08;1&#xff09;Vue会监视data中所有层次的数据 &#xff08;2&#xff09;如何监测对象中的数据 通过setter实现监视&#xff0c;且要在new Vue时传入要监测的数据对象中后追加的属性&#xff0c;Vue默认不做响应式处理如果要给后添加的属性做响应式&#xff0c;使…

【雕爷学编程】MicroPython动手做(18)——掌控板之声光传感器2

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

小研究 - 基于解析树的 Java Web 灰盒模糊测试(二)

由于 Java Web 应用业务场景复杂, 且对输入数据的结构有效性要求较高, 现有的测试方法和工具在测试Java Web 时存在测试用例的有效率较低的问题. 为了解决上述问题, 本文提出了基于解析树的 Java Web 应用灰盒模糊测试方法. 首先为 Java Web 应用程序的输入数据包进行语法建模创…

【C++】模板

前言 在我们平时的代码中经常会有不同类型的变量去执行效果差不多的函数。比如&#xff1a;swap(交换)&#xff0c;sort(排序)。这些函数里其实会有大部分重复的段落&#xff0c;在这种情况下我们会使用重载函数&#xff0c;但是函数重载会有如下的问题&#xff1a; 1. 重载的函…

测试开源C#人脸识别模块ViewFaceCore(4:口罩检测、性别预测、年龄预测)

ViewFaceCore模块中的MaskDetector类支持识别人脸是否戴了口罩或有遮挡&#xff0c;主要调用PlotMask函数执行口罩检测操作&#xff0c;其函数原型如下所示&#xff1a; PlotMaskResult PlotMask<T>(T image, FaceInfo info)public class PlotMaskResult{//// 摘要:// …

RabbitMQ 教程 | 第2章 RabbitMQ 入门

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

VMware Linux 可视化增加磁盘

1、VMware 增加磁盘 2、disks挂载磁盘 此处我挂载的是20G磁盘&#xff0c;截图只是用5G的做过程演示例子。 3、验证挂载磁盘

动手学深度学习v2笔记 —— 线性回归 + 基础优化算法

二 动手学深度学习v2 —— 线性回归 基础优化算法 目录: 线性回归基础优化方法 1. 线性回归 总结 线性回归是对n维输入的加权&#xff0c;外加偏差使用平方损失来衡量预测值和真实值的差异线性回归有显示解线性回归可以看作是单层神经网络 2. 基础优化方法 梯度下降 小批量…

Spring的创建及使用

文章目录 什么是SpringSpring项目的创建存储Bean对象读取Bean对象getBean()方法 更简单的读取和存储对象的方式路径配置使用类注解存储Bean对象关于五大类注解使用方法注解Bean存储对象Bean重命名 Bean对象的读取 使用Resource注入对象Resource VS Autowired同一类型多个bean对…

echart折线图,调节折线点和y轴的间距(亲测可用)

options代码&#xff1a; options {tooltip: {trigger: axis, //坐标轴触发&#xff0c;主要在柱状图&#xff0c;折线图等会使用类目轴的图表中使用。},xAxis: {type: category,//类目轴&#xff0c;适用于离散的类目数据&#xff0c;为该类型时必须通过 data 设置类目数据。…

iOS开发-启动页广告实现

iOS开发-启动页广告实现 启动页广告实现是一个非常常见的广告展示模式。 就是在启动时候显示广告&#xff0c;之后点击跳转到广告页面或者其他APP。 一、实现启动页广告 启动页广告控件实现&#xff0c;将View放置在keyWindow上&#xff0c;显示广告图片&#xff0c;点击广告…

Pytorch(二)

一、分类任务 构建分类网络模型 必须继承nn.Module且在其构造函数中需调用nn.Module的构造函数无需写反向传播函数&#xff0c;nn.Module能够利用autograd自动实现反向传播Module中的可学习参数可以通过named_parameters()返回迭代器 from torch import nn import torch.nn.f…