进程控制相关 API-创建进程、进程分离、进程退出、进程阻塞

news2025/1/16 16:46:21

进程控制相关 API

p.s 进程控制中的状态转换 相关 API,用户很少用到,在此不提。

一般来说,这些内核标准 API,在执行出错(可能是资源不够、权限不够等等)会返回负值(比如 -1),并设置 errno 值。

父进程创建子进程 fork()

在 Linux 中,为了创建一个子进程,父进程用系统调用 fork() 来创建子进程。fork() 其实就是把父进程复制了一份(子进程有自己的特性,比如标识、状态、数据空间(堆栈区和数据区)等(这些是子进程独有的);子进程和父进程共同使用程序代码、共用时间片(这些是共有的)等)。

通常在调用fork函数之后,程序会设计一个if选择结构。当PID等于0时,说明该进程为子进程,那么让它执行某些指令,比如说使用exec库函数(library function)读取另一个程序文件,并在当前的进程空间执行 (这实际上是我们使用fork的一大目的: 为某一程序创建进程);而当PID为一个正整数时,说明为父进程,则执行另外一些指令。由此,就可以在子进程建立之后,让它执行与父进程不同的功能。

pid_t fork();fork() 对子进程 返回 0,对父进程 返回 子进程的 ID,返回 小于 0 值为出错。

#include<stdio.h>
#include<unistd.h>
​
int main()
{
  int p_num = 0;
  int c_num = 0;
  int pid = fork();
  if(pid == 0) // 返回的pid为0为子进程
  {
        c_num++;
  }
  else
  {
        p_num++; // 返回的pid大于0为父进程
  }
  printf("p_num=%d, c_num=%d\n",p_num,c_num);
  printf("pid=%d\n",pid);
  return 0;
}
​
// 运行结果如下所示
p_num=1, c_num=0
pid=36101
p_num=0, c_num=1
pid=0

子进程总可以查询自己的 PPID 来知道自己的父进程是谁,这样,一对父进程和子进程就可以随时查询对方。

其它:

  • fork() 的 写时复制 概念,可网搜了解。即 用到某个资源时候才会复制,不需要修改的资源不会复制,尽量推迟高系统消耗的操作直到必要时才会执行。

  • vfork() 不常用,实现可能不会完全没问题,概念可网搜来了解。

进程分离 exec 族函数

通过 fork 后,子进程并没有和父进程独立开,用的是相同的代码。另外还有一个问题时,这个时候子进程的时间片是和父进程一分为二来共享的。为了彻底将父进程和子进程分离开来,就要用到一个系统调用 exec 族函数,这是读取另一个程序文件,并在当前的进程空间执行。当我们创建了一个进程之后,通常将子进程替换成新的进程映象,这可以用 exec 系列的函数来进行,且新进程与原进程有相同的 PID。

参考 Linux下exec函数族(execl,execv,execle,execve,execlp,execvp,fexecve)的使用和对比leumber的博客-CSDN博客exec和execv,exec系列函数(execl,execlp,execle,execv,execvp)使用_gauss的博客-CSDN博客 这个里面有每个 API 的使用例子,exec和execv区别 - CSDN。

  • 各个 API 原型:#include <unistd.h>

    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 execve(const char *path, char *const argv[], char *const envp[]);

    传入参数:path 参数表示你要启动程序的名称包括路径名;file 参数表示 要启动的 程序 / 文件 的文件名(系统从环境变量 PATH 里面寻找该程序,因此不用带路径全名);arg 参数表示启动程序所带的参数,一般第一个参数为要执行命令名,不是带路径且arg必须以NULL结束;

    返回值:成功返回0,失败返回-1。

  • exec 族函数名中 l 表示列表 list,v 表示数组。

    • execl、execlp、execle 将新程序的每个命令行参数都以一个单独的参数,这种参数列表以NULL结尾。

    • execv、execvp、execve 和 fexecve 则应先构造一个指向各参数的指针数组,然后将该数组地址作为参数传入。

  • exec 族函数名中 p 结尾表示函数第一个参数取 filename。

    • execlp、execvp与其他函数不同就是第一个参数取filename,用 PATH 环境变量寻找可执行文件,filename 既可以是文件路径加程序名,也可以是PATH环境变量下的 /sbin: /bin: /usr/bin: 即shell命令。

  • exec 族函数名中 e 结尾表示可以传递环境表信息 environ。

    • execle、execve、fexecve 可以传递一个指向环境字符串指针数组的指针。

// process.c
#include<stdio.h>
#include<unistd.h>
​
int main()
{
    int pid = fork();
    if(pid == 0)
    {
        execv("./test.o",NULL);  // test.o是一个经过编译的c语言文件,这里记得要放test.o的绝对路径
    }
    printf("This is parent process\n");
    return 0;
}
​
// test.c
#include<stdio.h>
int main()
{
    printf("This is child process");
    return 0;
}
​
// 运行结果如下所示
This is parent process
This is child process

exec 族函数的使用例子

/* exec.c */
#include <unistd.h>
main()
{
    char *envp[]={"PATH=/tmp","USER=lei","STATUS=testing",NULL}; /* 数组 必须以 NULL 做结尾 */
    char *argv_execv[]={"echo", "excuted by execv", NULL};
    char *argv_execvp[]={"echo", "executed by execvp", NULL};
    char *argv_execve[]={"env", NULL};
    if(fork()==0)
        if(execl("/bin/echo", "echo", "executed by execl", NULL)<0) /* 路径全名,传入参数写全,以NULL结尾 */
            perror("Err on execl");
    if(fork()==0)
        if(execlp("echo", "echo", "executed by execlp", NULL)<0) /* 只写执行程序的文件名,系统会去 PATH 环境变量寻找 */
            perror("Err on execlp");
    if(fork()==0)
        if(execle("/usr/bin/env", "env", NULL, envp)<0) /* 可传入环境变量 */
            perror("Err on execle");
    if(fork()==0)
        if(execv("/bin/echo", argv_execv)<0) /* 带 v 的就是 传入参数 以 指针数据(字符串数据)传入,其它与上面的 API 一样 */
            perror("Err on execv");
    if(fork()==0)
        if(execvp("echo", argv_execvp)<0)
            perror("Err on execvp");
    if(fork()==0)
        if(execve("/usr/bin/env", argv_execve, envp)<0)
            perror("Err on execve");
}
​
/* 执行 ./exec 后返回:
executed by execl
PATH=/tmp
USER=lei
STATUS=testing
executed by execlp
excuted by execv
executed by execvp
PATH=/tmp
USER=lei
STATUS=testing
*/

exec 族函数 的常见的错误返回(exec 返回 -1,并设置 errno 为以下的值):

  • 找不到文件或路径,此时errno被设置为ENOENT;

  • 数组argv和envp忘记用NULL结束,此时errno被设置为EFAULT;

  • 没有对要执行文件的运行权限,此时errno被设置为EACCES。

  • 等等,有很多种类型的错误返回。

更多要注意的地方:

  • 实际操作时, 一般在 调用 exec 函数之前 关闭所有已经打开的文件。也可以通过 fcntl() 让内核去完成。

进程的退出 return/exit()

参考 进程的几种退出机制_Leon_George的博客-CSDN博客,操作系统 — 进程的退出(exit)Dawn_sf的博客-CSDN博客exit 进程退出, exit函数及与return的区别_panda19881的博客-CSDN博客。

几种退出方式

  • 正常退出

    1. 在main()函数中执行return(renturn执行完后把控制权交给调用函数)。

    2. 调用exit()函数(exit执行完后把控制权交给系统)。

    3. 调用_exit()函数(同上)。

  • 异常退出

    1. 调用abort函数(exit是正常终止进程,abort是异常终止,突出在异常)。

    2. 进程收到某个信号,而该信号使程序终止。

exit()_exit()区别exit() 函数是在_exit()函数之上的一个封装,其会调用_exit(),并在调用之前先刷新流(stdin, stdout, stderr ...),即 把文件缓冲区的内容写回文件。exit在头文件stdlib.h中声明,而_exit()声明在头文件unistd.h中声明。使用 exit() 更安全。exit中的参数exit_code为0代表进程正常终止(即 exit(0);),若为其他值表示程序执行过程中有错误发生。

exitreturn 区别:如果return 或者exit出现在main函数中,两者的作用是一样(即 return 0;exit(0); 一样)。如果return出现在子程序中表示返回(仅意味着 退出/结束 其所在的函数或子进程),而exit出现在子进程中表示终止子进程。

但不管是哪种退出方式,系统最终都会执行内核中的某一代码。这段代码用来关闭进程所用已打开的文件描述符,释放它所占用的内存和其他资源。

父子进程终止的先后顺序不同会产生不同的结果

  • 子进程先于父进程终止,而父进程调用了wait函数:子进程退出并被父进程回收(好)

此时父进程会等待子进程结束。

  • 父进程先于子进程终止:子进程变孤儿进程(中)

此种情况就是我们前面所用的孤儿进程。当父进程先退出时,系统会让init进程接管子进程 。孤儿进程会被过继给init进程,init进程也就成了该进程的父进程。init进程负责该子进程终结时调用wait函数。init进程会在有子进程退出时调用wait函数。

  • 子进程先于父进程终止,而父进程又没有调用wait函数:子进程变僵死进程(坏)

此种情况子进程进入僵死状态,并且会一直保持下去直到系统重启。子进程处于僵死状态时,内核只保存进程的一些必要信息以备父进程所需。此时子进程始终占有着资源,同时也减少了系统可以创建的最大进程数。

因此要尽量避免这种情况发生,否则 僵死进程 不但占用着资源,而且可能越积累越多。一个糟糕的程序也完全可能造成子进程的退出信息滞留在内核中的状况(父进程不对子进程调用wait函数),这样的情况下,子进程成为僵尸(zombie)进程。当大量僵尸进程积累时,内存空间会被挤占。

僵死状态:一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放它仍占有的资源)的进程被称为僵死进程(zombie)。

僵死进程 和 孤儿进程 的释义:

总结:这三种进程退出,最好情况是 父进程调用 wait 正常回收 终止的子进程,其次是 父进程提前终止 / 子进程称为孤儿进程 / 子进程被过继给 init 进程 / init进程会在有子进程退出时调用wait函数,最坏是 父进程没有调用 wait 而子进程退出 / 子进程成为 僵死进程。

注册 进程退出时 调用的函数:#include <stdlib.h> int atexit (void (*function)(void)); ,注册的函数会在 调用 exit(); 或 从 main 退出 或 收到终止进程的信号(SIGTERM 或 SIGKILL)时候被调用一次。注册的函数内不能再调用 exit(),否则会引起无限递归。

进程的阻塞 wait()

处于运行状态的进程,在其运行过程中期待某一事件的发生,如等待键盘的输入、等待磁盘数据传输完成、等待其他进程发送信息,当被等待的时间未发生时,由进程自己执行阻塞原语,使自己的运行状态变为阻塞态。

即 休眠/阻塞 来 等待 信号(signal)父进程等待子进程退出(再回收其资源)

p.s 对于 等待 信号(signal)在 下面 进程间通讯(IPC) 里的 信号(Signal) 一节介绍。这里只讨论 父进程等待子进程退出(再回收其资源)。

引用 exec和execv区别 - CSDN(回过头意识到,我就相当于把格式整理地更漂亮了一遍。。)。

父进程 wait 阻塞来等待子进程退出(再回收其资源)

子进程终止时,内核会向其父进程 发送 SIGCHILD 信号。

当子进程终结时,它会通知父进程,并清空自己所占据的内存,并在内核里留下自己的退出信息(exit code,如果顺利运行,为0;如果有错误或异常状况,为>0的整数)。父进程在得知子进程终结时,有责任对该子进程使用wait系统调用(否则子进程就成了僵死进程,应当尽量避免)。这个wait函数能从内核中取出子进程的退出信息,并清空该信息在内核中所占据的空间。

#include <sys/types.h> /* 提供类型pid_t的定义 */ 
#include <sys/wait.h> 
pid_t wait(int *status);

进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何dump掉的毫不在意,只想把这个僵尸进程消灭掉,我们就可以设定这个参数为NULL。

如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。

例子:

/* wait1.c */
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
main()
{
    pid_t pc, pr;
    pc = fork();
    if(pc < 0)            /* 如果出错 */
        printf("error ocurred!\n");
    else if(pc == 0){    /* 如果是子进程 */ 
        printf("This is child process with pid of %d\n",getpid());
        sleep(10);       /* 睡眠10秒钟 */
    }
    else{                 /* 如果是父进程 */
        pr = wait(NULL); /* 在这里等待子进程的退出,并不在意其 exit 退出返回值 */
        printf("I catched a child process with pid of %d\n"),pr);
    }
    exit(0);
}

对 退出返回值做进一步分析的宏:

  • WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。(请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数--指向整数的指针status,而是那个指针所指向的整数)

  • WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出WEXITSTATUS(status)就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说,WIFEXITED返回0,这个值就毫无意义。

另一篇文章再讲一遍:

pid_t wait(int *status);,wait 系统调用会使父进程阻塞直到一个子进程结束或者是父进程接受到了一个信号。如果没有父进程没有子进程或者他的子进程已经结束了 wait 回立即返回。成功时(因一个子进 程结束)wait 将返回子进程的 ID,否则返回-1,并设置全局变量 errno。status 是子进程的 退出状态。子进程调用 exit / _exit 或者是 return 来设置这个值。为了得到这个值 Linux 定 义了几个宏来测试这个返回值。

另一篇文章具体讲几个宏的区别:

#include <sys/wait.h>
int WIFEXITED (status);
int WIFSIGNALED (status);
int WIFSTOPPED (status);
int WIFCONTINUED (status);
int WEXITSTATUS (status);
int WTERMSIG (status);
int WSTOPSIG (status);
int WCOREDUMP (status);

waitpid 阻塞等待特定 pid 的子进程退出

#include <sys/types.h> /* 提供类型pid_t的定义 */
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options);

waitpid() 参数说明:

  • pid:

    pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。

    pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。

    pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。

    pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

  • status:承接子进程的 exit 退出返回值,不用时填 NULL。

  • options:options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用 "|" 运算符把它们连接起来使用,如果我们不想使用它们,也可以把options设为0(不用时设为 0)。

    WNOHANG:即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。

    WUNTRACED:涉及到一些跟踪调试方面的知识,加之极少用到,有兴趣的读者可以自行查阅相关材料。

  • 返回值:

    waitpid的返回值比wait稍微复杂一些,一共有3种情况:

    1. 当正常返回的时候,waitpid返回收集到的子进程的进程ID;

    2. 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;

    3. 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

    当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD。

    子进程的结束状态返回后存于status,底下有几个宏可判别结束情况:

    • WIFEXITED(status)如果子进程正常结束则为非0值。

    • WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。

    • WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真。

    • WTERMSIG(status)取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。

    • WIFSTOPPED(status)如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。

    • WSTOPSIG(status)取得引发子进程暂停的信号代码。

其它 API

头文件:

#include <unistd.h>;
#include <pwd.h>;
#include <sys/types.h>;
  • pid_t getpid(void);,得到当前进程 pid。

    pid_t getppid(void);,得到当前进程的父进程的 pid。

  • 进程的实际用户、有效用户相关概念:

  • 改变实际用户 API:

  • 改变有效用户 API:

  • 得到用户 ID:

    uid_t getuid(void);uid_t geteuid(void);,分别得到进程的所有者用户的 ID 和 有效用户 ID。 gid_t getgid(void);git_t getegid(void);,分别得到组 ID 和有效组 ID。

  • 得到用户的更多信息:

    struct passwd { /* 这个结构体在 types.h 里面定义 */
        char *pw_name; /* 登录名称 */ 
        char *pw_passwd; /* 登录口令 */ 
        uid_t pw_uid; /* 用户 ID */ 
        gid_t pw_gid; /* 用户组 ID */ 
        char *pw_gecos; /* 用户的真名 */ 
        char *pw_dir; /* 用户的目录 */ 
        char *pw_shell; /* 用户的 SHELL */ 
    }; 
    struct passwd *getpwuid(uid_t uid); /* 返回 用户 ID 为 uid 的 用户信息的 struct passwd 结构体指针*/
  • sleep(x);,阻塞/延时 x 秒时间。

  • strerror(errno),返回一个指定的错误号的错误信息的字符串。

  • etc.

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

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

相关文章

Go实现LogAgent:海量日志收集系统【上篇——LogAgent实现】

Go实现LogAgent 项目架构图&#xff1a; 0 项目背景与方案选择 背景 当公司发展的越来越大&#xff0c;业务越来越复杂时&#xff0c;每个业务系统都有自己的日志。此时我们就应该将不同业务线的日志进行实时收集&#xff0c;存储到一个日志收集中心&#xff0c;最后再通过…

手写Mybatis:第7章-SQL执行器的定义和实现

文章目录 一、目标&#xff1a;SQL执行的定义和实现二、设计&#xff1a;SQL执行的定义和实现三、实现&#xff1a;SQL执行的定义和实现3.1 工程结构3.2 SQL执行实现的关系图3.3 执行器的定义和实现3.3.1 Executor 接口3.3.2 BaseExecutor 抽象基类3.3.3 SimpleExecutor 简单执…

编译工具:CMake(六) | 使用外部共享库和头文件

编译工具&#xff1a;CMake&#xff08;六&#xff09; | 使用外部共享库和头文件 步骤引入头文件搜索路径为 target 添加共享库 步骤 在/Compilation_tool/cmake 目录建立 t4 目录 建立src目录&#xff0c;编写源文件main.c&#xff0c;内容如下&#xff1a; #include <…

ModaHub魔搭社区——决胜大模型时代,算力、网络、向量数据库缺一不可

大模型应用场景日趋多样,需求也随着增加,进而倒逼着多元算力方面的创新,为满足AI工作负载的需求,采用GPU、FPGA、ASIC等加速卡的服务器越来越多。 根据IDC数据统计,2022年,中国加速服务器市场相比2019年增长44.0亿美元,服务器市场增量的一半更是来自加速服务器。 这意味…

shell bash中设置命令set

1 Preface/Foreword set命令用于shell脚本在执行命令时候&#xff0c;遇到异常的处理机制。 2 Usage 2.1 set -e 当执行命令过程中遇到异常&#xff0c;那么就退出脚本&#xff0c;不会往下执行其它命令。 #!/bin/bash #set -eroot GIT_TAG${CI_BUILD_TAG-NOTAG} GIT_REV…

MySQL创建用户时报错“Your password does not satisfy the current policy requirements“

MySQL创建用户时报错"Your password does not satisfy the current policy requirements" MySQL是一个流行的关系型数据库管理系统&#xff0c;它提供了许多安全性特性&#xff0c;其中之一是密码策略。在创建或更改用户密码时&#xff0c;MySQL会检查密码是否符合当…

2023开学礼《乡村振兴战略下传统村落文化旅游设计》许少辉农大图书馆

2023开学礼《乡村振兴战略下传统村落文化旅游设计》许少辉农大图书馆

HCIP---BGP协议

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 根据AS自治系统可以将动态路由协议划分为IGP和EGP协议。IGP协议是应用在AS内部&#…

手摸手2-springboot编写基础的增删改查

目录 手摸手2-springboot编写基础的增删改查创建controller层添加service层接口service层实现添加mapper层mapper层对应的sql添加扫描注解,对应sql文件的目录 手摸手2-springboot编写基础的增删改查 创建controller层 实现 test 表中的添加、修改、删除及列表查询接口&#x…

PHP8函数包含文件-PHP8知识详解

在php中&#xff0c;可以使用以下函数来包含其他文件&#xff1a;include()、include_once()、require()、require_once()。 1、include(): 包含并运行指定文件中的代码。如果文件不存在或包含过程中出现错误&#xff0c;将发出警告。 <?php include filename.php; ?>…

【前端demo】背景渐变动画

文章目录 效果过程代码htmlcss 其他demo 效果 效果预览&#xff1a;https://codepen.io/karshey/pen/OJrXZwQ 过程 注意&#xff0c;直接在body上加height:100%可能也会出现height为0的情况&#xff0c;这是需要令html的height:100% 代码 html <!DOCTYPE html> <…

面试题--从键盘输入网站到网页显示,之间发生了什么

文章目录 首先进入HTTP阶段协议栈阶段TCP阶段IP阶段MAC网卡交换机路由器抵达 首先进入HTTP阶段 1.解析对应的URL&#xff0c;访问一个对应的服务器xxx.com的一个文件index.html; 2 使用DNS查询对应的ip地址&#xff0c;通过DNS服务器进行查找 3 组装http报文&#xff0c;生成h…

成集云 | 多维表自动查询快递100信息 | 解决方案

源系统成集云目标系统 方案介绍 产品详情 维格表是一种新一代的团队数据协作和项目管理工具&#xff0c;它结合了可视化数据库、电子表格、实时网络协同、低代码开发技术四项功能&#xff0c;且支持API与可视化看板&#xff0c;操作简单&#xff0c;能提升中小企业的数字化生…

python网络编程

文章目录 socket套接字客户端/服务模型linux文件描述符fdLinux网络IO模型详解网络服务器Apache VS Nginx生产者消费者-生成器版客户端/服务端-多线程版IO多路复用TCPServer模型异步IO多路复用TCPServer模型 socket套接字 套接字&#xff08;socket&#xff09;是抽象概念,表示T…

【数据分享】1901-2022年1km分辨率的逐月降水栅格数据(免费获取/全国/分省)

气象指标在日常研究中非常常用&#xff0c;之前我们给大家分享过来源于国家青藏高原科学数据中心提供的气象指标栅格数据&#xff08;均可查看之前的文章获悉详情&#xff09;&#xff1a; 1901-2022年1km分辨率逐月平均气温栅格数据1901-2022年1km分辨率逐年平均气温栅格数据…

wsl中使用宝塔每次都要绑定账号问题解决

环境&#xff1a;windows11、wsl2、Ubuntu20.04、宝塔8.0.24 1、开启Hyper-V&#xff0c;如果是家庭版使用下面代码启用Hyper-V&#xff0c;创建个.cmd文件保存后使用管理员权限运行&#xff08;需要重启电脑&#xff09; pushd "%~dp0" dir /b %SystemRoot%\servi…

QT6配置Android环境的多次尝试

可能用到的链接&#xff1a;https://www.androiddevtools.cn/#&#xff08;Android开发工具&#xff09; https://developer.android.google.cn/studio&#xff08;Android studio 下载&#xff09; https://www.oracle.com/java/technologies/downloads&#xff08;java下载&a…

【pyinstaller 怎么打包python,打包后程序闪退 不打日志 找不到自建模块等问题的踩坑解决】

程序打包踩坑解决的所有问题 问题1 多个目录怎么打包 不管你包含多个层目录&#xff0c;引用多么复杂&#xff0c;只需要打包主程序所在文件即可&#xff0c;pyinstaller会自动寻找依赖包&#xff0c;如果报错自建模块找不到&#xff0c;参照问题3 pyinstaller main.py问题2…

QT创建可移动点类

效果如图所示&#xff1a; 创建新类MovablePoint&#xff0c;继承自QWidget. MovablePoint头文件: #ifndef MOVABLEPOINT_H #define MOVABLEPOINT_H#include <QWidget> #include <QPainter> #include <QPaintEvent> #include <QStyleOption> #includ…

——滑动窗口

滑动窗口 所谓滑动窗口&#xff0c;就是不断的调节子序列的起始位置和终止位置&#xff0c;从而得出我们要想的结果。也可以理解为一种双指针的做法。 leetcode76 class Solution {public String minWindow(String s, String t) {char[] schars s.toCharArray();char[] tc…