【Linux】进程控制,进程替换

2023-11-04

1.进程创建

fork函数初识

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

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

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

  • 分配新的内存块和内核数据结构给子进程

  • 将父进程部分数据结构内容拷贝至子进程

  • 添加子进程到系统进程列表当中

  • fork返回,开始调度器调度

image-20230907220741961

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

fork之前父进程独立执行,fork之后,父子两个执行流分别执行。

注意,fork之后,谁先执行完全由调度器决定

fork函数返回值

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

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

道理很简单一个父亲可以有很多孩子,但是一个孩子只能有一个父亲,所以一个父进程可以创建很多个子进程出来,而一个子进程只能有一个父进程。因此,父进程是不需要标记的,而对于父进程来说,它要可能要管理多个子进程,所以父进程要知道子进程PID才好管理好接下来的操作

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

父进程在调用fork函数的时候,fork函数在内部做了一系列的操作包括但不限于给子进程创建进程控制块task_struct ,进程地址空间mm_struct ,页表等其他信息的处理,也就是说在fork函数内部return之前就已经把子进程创建完毕,那么fork函数的return语句是父进程和子进程同时在执行的,这就是为什么fork有两个返回值的原因。

image-20230908113131115

写时拷贝

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

image-20230907221614923

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

进程最大的一个特点是具有独立性,如果在其中一个进程中修改了数据没有进行写时拷贝,那么将会影响到其他进程的数据,为了保证各个进程的相互独立,是必须要有写时拷贝的

为什么不在创建子进程的时候就进行数据的拷贝?

其实很多时候,子进程并不一定会使用的父进程的所有数据,并且如果在某些情况下子进程只是单纯的对父进程的数据进行读取,没有必要对数据进行拷贝。所有正确的做法应该是按需分配,在需要修改数据的时候再进行分配,这样可以高效的使用内存空间。

代码会不会进行写时拷贝

很多情况下都不需要代码进行拷贝,但凡是没有绝对,例如在进行进程替换的时候,就需要进行代码拷贝的写时拷贝

fork常规用法

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

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

fork调用失败的原因

一般不会出错

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

2. 进程终止

进程退出场景

  • 代码运行完毕,结果正确

  • 代码运行完毕,结果不正确

  • 代码异常终止

进程常见退出方法

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

  1. 从main返回

  2. 调用exit

  3. _exit (系统调用)

异常退出:

ctrl + c,信号终止

_exit函数

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

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

注意: 退出码都有对应的字符串含义,帮助用户确认执行失败的原因,而这些退出码具体代表什么含义是人为规定的,不同环境下相同的退出码的字符串含义可能不同。

C语言当中的strerror函数可以通过错误码,获取该错误码在C语言当中对应的错误信息

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

例如,以下代码中使用_exit终止进程,则缓冲区当中的数据将不会被输出。

image-20230908125233187

image-20230908125215530

exit函数

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

其实exit底层实现最后也会调用exit, 只不过在调用exit之前,它还做了其他工作:

  1. 执行用户定义的清理函数。

  2. 关闭所有打开的流,所有的缓存数据均被写入

  3. 调用_exit

image-20230907224303845

例如,以下代码中exit终止进程前会将缓冲区当中的数据输出。

image-20230908125122546

image-20230908125142529

return退出

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

return、exit和_exit之间的区别与联系

只有在main函数当中的return才能起到退出进程的作用,子函数当中return不能退出进程,而exit函数和_exit函数在代码中的任何地方使用都可以起到退出进程的作用。

在main函数执行return num等同于执行exit(num),因为调用main函数运行结束后,会将main函数的返回值当做exit的参数来调用exit函数。

img

3. 进程等待

进程等待的必要性

子进程退出,如果父进程放任不管,就可能造成僵尸进程的问题,进而造成内存泄漏,而且进程一旦进入了僵尸状态,那就刀枪不入,连”杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。

并且,子进程一般都是父进程给派发任务而创建的,所以父进程是需要知道子进程的运行结果和结果状态。

而父进程是需要进行进程等待的方式获取子进程的运行结果和对子进程的资源进行回收

进程等待的方法

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

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

image-20230908132937580

获取子进程status

进程等待所使用的两个函数wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统进行填充。

如果对status参数传入NULL,表示不关心子进程的退出状态信息。否则,操作系统会通过该参数,将子进程的退出信息反馈给父进程。

status是一个整型变量,但status不能简单的当作整型来看待,status的不同比特位所代表的信息不同,具体细节如下(只研究status低16比特位):

image-20230908133104800

//在代码中我们可以这样获取对应的状态码
exitCode = (status >> 8) & 0xFF; //退出码
exitSignal = status & 0x7F;      //退出信号

//对于此,系统当中提供了两个宏来获取退出码和退出信号。
//WIFEXITED(status):用于查看进程是否是正常退出,本质是检查是否收到信号。
///WEXITSTATUS(status):用于获取进程的退出码。
exitNormal = WIFEXITED(status);  //是否正常退出
exitCode = WEXITSTATUS(status);  //获取退出码

不过需要注意的是,如果一个进程非正常退出的时候,说明该进程是给信号所杀,那么对应的退出码就没有任何意义了,只需看它的退出信号即可。

//wait函数测试代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
  pid_t pid=fork();
  if(pid==-1){
    perror("fork");
    exit(1);
  }else if(pid==0){
      //子进程
      int cnt=5;
      while(cnt--){
        printf("我是子进程,我的pid是:%d,我的ppid是%d\n",getpid(),getppid());
        sleep(1);
      }
      exit(2);
  }else{
      //父进程
      int status;
      int ret=wait(&status);
      if(ret==-1){
        perror("wait");
        exit(3);
      }else{
        int exitSignal=status & 0x7F;//退出信号
        int exitCode=(status >>8) & 0xFF; //退出码
        printf("exitSignal:%d exitCode:%d \n",exitSignal,exitCode);
      }
  }
  return 0;
}

image-20230908135532424

我们可以使用以下监控脚本对进程进行实时监控:

while :;do ps -axj | head -1 && ps -axj | grep test | grep -v grep ; echo "#############"; sleep 1;done

image-20230908135602896

阻塞和非阻塞等待方式

进程的阻塞等待方式

创建子进程后,父进程可使用waitpid函数一直等待子进程(此时将waitpid的第三个参数设置为0),直到子进程退出后读取子进程的退出信息。

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

int main()
{
  pid_t pid=fork();
  if(pid==-1){
    perror("fork");
    exit(-1);
  }
  if(pid==0){
    //子进程
    int cnt=5;
    while(cnt--){
      printf("我是子进程,我的pid:%d,我的ppid:%d\n",getpid(),getppid());
      sleep(1);
    }
    exit(1);
  }
  //父进程
  int status;
  int ret=waitpid(pid,&status,0);//阻塞等待
  if(ret==-1){
      perror("wait");
      return 1;
  }else{
      printf("wait success,exitSignal:%d exitCode:%d\n",status & 0x7F,(status >>8)& 0xFF);
  }

  return 0;
}
//运行结果
//我是子进程,我的pid:5776,我的ppid:5775
//我是子进程,我的pid:5776,我的ppid:5775
//我是子进程,我的pid:5776,我的ppid:5775
//我是子进程,我的pid:5776,我的ppid:5775
//我是子进程,我的pid:5776,我的ppid:5775
//wait success,exitSignal:0 exitCode:1

同样我们可以测试一下在父进程运行过程中,我们可以尝试使用kill -9命令将子进程杀死,这时父进程也能等待子进程成功。(被信号所杀的进程,退出码没有意义)

image-20230908141336705

进程的非阻塞等待方式

上述所给例子中,当子进程未退出时,父进程都在一直等待子进程退出,在等待期间,父进程不能做任何事情,这种等待叫做阻塞等待。

实际上我们可以让父进程不要一直等待子进程退出,而是当子进程未退出时父进程可以做一些自己的事情,当子进程退出时再读取子进程的退出信息,即非阻塞等待。

做法很简单,向waitpid函数的第三个参数potions传入WNOHANG,这样一来,等待的子进程若是没有结束,那么waitpid函数将直接返回0,不予以等待。而等待的子进程若是正常结束,则返回该子进程的pid。

父进程可以隔一段时间调用一次waitpid函数,若是等待的子进程尚未退出,则父进程可以先去做一些其他事,过一段时间再调用waitpid函数读取子进程的退出信息。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(){
  pid_t pid=fork();
  if(pid==-1){
    perror("fork");
    exit(-1);
  }
  if(pid==0){
    //子进程
    int cnt=5;
    while(cnt--){
      printf("我是子进程,我的pid:%d,我的ppid:%d\n",getpid(),getppid());
      sleep(1);
    }
    exit(1);
  }
  //父进程
  int status;
  while(1){
       int ret=waitpid(pid,&status,WNOHANG);//非阻塞等待
      if(ret==-1){
          perror("wait");
          return 1;
      }else if(ret==0){
          printf("非阻塞等待中.....正在执行其他任务.......\n");
          sleep(1);
      }else {
          printf("wait success,exitSignal:%d exitCode:%d\n",status & 0x7F,(status >>8)& 0xFF);
          break;
      }
  }
  return 0;
}

image-20230908142407772

4. 进程程序替换

替换原理

用fork创建的子进程,执行的往往是和父进程相同的程序,虽然也有可能执行不同的代码,但是想要让子进程执行另一个程序,往往需要调用exec函数。

当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

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

显然不会,因为我们说过,进行最大的一个特点就是独立性,如果其中一个进程发生修改数据的操作,就会进行写时拷贝

替换函数

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

#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 execl(const char *path, const char *arg, …);

第一个参数是要执行程序的路径,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾。

例如,要执行的是ls程序。

execl("usr/bin/ls","ls","-a","-l",NULL);

image-20230908153527688

image-20230908153551745

事实证明:执行exec函数成功之后,exec函数下面的代码将全部不会并执行,因为在执行exec函数之后,所有代码数据已被替换。

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

第一个参数是要执行程序的名字,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾。

例如,要执行的是ls程序。

execlp("ls","ls","-a","-l",NULL)

image-20230908153839250

image-20230908153825240

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

第一个参数是要执行程序的路径,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾,第三个参数是你自己设置的环境变量。

例如,你设置了MYVAL环境变量,在mycmd程序内部就可以使用该环境变量。

char* myenvp[] = { "MYVAL=2023", NULL };
execle("./mycmd", "mycmd", NULL, myenvp);
  • 四、int execv(const char *path, char *const argv[]);

第一个参数是要执行程序的路径,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾。

例如,要执行的是ls程序。

char* myargv[] = { "ls", "-a", "-l", NULL };
execv("/usr/bin/ls", myargv);
  • 五、int execvp(const char *file, char *const argv[]);

第一个参数是要执行程序的名字,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾。

例如,要执行的是ls程序。

char* myargv[] = { "ls", "-a", "-l", NULL };
execvp("ls", myargv)
  • 六、int execve(const char *path, char *const argv[], char *const envp[]);

第一个参数是要执行程序的路径,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾,第三个参数是你自己设置的环境变量。

例如,你设置了MYVAL环境变量,在mycmd程序内部就可以使用该环境变量。

char* myargv[] = { "mycmd", NULL };
char* myenvp[] = { "MYVAL=2023", NULL };
execve("./mycmd", myargv, myenvp);

其实还有一个函数叫execve系统调用

#include <unistd.h>
int execve(const char *filename, char *const argv[],
                  char *const envp[]);

实际上有关exec是一个函数族,包括execle,execlp,execvp,execv,execl但是他们具体的实现都是调用execve()之上。它们的区别就在于对路径名,参数以及环境变量的指定上。下面分别从这三个方面来区分这几个函数:

image-20230908152737084

image-20230908155430586

命名理解

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

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

  • v(vector) : 参数用数组

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

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

函数解释

  • 这些函数如果调用成功,则加载指定的程序并从启动代码开始执行,不再返回。
  • 如果调用出错,则返回-1。

也就是说,exec系列函数只要返回了,就意味着调用失败。

做一个简易版的命令行解释器shell

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

#define LEN 1024 //命令的最大的长度
#define NUM 32 //命令拆分后的最大个数
int main()
{
  char cmd[LEN];
  char* myargv[NUM];
  const char* user=getenv("USER");//获取用户名
  const char* path=getenv("PWD");//获取当前路径
  while(1)
  {
    printf("[%s@ %s]# ",user,path);
    fgets(cmd,LEN,stdin);
    cmd[strlen(cmd)-1]='\0';
    //分割解析命令
    int i=0;
    myargv[i++]=strtok(cmd," ");
    while(myargv[i]=strtok(NULL," ")) i++;
    if(fork()==0)
    {

      //子进程执行替换任务
      execvp(myargv[0],myargv);
      exit(1);
    }
    int status;
    int ret=waitpid(-1,&status,0);//回收资源
    if(ret==-1){
      perror("wait");
    }else{
      printf("exit code:%d\n", (status>>8)& 0xFF);
    }
  }
  return 0;
}

image-20230908163925964

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Linux】进程控制,进程替换 的相关文章

  • Linux下的C#,Process.Start()异常“没有这样的文件或目录”

    我在使用 Process 类调用程序来启动程序时遇到问题 可执行文件的层次结构位于 bin 目录下 而当前工作目录需要位于 lib 目录下 project bin a out this is what I need to call lib
  • 在键盘热插拔上加载模块

    我正在尝试学习如何为 Linux 系统编写模块和驱动程序 类似于this https unix stackexchange com questions 120839 usb kernel module does not load on de
  • Mono 和 WebRequest 速度 - 测试

    在 mono 4 6 2 linux 中 我注意到 wget 下载文件的速度与webclient DownloadString 所以我做了一个小测试来调查 为什么 wget 明显比 C 快 根据我自己的实验 使用 wget 下载 手动读取文
  • 跟踪 pthread 调度

    我想做的是创建某种图表 详细说明 Linux 中 两个 线程的执行情况 我不需要查看线程的作用 只需查看它们何时被安排以及持续多长时间 基本上是一条时间线 在过去的几个小时里 我一直在互联网上搜索跟踪 pthread 调度的方法 不幸的是
  • 为什么使用Python的os模块方法而不是直接执行shell命令?

    我试图了解使用Python的库函数执行特定于操作系统的任务 例如创建文件 目录 更改文件属性等 背后的动机是什么 而不是仅仅通过执行这些命令os system or subprocess call 例如 我为什么要使用os chmod而不是
  • 使用 sed 将 old-link-url 替换为 new-link-url

    我正在 bash 中编写一个脚本 将 old link url 替换为 new link url 我的问题是 sed 由于斜杠而无法替换 url 如果我只输入一些文字就可以了 my code sed e s old link new lin
  • 变量作为 bash 数组索引?

    bin bash set x array counter 0 array value 1 array 0 0 0 for number in array do array array counter array value array co
  • 将 stdout 作为命令行 util 的文件名传递?

    我正在使用一个命令行实用程序 该实用程序需要传递文件名以将输出写入 例如 foo o output txt 它唯一写入的东西stdout是一条消息 表明它运行成功 我希望能够通过管道传输写入的所有内容output txt到另一个命令行实用程
  • 是否有可能通过 mmap 匿名内存“打孔”?

    考虑一个使用大量大致页面大小的内存区域 例如 64 kB 左右 的程序 每个内存区域的寿命都相当短暂 在我的特定情况下 这些是绿色线程的替代堆栈 如何最好地分配这些区域 以便一旦该区域不再使用 它 们的页面可以返回到内核 天真的解决方案显然
  • 如何在 Linux 中使用单行命令获取 Java 版本

    我想通过单个命令获取 Linux 中的 Java 版本 我是 awk 的新手 所以我正在尝试类似的事情 java version awk print 3 但这不会返回版本 我将如何获取1 6 0 21从下面的Java版本输出 java ve
  • gdb 错误 - 文件不是可执行格式:无法识别文件格式

    我正在尝试使用 gdb 调试某个名为 xdf 的程序 但是当我运行 gdb xdf 时 出现以下错误 home nealtitusthomas X ray astronomy heasoft 6 24 x86 64 pc linux gnu
  • 使用脚本自动输入 SSH 密码

    我需要创建一个自动向 OpenSSH 输入密码的脚本ssh client 假设我需要通过 SSH 进入myname somehost用密码a1234b 我已经尝试过 bin myssh sh ssh myname somehost a123
  • Bash 脚本 - 迭代 find 的输出

    我有一个 bash 脚本 其中需要迭代 find 命令输出的每一行 但似乎我正在迭代 find 命令中的每个单词 以空格分隔 到目前为止我的脚本看起来像这样 folders find maxdepth 1 type d for i in f
  • Linux、ARM:为什么仅当启动时存在 I2C GPIO 扩展器时才创建 gpiochip

    在 imx6sx 硬件平台 NXP 嵌入式 ARM 上使用 Linux 3 14 52 问题是设备树中指定的 PCF8575 I2C GPIO 扩展器不会实例化为 sys class gpio 结构中的设备 除非它们在内核启动期间存在 这些
  • 如何确保 numpy BLAS 库可用作动态加载库?

    The theano安装文档 http www deeplearning net software theano install html troubleshooting make sure you have a blas library指
  • 具有少量父设备属性的 udev 规则

    我需要复杂且通用的udev规则来确定插入任何 USB 集线器的特定端口的 USB 设备 所以 我必须结合设备树不同层的父属性 我有这个 udevadm info query all name dev ttyUSB0 attribute wa
  • “grep -q”的意义是什么

    我正在阅读 grep 手册页 并遇到了 q 选项 它告诉 grep 不向标准输出写入任何内容 如果发现任何匹配 即使检测到错误 也立即以零状态退出 我不明白为什么这可能是理想或有用的行为 在一个程序中 其原因似乎是从标准输入读取 处理 写入
  • Linux 上的 Python 3.6 tkinter 窗口图标错误

    我正在从 Python GUI 编程手册 学习 Python GUI 某项任务要求我通过将以下代码添加到我的配方中来更改窗口图标 Change the main windows icon win iconbitmap r C Python3
  • python:numpy 运行脚本两次

    当我将 numpy 导入到 python 脚本中时 该脚本会执行两次 有人可以告诉我如何阻止这种情况 因为我的脚本中的所有内容都需要两倍的时间 这是一个例子 usr bin python2 from numpy import print t
  • 套接字:监听积压并接受

    listen sock backlog 在我看来 参数backlog限制连接数量 这是我的测试代码 server initialize the sockaddr of server server sin family AF INET ser

随机推荐

  • python中哈希表和set的使用

    哈希表不能将可变对象作为key值 即引用类型的内容不能是可变的 这样不安全 因为hashcode函数是根据对象的内容计算出key和value的位置 如果引用的内容可变 那么每次查找的位置结果都不一样 之前存储的键值对就会找不到 不符合has
  • 区块链技术之分布式存储

    随着互联网技术应用技术的普遍使用 所有行业的数据量指数级增长 数据存储技术都需要更新 分布式存储是一种数据存储技术 它可以跨多个物理服务器传播文件 块存储或者对象存储 以实现高可用性 数据备份和灾难恢复目的 可扩展的存储服务以及数据中心的巨
  • K8S的金丝雀发布(Canary Release)

    金丝雀发布 Canary Release 1 概念 2 相关架构理念 3 金丝雀发布部署操作 4 访问测试 5 金丝雀隔离新的pod 6 重建 7 获取当前集群中所有的终结点 8 登录旧的pod中测试 9 查看更新状态信息 总结 1 概念
  • 树链剖分

    树链剖分 两个核心思想 将一棵树转化成一个序列 树中路径转化成 log n 段连续区间 相关概念 重儿子 某个节点的子节点所构成的子树中 子树节点数量最多对应的子节点为重儿子 如果有多个相同的最大数量 则任选一个为重儿子 也就是说 每个节点
  • Node.js 创建一个简单的web服务器

    Node可以写 web服务器 命令行工具 网络爬虫 桌面应用程序开发等 今天 我们利用node写一个简单的web服务器 一 引入主模块 let http require http 二 创建一个服务器 createServer可以看到源码注入
  • 微信小程序子页面自定义tabbar组件

    一 先言 有时候微信小程序会遇到代码合并 就比如把B小程序代码迁移到A小程序 要使得B作为A小程序的一个子页面子功能 因为本身小程序都有tabbar 原来B也有 这时候就要给B子功能自定义一个tabbar底部导航栏 注意 这个不是微信小程序
  • <转>Java集合框架之小结

    转载自 http jiangzhengjun iteye com blog 553191 1 Java容器类库的简化图 下面是集合类库更加完备的图 包括抽象类和遗留构件 不包括Queue的实现 2 ArrayList初始化时不可指定容量 如
  • python爬虫04 - xpath和lxml模块

    可以说 xpath中 x就是不确定 而path就是路径 指向 1 xpath介绍 1 1 基本概念 XPath XML Path Language 是一种XML的查询语言 他能在XML树状结构中寻找节点 XPath 用于在 XML 文档中通
  • FreeRTOS系列第6篇---FreeRTOS内核配置说明

    FreeRTOS内核是高度可定制的 使用配置文件FreeRTOSConfig h进行定制 每个FreeRTOS应用都必须包含这个头文件 用户根据实际应用来裁剪定制FreeRTOS内核 这个配置文件是针对用户程序的 而非内核 因此配置文件一般
  • PCL common模块应用实例

    目录 一 common模块中的头文件 二 基本函数 1 angles h 2 centriod h 3 common h 4 distance h 5 copy point h 6 geometry h 参考链接 本文由CSDN点云侠原创
  • 2022最新苹果开发者账号注册、付款流程图解【图文并茂】

    更新日期 2022 07 22 每年续费就有一个坑 文章末尾已注明 1 申请一个邮箱 用于注册苹果开发者账号 我的邮箱 密码 2 注册开发者账号 1 注册官网 官网地址 Apple Developer Program Apple Devel
  • 国内android第三方rom,安卓手机第三方rom大盘点,曾经火到不行,如今几乎全军覆没...

    自从谷歌接手安卓以后 从2005年到现在 经过了几十年的发展 安卓已经发布了数十个版本 成为目前唯一能和苹果匹敌的手机操作系统 虽然华为有了鸿蒙 但目前至少还没有完全用在手机上 打开APP 查看更多精彩图片 安卓由于其开放性而受到许多用户的
  • copilot command line

    copilot 支持command line了 linux macos都可以安装 npm i githubnext github copilot cli 安装之后 如果要能够正常使用的话 得先进行authorization github账户
  • app怎么修改服务器IP地址,怎么修改手机服务器ip地址

    怎么修改手机服务器ip地址 内容精选 换一换 如果私钥文件丢失了 可以为服务器替换新的密钥对 并使用新的私钥文件连接云手机 以下为替换服务器密钥对的操作指导 请提前在云服务器控制台创建密钥对 并将密钥对对应的私钥文件下载至本地 登录管理控制
  • nslookup命令详解:域名解析=>得到IP地址

    1 nslookup作用 nslookup用于查询DNS的记录 查询域名解析是否正常 在网络故障时用来诊断网络问题 2 查询 a 直接查询 nslookup domain dns server 如果没有指定dns服务器 就采用系统默认的dn
  • chrome 下载东西 失败禁止_如何修复最常见的Google Chrome下载错误

    尽管事实上大多数情况下Google Chrome浏览器都是很漂亮的浏览器 但有时候有些事情并不能完全按预期运行 其中有些涉及文件下载 正如许多Google Chrome用户在某个时候发现的那样 文件下载有时会失败 并不一定是直截了当的错误
  • 随想录:开发一流Android SDK

    http blog csdn net dd864140130 article details 53558011 自从前段时间离职后 因为个人的事情一直没有选择再工作 也导致原有的文章并没有按时产出 最近个人的事情整理的也差不多了 恰好有不少
  • 【python】把Excel中的数据在页面中可视化

    一 需求 最近我们数据可视化的老师让我们把广州历史房价中的房价数据可视化 然后给我们发了广州历史房价 xls 然后看了一下数据确实有点小多 反正复制粘贴是有点费劲的 所以就想借用python帮我把数据修改成我一键复制的模样 二 安装xlrd
  • win安装nacos

    nacos安装注意事项 nacos安装包下载之后 默认是集群模式 需要改成单例执行 路径 nacos bin startup cmd 2 nacos运行需要使用jdk jdk必须使用64位 3 本地新增nacos数据库 执行sql脚本 na
  • 【Linux】进程控制,进程替换

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