【Linux深入剖析】进程控制 | 进程程序替换--长篇深层次讨论


📙 作者简介 :RO-BERRY
📗 学习方向:致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识
📒 日后方向 : 偏向于CPP开发以及大数据方向,欢迎各位关注,谢谢各位的支持


请添加图片描述


目录

  • 1.进程创建
    • 1.1 fork函数
    • 1.2 写时拷贝
    • 1.3 为什么要写时拷贝
  • 2.进程终止
    • 2.1 退出码
    • 2.2 进程退出场景
    • 2.3 进程常见退出方法
      • 正常终止(可以通过 echo $? 查看进程退出码):
  • 3. 进程等待
    • 3.1 进程等待必要性
    • 3.2 wait方法
    • 3.2 waitpid方法
    • 3.3 获取子进程status
    • 3.4 阻塞与非阻塞等待
  • 4.进程程序替换
    • 4.1 替换原理
    • 4.2 替换函数
    • 4.3命名理解
    • 4.4 execl函数代码实现
    • 4.5 细节处理
    • 4.5 学习各种exec接口
      • execlp
      • execv接口
      • execvp接口
      • execle
  • 结尾


1.进程创建

1.1 fork函数

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
前面进行了基础介绍以及使用,这里我们再回顾一下

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

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

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

在这里插入图片描述

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


1.2 写时拷贝

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

在这里插入图片描述
在这个图里可以看到,因为操作系统要保证进程的独立性,会进行写时拷贝,重新开辟一个空间拷贝一个副本,实现两个进程写入不同地址空间
注意:

页表是有权限控制的!!!这是为了保证我们程序运行的安全性,阻止我们的非法操作

1.3 为什么要写时拷贝

1. 创建子进程的时候,为什么不把父进程的数据直接给子进程?

  1. 把数据给你,你不一定用;把数据给你,你不一定全用。把数据给你,你全部都用,不代表你现在就要全部都用。
  2. 操作系统为了系统的效率,不浪费额外空间,在你需要写入的时候,操作系统得到信号,就能把所有数据给你

2.为什么是写时拷贝,而不是写时申请,拷贝多麻烦,申请一份新空间不就可以了?

对写的理解,对数据的操作无非增删查改,这也是需要完整数据操作的,例如a++,这也是在原数据之上进行操作,这也是为了确保程序的完整性

3.为什么我们在进行fork函数调用的时候,页表上对于数据是只读,但是在后面进行写时拷贝的时候,权限发生了改变?

这是因为我们的页表对于父子进程的数据初始就设置为只读权限,同问题一,当发生写时拷贝的时候,会告诉操作系统需要使用数据,就会发生缺页中断,然后改变权限,这里也是为了效率问题以及资源浪费还有程序安全性的问题


2.进程终止

2.1 退出码

我们学习C语言都知道,一般main函数写完我们想要的操作之后,都会在程序的最后写一个return 0

这个返回值叫做进程的退出码
一般0,表示进程执行成功 非0,表示失败
当返回为非0的时候,不同的数字会代表不同的失败情况

当我们返回值为1的时候

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
  printf("hello linux!\n");
  return 1;
}

我们使用指令
echo &?

在这里插入图片描述

程序是可以看到我们返回的值是多少的,程序根据我们的退出码就可以知道我们进程成功还是失败了,以及失败的原因是什么

这种错误退出码都会对应着一个错误描述

1.使用语言和系统自带的方法,进行转化
2.可以自定义

函数strerror

man strerror :C语言上将错误码转化为错误描述

在这里插入图片描述
函数使用:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
  for(int i = 0;i < 200; i++)
  {
    printf("%d:%s\n",i,strerror(i));
  }
  return 1;
}

运行结果:
在这里插入图片描述

这里只展现部分,在输出行里,我们输出到133个之后,程序便没有了错误描述,也就是说Linux里默认退出码一共有133个

结论

  1. main函数return返回的时候,表示进程退出,return xxx,返回的是退出码,可以设置退出码的字符串含义
  2. 其他函数退出,仅仅表示函数调用完毕!

2.2 进程退出场景

通过2.1可以总结进程退出场景一共有以下三种:

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

前两种好理解,最后一种不好理解
进程出现异常是进程收到了异常信号

例:
我们可以在进程运行的时候使用kill指令让进程收到异常信号

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
  while(1)
  {
    printf("I am a process,pid=%d\n",getpid());
    sleep(1);
  }
  return 0;
}

运行结果:
在这里插入图片描述
进程出现Floating point exception这是除0异常

进程收到的异常信号,每个信号都有不同的编号,不同的编号表明异常的原因

结论:

任何进程最终的执行情况,我们可以用两个数字表明具体执行情况,一个是进程编号,一个是进程退出码


2.3 进程常见退出方法

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

  1. 从main函数,通过return返回

一般都是这个方法不用介绍

  1. 调用exit函数

exit为进程终止函数

在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
  while(1)
  {
    printf("I am a process,pid=%d\n",getpid());
    sleep(1);
    exit(3);     //以错误码3退出
  }
  return 0;
}

运行结果:
在这里插入图片描述

exit就是用来终止进程的,exit(退出码)
在我们的进程代码中,任何地方调用exit,都表示进程退出

  1. _exit

man _exit:此函数用法和exit一模一样

在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
  while(1)
  {
    printf("I am a process,pid=%d\n",getpid());
    sleep(1);
    _exit(3);
  }
  return 0;
}

运行结果:
在这里插入图片描述

📖exit与_exit区别
我们举例说明

  • 代码1: 测试exit
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
  printf("I am a process");  //这里没有带换行
  sleep(3);
  exit(1);
}

运行结果: 三秒后屏幕进行了打印
在这里插入图片描述

  • 代码2: 测试_exit
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main()
{
  printf("I am a process");
  sleep(3);
  _exit(1);
}

运行结果: 屏幕啥也没有,执行了两次均啥也没看见
在这里插入图片描述

结论:

exit支持刷新缓冲区
_exit不支持刷新缓冲区

注意:
我们之前所谈的和缓冲区(进度条之类),绝对不是操作系统里的缓冲区


3. 进程等待

3.1 进程等待必要性

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

为什么要进行等待?
1.父进程通过wait方式,回收子进程的资源(必然)
2.通过wait方式,获取子进程的退出信息(可选)

3.2 wait方法

man 2 wait

在这里插入图片描述

默认会进行阻塞等待,等待任意一个进程
返回值:
>0等待成功,等待的子进程的pid
<0等待失败

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
  pid_t id =fork();
  if(id == 0)
  {
    //child
    int cnt=5;
    while(cnt)
    {
      printf("child is running, pid: %d,ppid: %d\n",getpid(),getppid());
      sleep(1);
      cnt--;
    }
    printf("子进程准备退出,马上变成僵尸进程\n");
    exit(0);
  }
  printf("父进程休眠\n");
  sleep(15);
  printf("父进程开始回收\n");
  //father
  pid_t rid = wait(NULL);          //阻塞等待
  if(rid > 0)
  {
    printf("wait success,rid: %d\n",rid); //这里应该拿到子进程pid
  }
  printf("父进程会回收僵尸成功\n");
  sleep(10);

  return 0;
}

运行结果:
在这里插入图片描述

可以看到一开始父子进程一起运行,后来子进程退出变为僵尸进程,最后父进程对子进程进行了回收,僵尸进程不见了


3.2 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,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。

在这里插入图片描述

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
  pid_t id =fork();
  if(id == 0)
  {
    //child
    int cnt=5;
    while(cnt)
    {
      printf("child is running, pid: %d,ppid: %d\n",getpid(),getppid());
      sleep(1);
      cnt--;
    }
    exit(1);
  }
  int status = 0;
  pid_t rid = waitpid(id,&status,0);          //阻塞等待
  if(rid > 0)
  {
    printf("wait success, rid: %d, status: %d\n",rid,status);
  }

  return 0;
}

执行结果:
在这里插入图片描述

rid输出为12231,为子进程的pid,说明函数使用成功,那为什么status=256呢?这里的status如何进行理解

3.3 获取子进程status

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

在这里插入图片描述
在这里插入图片描述

如上图,我们的status就会被写成32为编码(前16位不管),后十六位分别代表着退出码以及信号编号,中间有一位也不需要管。
这是因为我们任何进程最终的执行情况,我们需要用两个数字表明具体执行情况,一个是进程编号,一个是进程退出码,在这里我们的status则代表了这两个数字

status:256 由32为编码得到
0000 0000 0000 0000 0000 0001 0000 0000
此处退出码为1,表示进程成功运行
信号编号为0,表示进程成功运行

测试代码:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
  pid_t id =fork();
  if(id == 0)
  {
    //child
    int cnt=5;
    while(cnt)
    {
      printf("I am child process, pid: %d,ppid: %d, cnt: %d\n",getpid(),getppid(),cnt);
      sleep(1);
      cnt--;
    }
    sleep(1);
    exit(1);
  }
  int status = 0;
  pid_t rid = waitpid(id,&status,0);          //阻塞等待
  if(rid > 0)
  {
    printf("wait success, rid: %d, status: %d,exit signo: %d,exit code: %d\n",rid,status,status&0x7F,(status>>8)&0xFF);
  }

  return 0;
}
  1. 正常退出

在这里插入图片描述

  1. 被信号所杀

在这里插入图片描述

上面我们使用位运算才得到的status,我们可以使用如下两个宏来获取status
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

3.4 阻塞与非阻塞等待

我们上面子进程在运行的时候,只有子进程在进行打印,父进程在进行阻塞等待,它其实能进行阻塞等待也能进行非阻塞等待

阻塞就是调用wait函数的时候,父进程会卡在这里,直到我们的子进程结束,wait才会返回,也就是说父进程在进程调用的时候,发现子进程状态没有退出,父进程就进入了等待队列,进行等待状态

pid_t waitpid(pid_t pid, int *status, int options);
在这里第三个参数options默认为0表示阻塞等待,这个参数还能被设置为WNOHANG,这是一个宏

WNOHANG : return immediately if no child has exited.
它的作用是:如果父进程在调用的时候,发现子进程没有退出,waitpid会以出错的形式进行返回

这种方式就叫非阻塞

代码实现:

#include <stdio.h> 
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
  pid_t pid;
 
  pid = fork();
  if(pid < 0)
  {
    printf("%s fork error\n",__FUNCTION__);
    return 1;
  }
  else if( pid == 0 )
  { //child
    printf("child is run, pid is : %d\n",getpid());
    sleep(5);
    exit(1);
   }
  else
  {
    int status = 0;
    pid_t ret = 0;
    do
    {
      ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待
      if( ret == 0 )
      {
        printf("child is running\n");
      }
      sleep(1);
    }while(ret == 0);
 
    if( WIFEXITED(status) && ret == pid )
    {
      printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));
    }
    else
    {
      printf("wait child failed, return.\n");
      return 1;
    }
  }
  return 0;
}

运行结果:
在这里插入图片描述


4.进程程序替换

我们的程序只能执行我们的代码
如果我们创建的子进程,想执行其他程序的代码呢?

这种操作就可以使用我们的进程程序替换操作来解决

4.1 替换原理

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

在这里插入图片描述

4.2 替换函数

其实有六种以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 execve(const char *path, char *const argv[], char  *const envp[]);

man execl

在这里插入图片描述

4.3命名理解

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

l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量

在这里插入图片描述


4.4 execl函数代码实现

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

参数:

const char *path:路径以及文件名
const char *arg:执行操作
...:三个点为可变参数

可变参数如何理解呢?
类比printf函数

man 3 printf

在这里插入图片描述

printf函数的第三个参数也为三个点,在这里的含义也为可变参数,因为这个参数数量不确定

注: 函数最后要以NULL结尾

代码实现LS操作:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
  printf("I am a process,pid: %d\n",getpid());
  printf("exec begin...\n");

  execl("/usr/bin/ls","ls","-a","-l",NULL);   //NULL   不是 "NULL"
  
  printf("execl end...\n");

  return 0;
}

运行结果:
在这里插入图片描述


代码实现top操作:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
  printf("I am a process,pid: %d\n",getpid());
  printf("exec begin...\n");

  execl("/usr/bin/top","top",NULL);   //NULL   不是 "NULL"
  //execl("/usr/bin/ls","ls","-a","-l",NULL);   //NULL   不是 "NULL"
  
  printf("execl end...\n");

  return 0;
}

运行结果:
在这里插入图片描述

这里我们使用了我们自己的程序,就实现了系统的指令,这就叫做程序替换!!

4.5 细节处理

  1. 大家有没有发现上面我们的execl函数后面还有一个printf函数输出end没有进行输出,这是为什么?

程序替换一旦成功,exec后续的代码不再执行,因为被替换掉了

  1. exec函数的返回值去哪里了?
  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值。
  • 如果路径文件不存在则会失败

3. 程序替换后会创建新进程吗?

不会创建新进程,pid不会变,这是一个进程,只不过输出内容发生了改变

4. 创建一个进程,是先创建pcb,地址空间,页表等,还是先把程序加载到内存?

先创建先创建pcb,地址空间,页表等,程序替换所做的本质工作就是加载!

【补充】

我们使用exec函数使用的是标准传参,也可以非标准传参
exec*可移植性系统的指令,无论是什么语言,只要能在Linux下运行,都能进行执行

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
  printf("I am a process,pid: %d\n",getpid());
  printf("exec begin...\n");

  execl("/usr/bin/top","/usr/bin/top",NULL);   //NULL   不是 "NULL"
  //execl("/usr/bin/ls","ls","-a","-l",NULL);   //NULL   不是 "NULL"
  
  printf("execl end...\n");

  return 0;
}

运行结果:
在这里插入图片描述

4.5 学习各种exec接口

execlp

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

execlp接口比execl接口名字上多了一个p,p表示PATH,也就是你不用告诉系统程序在哪里,只需要告诉我名字是什么,系统替换的时候,会自动去PATH环境变量中查找

程序代码:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
  printf("I am a process,pid: %d\n",getpid());
  printf("exec begin...\n");

  //execlp("/usr/bin/top","/usr/bin/top",NULL);   //NULL   不是 "NULL"
  execlp("ls","ls","-a","-l",NULL);   //NULL   不是 "NULL"
  
  printf("execl end...\n");

  return 0;
}

运行结果:
在这里插入图片描述

注意:

execlp("ls","ls","-a","-l",NULL);
这里的第一个ls表示你想执行谁,第二个ls表示你想怎么执行,这个是不重复的

execv接口

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

参数:

const char *path:目标路径
char *const argv[]:命令行参数表【数组】与main函数中的argv功能相仿

名称中带V,指的是vector,数组

代码实现:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

char *argv[]={(char*)"ls",(char*)"-a",(char*)"-l"};
int main()
{
  printf("I am a process,pid: %d\n",getpid());
  printf("exec begin...\n");

  //execlp("/usr/bin/top","/usr/bin/top",NULL);   //NULL   不是 "NULL"
  execv("/usr/bin/ls",argv);   //NULL   不是 "NULL"
  
  printf("execl end...\n");

  return 0;
}

运行结果:
在这里插入图片描述

execvp接口

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

名称带P,写入文件名即可
代码实现:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

char *argv[]={(char*)"ls",(char*)"-a",(char*)"-l"};
int main()
{
  printf("I am a process,pid: %d\n",getpid());
  printf("exec begin...\n");

  //execlp("/usr/bin/top","/usr/bin/top",NULL);   //NULL   不是 "NULL"
  execvp("ls",argv);   //NULL   不是 "NULL"
  
  printf("execl end...\n");

  return 0;
}

运行结果:
在这里插入图片描述

execle

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

名称中带e,代表的是与环境变量挂钩

我们先试用execl接口实现C语言代码替换执行C++代码,获取C++的环境变量
Makefile

.PHONY:all
all:test myprocess
test:test.cc
	  g++ -o $@ $^ -std=c++11
myprocess:myprocess.c
		gcc -o $@ $^
.PHONY:clean
clean:
	rm -f myprocess test

这里makefile要实现编译俩个文件,需要在前面写一个依赖关系all
后缀为.cc/cpp/cxx的都是C++文件后缀

myprocess.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

char *argv[]={(char*)"ls",(char*)"-a",(char*)"-l"};
int main()
{
  printf("I am a process,pid: %d\n",getpid());
  printf("exec begin...\n");

  execl("./test","test",NULL);   //NULL   不是 "NULL"
  //execvp("ls",argv);   //NULL   不是 "NULL"
  
  printf("execl end...\n");

  return 0;
}

test.cc

#include<iostream>
using namespace std;

int main(int argc,char *argv[],char *env[])
{
  for(int i=0; env[i]; i++)
  {
    printf("env[%d]: %s\n", i, env[i]);
  }
  cout << "hello C++" << endl;
  return 0;
}

执行结果:
在这里插入图片描述

进程程序替换不会替换掉环境变量数据
想单纯添加环境变量可以在代码里使用putenv函数

回归正题,execle就是可以添加全新的环境变量表

代码正文:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

char *argv[]={(char*)"ls",(char*)"-a",(char*)"-l"};
int main()
{
  char *const env[] ={(char*)"haha=hehe",(char*)"sss=adadada"};
  printf("I am a process,pid: %d\n",getpid());
  printf("exec begin...\n");

  execle("./test","test",NULL,env);   //NULL   不是 "NULL"
  //execvp("ls",argv);   //NULL   不是 "NULL"
  
  printf("execl end...\n");

  return 0;
}

运行结果:
在这里插入图片描述


结尾

这些接口使用功能大差不差,知识在使用上有所区别,传参方式不同,为了适应不同的使用场景
下节课让我们来使用以上讲的东西制作一个shell外壳!!

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

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

相关文章

C#中什么是非托管代码?托管代码和非托管代码有什么区别

在C#中&#xff0c;托管代码和非托管代码是两种不同类型的代码&#xff0c;它们在内存管理和执行环境上有所不同。 托管代码&#xff08;Managed Code&#xff09;&#xff1a; 托管代码是由.NET运行时&#xff08;CLR&#xff0c;Common Language Runtime&#xff09;管理和执…

【每日刷题】哈希-随想录2、3、4、5、8、LC49、LC128

随想录2、LC242 有效的字母异位词随想录3、LC349两个数组的交集 3. 随想录4、LC202 快乐数 给一个整数&#xff0c;计算该数字每一位数字的平方和。核心是先拿到每一位数字&#xff0c;怎么拿&#xff1f; int 2579 2579 / 10 257 … 9 257 10 25 … 7 25 / 10 2 … 5 2 …

期货开户坚持固定的盈利模式

1、超级操盘手比的往往不是技术&#xff0c;而是素质。成功的交易者有着一种与众不同的品质&#xff0c;他们拥有正确的思维方式&#xff0c;严谨的交易态度&#xff0c;强烈的自信心、果敢&#xff0c;和面对失败永不言败的精神&#xff0c;即使在系统最困难的时候&#xff0c…

python模型训练

目录 1、新建模型 train_model.py 2、运行模型 &#xff08;1&#xff09;首先会下载data文件库 &#xff08;2&#xff09;完成之后会开始训练模型&#xff08;10次&#xff09; 3、 训练好之后&#xff0c;进入命令集 4、输入命令&#xff1a;python -m tensorboard.ma…

解决Unable to load class ‘org.gradle.api.attributes.VerificationType‘

在使用AdnroidStudio开发过程中难免会遇到Unable to load class org.gradle.api.attributes.VerificationType报错&#xff0c;可以尝试清理缓存重启解决 打开 File-》Invalidate Caches... 重启AndroidStudio后&#xff0c;重新加载即可&#xff0c;但也不是百分百解决。

java数据结构与算法刷题-----LeetCode437. 路径总和 III(前缀和必须掌握)

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. 深度优先2. 前缀和 1. 深度优先 解题思路&#xff1a;时间复…

leetcode刷题(javaScript)——链表相关场景题总结

链表中的元素在内存中不是顺序存储的&#xff0c;而是通过next指针联系在一起的。常见的链表有单向链表、双向链表、环形链表等 在 JavaScript 刷题中涉及链表的算法有很多&#xff0c;常见的包括&#xff1a; 1. 遍历链表&#xff1a;从头到尾遍历链表&#xff0c;处理每个节点…

08、关于语法:resp?.data?.data 的含义与实际操作中可能遇到的问题

1、数据情况&#xff1a; 其一、从后端拿到的数据为&#xff1a; let resp.data {"data": [],"lag_mode": 3,"totol": 0 }或&#xff1a; let resp.data {"data": [],"totol": 0 }其二、目标数据为&#xff1a; // 想要…

1小时网络安全事件报告要求,持安零信任如何帮助用户应急响应?

12月8日&#xff0c;国家网信办起草发布了《网络安全事件报告管理办法&#xff08;征求意见稿&#xff09;》&#xff08;以下简称“办法”&#xff09;。拟规定运营者在发生网络安全事件时应当及时启动应急预案进行处置。 1小时报告 按照《网络安全事件分级指南》&#xff0c…

命令行启动mongodb服务器的问题及解决方案 -- Unrecognized option: storage.journal

目录 mongodb命令行启动问题 -- Unrecognized option: storage.journal问题日志&#xff1a;问题截图&#xff1a;问题来源&#xff1a;错误原因&#xff1a;解决方式&#xff1a; mongodb命令行启动问题 – Unrecognized option: storage.journal 同样是格式出问题的问题分析和…

ThreadLocal 为什么会内存泄漏吗?是怎么产生的?

ThreadLocal是什么 ThreadLocalMap 如何避免泄漏 ThreadLocal是什么 ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射&#xff0c;各个线程之间的变量互不干扰&#xff0c;在高并发场景下&#xff0c;可以实现无状态的调用&…

WPF 滑动条样式

效果图&#xff1a; 浅色&#xff1a; 深色&#xff1a; 滑动条部分代码&#xff1a; <Style x:Key"RepeatButtonTransparent" TargetType"{x:Type RepeatButton}"><Setter Property"OverridesDefaultStyle" Value"true"/&g…

[攻防世界]-Web:fileinclude解析(文件包含,添加后缀)

查看网页 查看源代码 意思就是&#xff0c;如果变量lan被设置就会触发文件包含。 但是要注意&#xff0c;这里的文件包含会自动加上后缀&#xff0c;所以payload要注意一点 payload&#xff1a; languagephp://filter/readconvert.base64-encode/resourceflag

基带信号处理设计原理图:2-基于6U VPX的双TMS320C6678+Xilinx FPGA K7 XC7K420T的图像信号处理板

基于6U VPX的双TMS320C6678Xilinx FPGA K7 XC7K420T的图像信号处理板 综合图像处理硬件平台包括图像信号处理板2块&#xff0c;视频处理板1块&#xff0c;主控板1块&#xff0c;电源板1块&#xff0c;VPX背板1块。 一、板卡概述 图像信号处理板包括2片TI 多核DSP处理…

考取ORACLE数据库OCP的必要性 Oracle数据库

OCP证书是什么&#xff1f; OCP&#xff0c;全称Oracle Certified Professional&#xff0c;是Oracle公司的Oracle数据库DBA&#xff08;Database Administrator&#xff0c;数据库管理员)认证课程。这是Oracle公司针对数据库管理领域设立的一项认证课程&#xff0c;旨在评估和…

(Sora模型风口)2024最新GPT4.0使用教程,AI绘画,一站式解决

一、前言 ChatGPT3.5、GPT4.0、GPT语音对话、Midjourney绘画&#xff0c;文档对话总结DALL-E3文生图&#xff0c;相信对大家应该不感到陌生吧&#xff1f;简单来说&#xff0c;GPT-4技术比之前的GPT-3.5相对来说更加智能&#xff0c;会根据用户的要求生成多种内容甚至也可以和…

云上攻防-云原生篇Docker安全权限环境检测容器逃逸特权模式危险挂载

知识点: 1、云原生-Docker安全-容器逃逸&特权模式 2、云原生-Docker安全-容器逃逸&挂载Procfs 3、云原生-Docker安全-容器逃逸&挂载Socket 4、云原生-Docker安全-容器逃逸条件&权限高低 章节点&#xff1a; 云场景攻防&#xff1a;公有云&#xff0c;私有云&…

GIT分支管理与远程操作

文章目录 10.分支操作-分支介绍(掌握)目标内容小结 11.分支操作-分支创建与切换目标内容小结 12.分支操作-分支合并与删除目标内容小结 13.GIT远程仓库介绍与码云仓库注册创建目标内容小结 14.GIT远程仓库操作-关联、拉取、推送、克隆(不用刻意记住命令)目标内容小结 10.分支操…

Zynq—AD9238数据采集DDR3缓存千兆以太网发送实验(一)

Zynq—AD9238数据采集DDR3缓存千兆以太网发送实验&#xff08;前导&#xff09; 四、AXI转FIFO接口模块设计 1.AXI接口知识 AXI协议是基于 burst的传输&#xff0c;并且定义了以下 5 个独立的传输通道&#xff1a; 读地址通道&#xff08;Read Address Channel&#xff0c; …

python统计分析——广义线性模型的评估

参考资料&#xff1a;用python动手学统计学 残差是表现数据与模型不契合的程度的重要指标。 1、导入库 # 导入库 # 用于数值计算的库 import numpy as np import pandas as pd import scipy as sp from scipy import stats # 导入绘图的库 import matplotlib.pyplot as plt i…