【Linux学习】日积月累——进程控制

一、进程创建

1.1 fork函数的认识

#include<unistd.h>
pid_t fork(void);
返回值:自进程返回0,父进程返回子进程PID,出错返回-1
  • 进程调用fork,当控制转移到内核中的fork代码后,内核做:
  • 分配新的内存块和内核数据结构给子进程;
  • 将父进程部分数据结构内容拷贝至子进程;
  • 添加子进程到系统进程列表中;
  • fork返回,开始调度器调度。

image-20230723162019176

图1 fork创建子进程

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

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<errno.h>
#include<stdlib.h>

int main(){
    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;
}

调试结果:

image-20230723165613647

图2 输出结果

  从图2中可以看到:进程2981打印before消息,然后它有打印after。另一个after消息由2982打印的。注意到进程2982没有打印before,见图3:

image-20230723170343389

图3 fork创建的子进程执行

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

1.2 写时拷贝

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

image-20230723164131921

图4 写时拷贝
注意:写时拷贝的本质是按需申请资源的策略。

1.3 fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的段码段。例如,父进程等待客户端请求,生成子进程来处理请求;

  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

1.4 fork调用失败原因

  • 系统中有太多的进程;

  • 实际用户的进程数超出了限制。

二、进程终止

2.1 进程退出场景

  • a.正常执行完(1.结果正确;2.结果不正确);

  • b.崩溃了(进程异常)【具体在进程信号处详细说明】 – 崩溃的本质:进程因为某些原因,导致进程收到来自操作系统的信号(kill -9)。

  在main()函数中,return 0;中0是进程的退出码,表示正常执行完了(1.结果正确(0);2.结果不正确(!0非零;1,2,3,4 --> 表示不同原因))-> 供用户进行进程退出健康状态的判定。

image-20230608221525174

图5 进程退出码查看
  在图5中,`echo $?`只会保留当前最近一次执行的进程的退出码。下列代码执行打印CentOS7中,所有的程序退出码:
#include<stdio.h>
#include<string.h>
int main(){
    for(int i = 0; i <= 200; i++){
        printf("%d : %s\n", i, strerror(i));
        //char* strerror(int errnum); --> #include<string.h>
    }
    return 0;
}

image-20230723223233746

图6 遍历进程退出码

  上述代码执行的是遍历Linux系统(主要指CentOS7)中进程退出码,具体如图6所示,有(0~133)即134个。

进程退出:就是操作系统内少了一个进程,操作系统就要释放进程对应的内核数据结构+代码和数据(如果有独立的)。

2.2 进程常见退出方法

2.2.1 常见的三种方法

  • main( )函数return,其他函数return 仅仅代表该函数返回 -> 进程执行,本质是main执行流执行!
  • exit()函数退出 – C标准库函数调用
  • _exit() – 系统函数调用

2.2.2 方法的刨析

exit(int code){
	//code代表就是进程的退出码,等价于main(){return xxx};
	//冲刷缓冲区
	_exit(code);
}

在代码的任意地方调用该函数都表示进程退出!,具体下列代码所示:

#include<stdlib.h>

int main(){
    for(int i = 0; i <= 140; i++){
        printf("%d : %s\n", i, strerror(i));
        exit(123);
    }
    return 0;
}

image-20230723224115899

图7 遍历进程退出码
综合return和exit的案例:
int add_to_top(int top){
    printf("enter add_to_top\n");
    int sum = 0;
    for(int i = 0; i <= top; i++){
        sum += i;
    }
    exit(213);
    printf("out add_to_top\n");
    return sum;
}

int main(){
    int result = add_to_top(100);
    if(result == 5050){
        return 0;
    }else{
        return 11;//计算结果不正确
    }
}

image-20230723225403739

图8 遍历进程退出码

注意:exit()函数退出,在代码的任意地方调用该函数都表示进程退出。

_exit()函数,貌似等价于exit(),但不会清理缓冲区

_exit(int status)

exit()函数的调用举例:

int main(){
    printf("welcome to TU Berlin\n");//输出缓冲区
    sleep(2);
    exit(107);//关闭文件+冲刷缓冲区
    return 0;
}

image-20230723230717191

图9 exit()函数调用调试结果

_exit()函数的调用举例:

int main(){
    printf("welcome to TU Berlin");//输出缓冲区
    sleep(2);
    _exit(108);//干掉进程,不会对缓冲区数据做任何刷新

    return 0;
}

image-20230723230755890

图10 _exit()函数调用调试结果
exit(int code){
	//冲刷缓冲区
	_exit(code);
}

这里我推荐使用exit()函数,上述代码可知exit()函数是_exit()的封装,但_exit()函数退出时没有刷新缓冲区。那么缓冲区在哪里?这个缓冲区不在操作系统内部,而是在C库。

异常退出

  • ctrl + c,信号终止

image-20230608235516607

图11 进程的执行
./mytest  //mytest变成一个子进程,父进程是bash

三、进程等待

3.1 进程等待必要性

  • 子进程退出,若父进程不管不顾就会造成“僵尸进程”,从而引起内存泄漏
  • 另外,进程一旦变成僵尸进程,那就刀枪不入,kill -9也无能为力,因为谁也无法杀死一个已死的进程;
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如子进程运行完成,结果正确还是错误,亦或是否正常退出;
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

image-20230723233631825

图12 进程资源等待

等待本质就是通过系统调用,获取子进程退出码或退出信号的方式。

3.2 进程等待的方法

3.2.1 wait方法

#include<sys/types.h>

#include<sys/wait.h>

pid_t wait(int *status);

返回值:成功返回被等待进程pid,失败则返回-1.

参数:输出型参数,获取子进程退出状态,不关心则可设置成NULL.

3.2.2 waitpid方法

image-20230723235826388

图13 waitpid()函数

返回值:

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

注意:status是输出型参数。

参数:

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,子进程存在且正常运行,则进程可能阻塞;

若不存在该子进程,则立即出错返回。

image-20230802125556238

图14 父进程阻塞,在wait子进程退出后继续

3.2.3 获取子进程status

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

image-20230724001354882

图15 退出码与退出信号

  图15中,0表示没有收到信号,正常退出 -> 退出码(0 1 2…)。下列的实例1为wait方法的使用:

int main(){
    pid_t id = fork();
    if(id == 0){
        //子进程
        int cnt = 5;
        while(cnt){
            printf("我是子进程, 我还活着呢, 我还有%ds, PID:%d, PPID: %d\n",cnt--, getpid(), getppid());
            sleep(1);
        }
        exit(0);
    }
    sleep(10);
    //父进程
    pid_t ret_id = wait(NULL);
    printf("我是父进程, 等待子进程成功, PID: %d, PPID: %d, ret_id: %d\n",getpid(), getppid(), ret_id);
    sleep(5);
}

image-20230724003338528

图16 wait()函数的使用

实例2为退出码与退出信号的simulate:

int main(){
    pid_t id = fork();
    if(id == 0){
        //子进程
        int cnt = 5;
        while(cnt){
            printf("我是子进程, 我还活着呢, 我还有%ds, PID:%d, PPID: %d\n",cnt--, getpid(), getppid());
            sleep(1);
        }
        exit(0);
    }
    sleep(10);
    //父进程
    int status = 0;
    pid_t ret_id = waitpid(id, &status, 0);
    printf("我是父进程, 等待子进程成功, PID: %d, PPID: %d, ret_id: %d, child exit status: %d, child exit signal: %d\n",\
    getpid(), getppid(), ret_id, (status>>8)&0xFF, status & 0x7F);
    sleep(5);
    // return 0;
}

image-20230724003144010

图17 waitppid()函数使用

父进程在wait的时候,若子进程没有退出,父进程只能一直在调用waitpid进行等待(阻塞等待)。

四、进程程序替换

4.1 替换原理

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

image-20230802132202121

图18 进程替换原理

4.2 替换函数

将以exec开头的函数,统称为exec函数:

#include<unistd.h>
int execl(const char* path, const char* arg, …);
int execv(const char* path, char* const argv[]);

int execlp(const char* file, const char* arg, …);
int execvp(const char* file, char* const argv[]);

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

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

image-20230802173401488

图19 exec函数

4.2.1 函数解释

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

4.2.2 命名理解

这些函数原型看起来很容易混,若掌握规律则很好记忆:

  • l(list): 表示参数采用列表
  • v(vector): 参数用数组
  • p(path): 有p自动搜索环境变量PATH
  • e(env): 表示自己维护环境变量
函数名参数格式是否带路径是否使用当前环境变量
execl列表NO
execlp列表YES
execle列表NO不是,须自己组装env
execv数组NO
execvp数组YES
execve数组NO不是,须自己组装env

4.3 替换函数案例

4.3.1 execl调用举例

int main(){
    pid_t id = fork();
    assert(id >= 0);
    (void)id;

    if(id ==0){
        printf("我是子程序, pid: %d, ppid: %d\n", getpid(), getppid());
        execl("/bin/ls", "ls", "-a", "-l", NULL);//必须NULL结束
        //执行程序替换,新的代码和数据就被加载了,后续的代码属于旧代码,直接被替换,没有机会执行了
        printf("子程序还在呢!\n");
    }
    sleep(5);
    while(1){
        int status = 0;
        pid_t ret_id = waitpid(id, &status, WNOHANG);
        if(ret_id < 0){
            printf("waitpid error!\n");
            exit(0);
        }else if(ret_id == 0){
            printf("子程序还活着呢!\n");
            continue;
        }else{
            printf("我是父进程, 我的pid: %d, child exit code: %d, child exit signal: %d\n",\
                getpid(), (status>>8)&0xFF, status & 0x7F);
            break;
        }
    }
}

image-20230801113324673

图20 execl替换函数

注意execl,如果替换成功,不会有返回值;如果替换失败,一定返回值 。

4.3.2 execv调用举例

int main(){
    pid_t id = fork();
    assert(id >= 0);
    (void)id;

    if(id == 0){

        printf("我是子程序, pid: %d, ppid: %d\n", getpid(), getppid());
        char* const myargv[] = {
            "ls",
            "-a",
            "-n",
            NULL
        };
        execv("/bin/ls", myargv);//命令ls -an替换子进程
        printf("you failed!\n");
    }
    sleep(5);
    while(1){
        int status = 0;
        pid_t ret_id = waitpid(id, &status, WNOHANG);
        if(ret_id < 0){
            printf("waitpid error!\n");
            exit(0);
        }else if(ret_id == 0){
            printf("子程序还活着呢!\n");
            continue;
        }else{
            printf("我是父进程, 我的pid: %d, child exit code: %d, child exit signal: %d\n",\
                getpid(), (status>>8)&0xFF, status & 0x7F);
            break;
        }
    }
}

image-20230801114156242

图21 execv替换函数

4.3.3 execlp调用举例

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

p表示path(也是相对路径),当我们执行指定程序的时候,只需要指定程序名即可,系统会自动在环境变量PATH中进行查找。

int main(){
    pid_t id = fork();
    assert(id >= 0);
    (void)id;

    if(id == 0){
        //child
        printf("我是子进程, PID: %d\n", getpid());
        execlp("ls", "ls", "-a", "-i", NULL);//命令ls -ai来替换子进程
        printf("you fail\n");
    }
    //father
    sleep(5);
    int status = 0;
    printf("我是父进程, PID: %d\n", getpid());
    waitpid(id, &status, 0);
    printf("child exit code: %d\n", WEXITSTATUS(status));
}

image-20230801114824467

图22 execlp替换函数

4.3.4 execvp调用举例

int execvp(const char* file, char* const argv[]);

execvp中p就不需要绝对路径

int main(){
    pid_t id = fork();
    if(id == 0){
        //子进程
        printf("我是子进程, PID: %d\n", getpid());
        char* const myargv[] = {
            "ls",
            "-a",
            "-l",
            "-n",
            NULL
        };
        execvp("ls",myargv);
        printf("you failed\n");
    }
    //父进程
    sleep(5);
    int status = 0;
    printf("我是父进程, PID: %d\n", getpid());
    waitpid(id, &status, 0);
    printf("child exit code: %d\n", WEXITSTATUS(status));
    return 0;
}

image-20230801115044943

图23 execvp替换函数

4.3.5 execl替换的单个子进程

int main(){
    pid_t id = fork();
    if(id == 0){
        //子进程
        printf("我是子进程, PID: %d\n", getpid());
        execl("./exec/otherproc","otherproc",NULL);
        printf("you failed\n");
    }
    //父进程
    sleep(5);
    int status = 0;
    printf("我是父进程, PID: %d\n", getpid());
    waitpid(id, &status, 0);
    printf("child exit code: %d\n", WEXITSTATUS(status));
    return 0;
}

image-20230801115618992

图24 execl替换单个子进程

五、进程控制项目实战——模拟shell

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/types.h>
#include<sys/wait.h>

#define MAX 1024
#define ARGC 64
#define SEP " "

int split(char* commandstr, char* argv[]){
    assert(commandstr);
    assert(argv);

    argv[0] = strtok(commandstr, SEP);
    if(argv[0] == NULL){
        return -1;
    }

    int i = 1;
    while(1){
        argv[i] = strtok(NULL,SEP);
        if(argv[i] == NULL){
            break;
        }
        i++;
    }
    return 0;
}

void debugPrint(char* argv[]){
    for(int i = 0; argv[i]; i++){
        printf("[%d]: %s\n", i, argv[i]);
    }
}

void showEnv(){
    extern char** environ;
    for(int i = 0; environ[i]; i++){
        printf("%d: %s\n", i, environ[i]);
    }
}

int main(){
    int last_exit = 0;

    char myenv[32][256];
    int env_index = 0;
    while(1){
        char commandstr[MAX] = {0};
        char* argv[ARGC] = {NULL};
        printf("[ChuHsiang@HuaWeiCloud]# ");
        fflush(stdout);
        // sleep(100);

        char* s = fgets(commandstr, sizeof(commandstr), stdin);
        assert(s);
        (void)s;

        commandstr[strlen(commandstr) - 1] = '\0';

        int n = split(commandstr,argv);
        if(n != 0){
            continue;
        }
        // debugPrint(argv);
        //version 2 : 说明几个细节

        //cd .. /cd/ 等 bash自己执行的命令,称之为内建命令/内置命令  --> int chdir(const char *path);
        if(strcmp(argv[0], "cd") == 0){
            if(argv[1] != NULL){
                //说到底,cd命令,重要的表现就如同bash自己调用了对应的函数
                chdir(argv[1]);
            }
            continue;//不会往下继续执行,回到while(1)重新开始
        }else if(strcmp(argv[0], "export") == 0){
            if(argv[1] != NULL){
                strcpy(myenv[env_index], argv[1]);
                putenv(myenv[env_index++]);
            }
            continue;
        }else if(strcmp(argv[0], "env") == 0){
            showEnv();
            continue;
        }else if(strcmp(argv[0], "echo") == 0){
            //echo $PATH
            const char* target_env = NULL;
            if(argv[1][0] == '$'){
                if(argv[1][1] == '?'){
                    printf("%d\n", last_exit);
                    continue;
                }else{
                    target_env = getenv(argv[1]+1);
                }
            }
            if(target_env != NULL){
                printf("%s=%s\n", argv[1]+1, target_env);
            }

            continue;
        }

        // else if(strcmp(argv[0], "export") == 0){
        //     if(argv[1] != NULL){
        //         putenv(argv[1]);
        //     }
        //     continue;
        // }

        if(strcmp(argv[0], "ls") == 0){
            int pos = 0;
            while(argv[pos]){
                pos++;
            }
            argv[pos++] = (char*)"--color=auto";
            argv[pos] = NULL;//比较安全的做法
        }

        //version 1
        pid_t id = fork();
        assert(id >= 0);
        (void)id;

        if(id == 0){
            //child
            // execvp(argv[0], argv);
            execvp(argv[0], argv);
            exit(1);
        }
        int status = 0;
        pid_t ret_id = waitpid(id, &status, 0);
        if(ret_id > 0){
            last_exit = WEXITSTATUS(status);
        }
    }
}

image-20230802174445975

图25 simulate of shell

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux ALSA音频工具aplay、arecord、amixer的使用方法

ALSA 是Advanced Linux Sound Architecture的缩写&#xff0c;先进的Linux音频架构&#xff0c;为Linux操作系统提供音频和MIDI功能。 aplay命令 aplay是播放命令。 rootimx6ul7d:~# aplay -h Usage: aplay [OPTION]... [FILE]...-h, --help help--version …

解决Hadoop审计日志hdfs-audit.log过大的问题

【背景】 新搭建的Hadoop环境没怎么用&#xff0c;就一个环境天天空跑&#xff0c;结果今天运维告诉我说有一台服务器磁盘超过80%了&#xff0c;真是太奇怪了&#xff0c;平台上就跑了几个spark测试程序&#xff0c;哪来的数据呢&#xff1f; 【问题调查】 既然是磁盘写满了&…

浅谈微服务异步解决方案

导言 异步是一种设计思想&#xff0c;不是设计目的&#xff0c;因此不要为了异步而异步&#xff0c;要有所为&#xff0c;有所不为。 异步不是『银弹』&#xff0c; 避免试图套用一个『异步框架』解决所有问题&#xff0c; 需要根据不同的业务特点或要求&#xff0c;选择合适的…

VSCode自定义闪烁光标

打开VSCode 组合键ctrlshiftp搜索"settings.json",打开User Settings 加上这一句 "editor.cursorStyle": "block","workbench.colorCustomizations": {"editorCursor.foreground": "#5c8fb1","terminalCurs…

IDEA中Git面板操作介绍 变基、合并、提取、拉取、签出

IDEA中Git面板操作介绍 变基、合并、提取、拉取、签出 面板介绍 变基、合并 提取、拉取 签出、Checkout 面板介绍 如图&#xff0c;在IDEA的Git面板中&#xff0c;仓库会分为本地仓库和远程仓库&#xff0c;代码仓库里面放的是各个分支。 分支前面的书签&#x1f516;标志…

JavaScript高级——ES6基础入门

目录 前言let 和 const块级作用域模板字符串一.模板字符串是什么二.模板字符串的注意事项三. 模板字符串的应用 箭头函数一.箭头函数是什么二.普通函数与箭头函数的转换三.this指向1. 全局作用域中的 this 指向2. 一般函数&#xff08;非箭头函数&#xff09;中的this指向3.箭头…

MongoDB文档--基本安装-linux安装(mongodb环境搭建)-docker安装(挂载数据卷)-以及详细版本对比

阿丹&#xff1a; 前面了解了mongodb的一些基本概念。本节文章对安装mongodb进行讲解以及汇总。 官网教程如下&#xff1a; 安装 MongoDB - MongoDB-CN-Manual 版本特性 下面是各个版本的选择请在安装以及选择版本的时候参考一下&#xff1a; MongoDB 2.x 版本&#xff1a…

ELK日志分析系统概述及部署

ELK 平台是一套完整的日志集中处理解决方案&#xff0c;将 ElasticSearch、Logstash 和 Kibana 三个开源工具配合使用&#xff0c;完成更强大的用户对日志的查询、排序、统计需求。 一、ELK概述 1、组件说明 ①ElasticSearch ElasticSearch是基于Lucene&#xff08;一个全文…

XML约束和解析

文章目录 概述使用场景语法dtd约束Schema约束解析DOM4j&#xff08;重点&#xff09; 概述 可扩展的标记性语言 使用场景 以前: 传输数据的媒介。 例如&#xff1a;微服务架构中&#xff0c;可以用xml文件进行多语言之间的的联系。 现在: 做配置文件 现在作为传输数据的媒介…

使用Gunicorn+Nginx部署Flask项目

部署-开发机上的准备工作 确认项目没有bug。用pip freeze > requirements.txt将当前环境的包导出到requirements.txt文件中&#xff0c;方便部署的时候安装。将项目上传到服务器上的/srv目录下。这里以git为例。使用git比其他上传方式&#xff08;比如使用pycharm&#xff…

Matlab Optimization Toolbox中的遗传算法工具包(GA)

matlab optimization 中使用了GA求解器 默认的是小于等于 找到GA 工具包 找到 APP选择 Optimization Tool 选择Solver ga - Genetic Algorithm 应用GA solver 定义适应度函数(Fitness function)与问题约束(Constraints) example one 优化函数 sin(x) 2 * cos(x)极其重要的…

浅谈3D隐式表示(SDF,Occupancy field,NeRF)

本篇文章介绍了符号距离函数Signed Distance Funciton(SDF)&#xff0c;占用场Occupancy Field&#xff0c;神经辐射场Neural Radiance Field&#xff08;NeRF&#xff09;的概念、联系与区别。 显式表示与隐式表示 三维空间的表示形式可以分为显式和隐式。 比较常用的显式表…

高并发与性能优化的神奇之旅

作为公司的架构师或者程序员&#xff0c;你是否曾经为公司的系统在面对高并发和性能瓶颈时感到手足无措或者焦头烂额呢&#xff1f;笔者在出道那会为此是吃尽了苦头的&#xff0c;不过也得感谢这段苦&#xff0c;让笔者从头到尾去探索&#xff0c;找寻解决之法。 目录 第一站…

让数据管理由繁至简的低代码开发平台

随着社会数字化能力的快速升级&#xff0c;各行各业正逐渐迈向数字化转型的新时代。尤其是AI的爆发&#xff0c;数据智能技术正在彻底改变着这个行业的面貌&#xff0c;随着越来越多的企业开始将人工智能、机器学习和大数据分析技术应用到其业务中&#xff0c;数据的价值正在得…

VirtualBox Ubuntu无法安装增强功能以及无法复制粘贴踩坑记录

在VirtualBox安装增强功能想要和主机双向复制粘贴&#xff0c;中间查了很多资料&#xff0c;终于是弄好了。记录一下过程&#xff0c;可能对后来人也有帮助&#xff0c;我把我参考的几篇主要的博客都贴上来了&#xff0c;如果觉得我哪里讲得不清楚的&#xff0c;可以去对应的博…

Vue.js2+Cesium 四、模型对比

Vue.js2Cesium 四、模型对比 Cesium 版本 1.103.0&#xff0c;低版本 Cesium 不支持 Compare 对比功能。 Demo 同一区域的两套模型&#xff0c;实现对比功能 <template><div style"width: 100%; height: 100%;"><divid"cesium-container"…

论文笔记:Adjusting for Autocorrelated Errors in Neural Networks for Time Series

2021 NIPS 原来的时间序列预测任务是根据预测论文提出用一阶自回归误差预测 一阶差分&#xff0c;类似于ResNet的残差思路&#xff1f;记为pred&#xff0c;最终的预测结果

Java:Map的getOrDefault()方法结果仍为null

1、问题 今天在工作中遇到一个问题&#xff0c;在一个通用的数据处理方法中&#xff0c;方法会从一个Map类型参数通过key里获取对象value&#xff0c;但方法的调用者并不都会传递value实例&#xff0c;若没有获取到value则需要初始化一个&#xff0c;处理方式是调用了Map的getO…

大牛练成记:用JavaScript徒手写出一个日期选择插件

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;本文已收录于专栏&#xff1a;100个JavaScript的小应用。 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收…

【C语言】初识指针

【C语言】初识指针 一、指针是什么&#xff1f;二、指针和指针类型1. 指针-整数2. 指针的解引用三、野指针1.野指针成因2 .如何规避野指针四、指针运算五、二级指针七、指针数组 &#x1f388;个人主页&#xff1a;库库的里昂&#x1f390;CSDN新晋作者&#x1f389;欢迎 &…