当前位置: 首页 > news >正文

【Hello Linux】进程控制 (内含思维导图)

作者:@小萌新
专栏:@Linux
作者简介:大二学生 希望能和大家一起进步!
本篇博客简介:简单介绍下进程的控制 包括进程启动 进程终止 进程等待 进程替换等概念

进程控制

  • 介绍
  • 进程创建
    • fork函数
      • fork函数的返回值
      • fork函数的使用
      • 写时拷贝技术
      • fork常规用法
      • fork调用失败的原因
  • 进程终止
    • 进程退出的场景
      • 进程退出码
    • 进程正常退出
      • return
      • exit
      • _exit
      • return exit _exit之间的区别和联系
    • 进程异常退出
  • 进程等待
    • 进程等待的必要性
    • 如何进行进程等待
      • wait
      • waitpid
      • status详解
      • 阻塞和非阻塞详解
    • 多进程等待模型
    • 非阻塞轮询模型
  • 进程替换
    • 进程替换是什么
    • 为什么要进行进程替换
    • 进程替换的原理
    • 如何进行进程替换
    • 六大替换函数详解
      • exec函数的返回值
      • execl
      • execlp
      • execle
      • execv
      • execvp
      • execve
      • 替换函数的命名理解
  • 思维导图总结

介绍

下面我们将分别从进程创建 进程终止 进程等待 进程替换四个方面来介绍进程控制相关内容
我们会先从进程相关函数的使用开始学习直至了解其底层原理
最后我们将灵活运用上面学到的所有知识来写一个简单的shell程序

进程创建

fork函数

我们学习到目前为止一共有两种比较常见的创建进程方式

一种是直接输入可执行文件 比如说 ls ll等 这实际上就是创建了一个进程

在这里插入图片描述
还有一种方式就是通过调用fork()函数来在已存在的进程中创建一个进程

新创建的进程叫做子进程 原来的进程叫做父进程

fork函数的返回值

一般来说对于一个函数而言 返回值我们不必讲解 但是fork函数很特殊

因为它有两个返回值 一个是给父进程的返回值 一个是给子进程的返回值

我们从系统的角度来理解一下为什么存在两个返回值

在前面的博客中我们知道了 进程其实就是 程序+PCB+mm_struct+页表

在这里插入图片描述

我们在调用fork这个函数的时候 操作系统会对子进程进行创建PCB以及

mm_struct和页表映射等一系列操作 当程序计数器走到return pid这一步的时候事实上的子进程已经被创建完毕了

所以说父子进程都走到了这一步 所以会有两个返回值也就不奇怪了

  • 如果创建子进程失败便会对父进程返回-1
  • 如果创建子进程成功便会对父进程返回子进程pid 对子进程返回0

为什么我们对于父进程就是返回pid 对于进程就直接返回0呢?

因为父进程对于子进程是一个一对多的关系 一个父进程能够创建很多个子进程 所以说父进程需要知道子进程的pid才能够唯一标识之

而子进程对于父进程是一个一对一的关系 一个子进程只能有一个父进程 所以说对于子进程来说并不需要特别标识什么

fork函数的使用

我们使用fork函数一般是因为要通过多个进程去处理问题

所以说一般是让父子进程去做不同的事情

上面的返回值部分我们也解释过 我们可以通过返回值来分辨父子进程

那么一个简单的多进程C++示例程序就可以这么写

  1 #include <unistd.h>2 #include <stdio.h>3 #include <iostream>4 using namespace std;5 6 int main()7 {8   int ret = fork();9   if (ret == 0)10   {11     // child                                                             12     printf("im child my pid is:%d my ppid is:%d\n",getpid(),getppid());13   }14   else 15   {16     // father17     printf("im father my pid is:%d my ppid is:%d\n",getpid(),getppid());18     sleep(1);                                                            19   }20 21   return 0;22 }

我们让父子进程分别打印它们的pid和它们父进程的pid 在linux下运行效果如下

在这里插入图片描述
我们可以发现子进程的pid就是父进程的ppid

我们在写业务的时候如果需要使用多进程 可以直接将if else里面的逻辑替换

写时拷贝技术

子进程的创建过程中会伴随着PCB mm_struct的创建 页表映射
在这里插入图片描述
一般来说父子进程的代码和数据都是共享的 所以它们会被页表映射到同一物理地址中

在这里插入图片描述
但是对于子进程来说 我们有可能会修改它的各项数据 此时便会发生写时拷贝

在这里插入图片描述
子进程的页表会重新映射一份物理内存给子进程

但是此时子进程的mm_struct地址却没有改变

所以这就会造成一个很奇怪的现象

那就是打印出来的地址相同 可是地址里面的数值不同

造成这个现象的原因其实就是虚拟地址没有变而物理地址变化了

我们为什么要进行写时拷贝呢?

因为进程具有独立性 比如说上面的例子 如果我们不进行写时拷贝的话子进程数据的修改就会影响到父进程了

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

这是因为子进程有可能不会修改数据 所以说我们没有必要进行数据拷贝 直接共享父进程的数据可以更高效的使用内存空间

代码会不会进行写时拷贝?

代码一般情况下不会进行写时拷贝 当然代码也可以进行写时拷贝 比如说我们后面讲到的进程替换就用到了代码的写时拷贝

fork常规用法

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

fork调用失败的原因

  1. 系统中有太多的进程 内存空间不足 子进程创建失败
  2. 实际用户的进程数超过了限制 子进程创建失败

进程终止

进程退出的场景

进程退出一共有三种场景

  1. 代码运行完毕 结果正确
  2. 代码运行完毕 结果错误
  3. 代码异常终止

进程退出码

我们在写c语言程序的时候 一般会在最后面加上return 0

在这里插入图片描述
那么大家有没有想过 这个return 0是返回给谁的呢?

我们在C/C++中 在代码最后都会写上return 0; 对于这个返回值我们称它为进程退出码 对于正确的进程一般都以0作为进程退出码 而非0就作为错误的进程的退出码 因此不同的错误对应的退出码也是不同的

比如说我们写出下面的这样一个代码

在这里插入图片描述
如果说我们的运行结果是正确的话 我们最后返回的就是0

如果运行的结果是错误的话 我们最后返回的就是一个错误码

这个错误码可能是一个整数 这个整数代表着各种各样的错误

在c语言中 我们可以使用下面的这段代码来获取所有的错误码

在这里插入图片描述

那么我们可以如何获取错误码呢?

在linux中 我们可以通过下面的代码来获取上一个进程的错误码

   echo $?

如果我们运行上面的程序 我们可以发现退出码是0

在这里插入图片描述
如果我们稍微修改下程序 让==变成 != 那么我们就会发现退出码是1
在这里插入图片描述
当我们调用系统进程 比如说ls ll的时候 实际上它们也会有一个返回码
在这里插入图片描述
当我们正常使用的时候 这个返回码就是0

如果我们使用失败的话 它也会返回我们一个错误码 来告诉我们为什么失败

在这里插入图片描述

进程正常退出

在linux中 我们让进程退出一般有三种方式

  • return退出
  • exit函数退出
  • _exit函数退出

下面我们将分别写出代码来介绍这三种退出方式

return

在这里插入图片描述
上面是我们验证return退出进程的代码

我们可以使用

 echo  $?

来查看上一个进程的退出码 下面的退出也是使用这种方式

我们使用的时候不再赘述

在这里插入图片描述
事实上我们return的也确实是0

exit

我们除了可以使用return退出之外还可以使用exit来退出一个进程

它的使用方式如下

代码如下

在这里插入图片描述

接下来我们接受退出码 我们发现确实是10
在这里插入图片描述

_exit

我们在写代码的时候 并不常用_exit函数

它能够在任何地方强制退出进程 直接清空缓冲区 不输出任何的数据

在这里插入图片描述
我们编译后执行这段代码 我们会发现没有任何的数据被打印

在这里插入图片描述
接下来 我们继续调用echo $?来获取进程的退出码
在这里插入图片描述
我们能够获取到进程的退出码是30

return exit _exit之间的区别和联系

区别

  • return只有在主函数中才有退出进程的功能 在子函数中是没有这个功能的 而exit和_exit在任何地方都可以退出进程
  • exit和return在结束进程的时候会执行用户的清除函数 刷新缓冲区 关闭流等 而_exit则会直接退出 什么都不会做

在这里插入图片描述

联系

实际上我们在主函数中的return num 就相当于调用了 exit(num)

而exit(num)在执行完用户定义的清理函数 清空缓冲区 关闭流之后还是会调用_exit函数

进程异常退出

在linux中 进程异常退出一般有两种方式

  • 释放kill信号给进程后退出
  • 出现如 除0问题 指针越界等问题程序终止

进程等待

进程等待的必要性

  • 子进程退出 父进程如果不读取子进程的退出信息 子进程就会变成僵尸进程 进而造成内存泄漏
  • 进程一旦变成僵尸进程 那么就算是kill -9命令也无法将其杀死 因为谁也无法杀死一个已经死去的进程
  • 父进程派给子进程的任务完成的如何 我们需要知道 子进程运行完成 结果对还是不对 是否正常退出
  • 父进程通过进程等待的方式 回收子进程资源 获取子进程退出信息

上面是比较详细的说法

我们进程等待最主要的原因就是为了让父进程等待子进程结束回收资源 获取信息 避免子进程变成僵尸进程

如何进行进程等待

wait

我们可以使用wait函数来让父进程等待子进程运行结束

它的函数使用方法如下

pid_t wait(int* status);

它的返回值是一个pid_t类型的数据 如果等待成功会返回这个进程的pid 等待失败则会返回-1

它的参数是一个指针 这个指针我们会在后面详细讲解

它的作用是让父进程等待任意子进程

我们写出下面的代码 它的目的是为了验证wait的作用

它的逻辑是创建两个进程 子进程五秒后结束 父进程先休眠十五秒让子进程进入僵尸状态 当父进程休眠完毕之后使用wait函数回收子进程 之后打印出父进程回收的结果

  1 #include <stdio.h>2 #include <unistd.h>3 #include <stdlib.h>4 #include <sys/wait.h>5 #include <sys/types.h>6 // 演示wait函数的作用7 int main()8 {9   int ret = fork();10   if (ret == 0)11   {12     // child13     int count = 5;14     while(count--)15     {16       printf("hello world!\n");17       sleep(1);18     }19 20     exit(0); // 子进程退出21   }                                                                                                               22 23   // father24   sleep(15);25 26   pid_t id = wait(NULL);27   if (id > 0)28   {29     printf("wait success!\n");30   }31 32   sleep(10);33   return 0;34 }

下面是这段代码的执行结果

在这里插入图片描述
可以看到我们等待成功了

waitpid

waitpid和wait函数的作用差不多 都是等待一个子进程结束

它的函数使用方法如下

pid_t waitpid(pid_t pid, int* status, int options);

它们的返回值是完全一样的 如果等待成功会返回等待进程的pid 如果等待失败则返回-1

这个函数具有三个参数

我们下面一个个介绍它们的作用

pid_t pid

此项参数需要我们填入一个子进程的pid来指定等待该子进程结束

如果我们想等待任意进程都可以 这里可以填-1

int* status

此项参数需要我们填入一个地址

它是一个输出型参数 当我们填入一个地址后该函数会在该地址处写入子进程的退出状态

如果我们不关心这个退出状态 这里可以填写null

int options

此项参数需要我们填入选项

如果我们想要父进程一直等待子进程结束 则可以填入0 (阻塞等待)

如果我们只想父进程问一次子进程有没有结束 则可以填入WNOHANG (非阻塞等待)

status详解

注意: 我们这里只研究的status低16位!

status是一个整型变量 但是我们不能单独的把它看作一个整型

分别三个部分研究更合适

在这里插入图片描述

  1. 高八位表示的是退出状态 即我们的退出码
  2. 低七位表示的是终止信号 如果我们的进程被信号所杀则此处会有终止信号
  3. 第八位表示的是core dump表示 这个我们暂时不需要了解

所以说我们只需要通过一系列的位操作就能够获取到位信号

exitCode = (status >> 8) & 0xFF; //退出码
exitSignal = status & 0x7F;      //退出信号

linux中提供了两个宏来让我们获取退出码和退出信号

它们分别是

  • WIFEXITED(status):用于查看进程是否是正常退出 本质是检查是否收到信号
  • WEXITSTATUS(status):用于获取进程的退出码

我们可以写出两段代码来运用下它们

在这里插入图片描述
运行后结果和预期一致

在这里插入图片描述

阻塞和非阻塞详解

我们下面通过一个小故事 来讲解阻塞和非阻塞的概念

假如现在是期末复习阶段 明天就要考试了 你却完全没有复习
你的一个朋友张三 它复习的特别好 你想要去找他要复习资料
现在你来到张三的宿舍楼下 打电话给张三 让他下来请他去吃个饭 顺便要一下复习资料
张三电话里回复你说 现在还不太方便 还要30分钟才能下楼
假设你现在跟张三说 那你先别挂电话了 我在下面一直等着你 然后你就一直等着 什么事情都不做 这就叫做阻塞等待
假设你现在跟张三说 那你先忙 我先玩会儿游戏 然后你就去打游戏 每隔五分钟再打个电话问张三有没有好 这就叫做非阻塞等待

多进程等待模型

上面的代码都是一个父进程创建了一个子进程

但是事实上我们的一个父进程可以创建多个子进程并且可以等待它们退出

这个叫做多进程的创建和等待的代码模型

代码表示如下
在这里插入图片描述
简单描述下上面的代码

我们创建了十个子进程 并且每个子进程的退出码都不一样

之后我们使用父进程根据储存的子进程pid一个个的等待回收并且接受它们的status

分析出来它们的exit code

演示结果如下

在这里插入图片描述

非阻塞轮询模型

我们前面讲过 父进程的等待分为阻塞等待和非阻塞等待

而阻塞等待的时候我们的父进程是做不了任何事情的 这也是移动十分浪费效率的方式

所以说我们平时推荐大家使用非阻塞轮询方式

我们只需要将waitpid的第三个参数改为WNOHANG就可以了

下面是代码示例

在这里插入图片描述
简单介绍下这段代码

首先这段代码会创建一个子进程 这个子进程会休眠十五秒

父进程会使用非阻塞模式来查询这个子进程有没有死亡

如果返回的结果是0(子进程还在执行)那么父进程就会做自己的事情一秒钟 一秒钟之后再来问

运行结果如下

在这里插入图片描述

进程替换

进程替换是什么

我们在执行一个进程的时候 可能想要这个进程去执行其他程序的代码 我们使用进程替换函数替换当前进程的数据段和代码段的过程就叫做进程替换

为什么要进行进程替换

因为这个进程跑完一部分之后我们想要调用另外一个程序 或者说要使用多种语言执行任务的时候需要用到进程替换

进程替换的原理

用fork创建子进程后 子进程执行的是和父进程相同的程序(但有可能执行不同的代码分支) 若想让子进程执行另一个程序 往往需要调用一种exec函数
在这里插入图片描述
当我们调用函数之后该进程的数据段和代码段全部被替换

并且重载程序计数器 让它从新程序的开头开始执行

当新程序重载时 有没有创建新的进程

答案是否定的 因为新程序的重载仅仅是替换了物理内存的数据段和代码段 并没有改变PCB和mm_struct 所以说并没有创建新的内存 内存的pid还是和原来一样的

当子进程进行程序替换后 会不会影响父进程

不会 虽然说子进程和父进程的数据段和代码段大部分是共享的 但是如果我们修改了子进程的数据段和代码段此时便会发生写时拷贝 从而保证进程之间的独立性

如何进行进程替换

我们可以使用进程替换函数来进行进程替换

我们下面直接写出一个替换函数的使用代码 在初步了解如何使用之后再做详细的讲解

代码如下

在这里插入图片描述
解释下上面的代码

首先会打印一句话 进程正在运行

接着我们会替换进程为 ls 理论上来说 我们后面的语句也不会打印了

在这里插入图片描述
事实上也符合我们的预期

大概了解进程替换函数是怎么一个效果之后我们来了解它们的具体使用

六大替换函数详解

因为所有的替换函数都是以exec开头的函数 它们统称为exec函数

exec函数的返回值

我们都知道 进程的替换有成功和失败两种情况 所以说它的返回值我们也要分两种情况讨论

如果进程替换失败则返回 -1 这个时候可以让我们的被替换的进程知道替换没有成功 从而能够决定下一步怎么走

如果进程替换成功则没有返回值 因为进程替换成功之后原来的进程事实上就不存在了 返回一个值没有任何的意义

对于exec函数来说它们的返回值都遵循我们上面的原则 所以对于下面的函数我们就只讨论它们的参数了

execl

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

我们先看这个函数的名字 相比我们的exec多了一个l

这个l其实就是列表的意思 意味着它的参数要使用列表的形式传入

它的第一个参数是 const char *path 它代表着要执行程序的路径

它的第二个参数是 const char *arg, ... 它代表着可变参数列表 是使用NULL结尾的

例如我们要执行ls程序的话 就可以写出下面的代码

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

execlp

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

我们先看这个函数的名字 相比我们的exec多了一个l 多了一个p

p代表的是path 路径 意味着这个函数能够自动推导路径

它的第一个参数是 const char *file 它代表着要执行的程序名

它的第二个参数是 const char *arg, ... 它代表着可变参数列表 是使用NULL结尾的

例如我们要执行ls程序的话 就可以写出下面的代码

 execlp("ls" , "ls" , "-a" , "-i" , NULL);

execle

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

我们首先看这个函数的名字 相比我们的exec多了一个l 多了一个e

多了的这个e意味着它可以自己配置一个环境变量 我们在自己的函数内部就可以使用我们配置的这个环境变量

它的第一个参数是const char *path 它代表着要执行程序的路径

它的第二个参数是 const char *arg, ... 它代表着可变参数列表 是使用NULL结尾的

它的第三个参数是 *const envp[] 它代表着一个数组 数组里面是我们自己配置的环境变量

例如 我们可以自己设置一个env环境变量 并在我们的程序中使用它

char* envp[] = { "val", NULL };
execle("./mycmd", "mycmd", NULL, envp);

execv

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

我们先看这个函数的名字 相比我们的exec多了一个v

这个v我们可以将它理解为vector 数组的意思 我们传递的参数要以数组的形式传递

它的第一个参数是 const char *path 它代表着要执行程序的路径

它的第二个参数是 char *const argv[] 它代表着一个数组 我们将要执行的命令放在数组中并且以null结尾

例如我们要执行ls程序的话 就可以写出下面的代码

char* myargv[] = { "ls", "-a", "-i", NULL };
execvp("/usr/bin/ls", myargv);

execvp

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

我们先看这个函数的名字 相比我们的exec多了一个vp

这个v我们可以将它理解为vector 数组的意思 我们传递的参数要以数组的形式传递

p代表的是path 路径 意味着这个函数能够自动推导路径

它的第一个参数是 const char *file 它代表着要执行的程序名

它的第二个参数是 char *const argv[] 它代表着一个数组 我们将要执行的命令放在数组中并且以null结尾

例如我们要执行ls程序的话 就可以写出下面的代码

char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execvp("ls", myargv);

execve

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

我们先看这个函数的名字 相比我们的exec多了一个vc

这个v我们可以将它理解为vector 数组的意思 我们传递的参数要以数组的形式传递

这个e意味着它可以自己配置一个环境变量 我们在自己的函数内部就可以使用我们配置的这个环境变量

例如 我们可以自己设置一个env环境变量 并在我们的程序中使用它

char* myargv[] = { "mycmd", NULL };
char* env[] = { "val", NULL };
execve("./mycmd", myargv, env);

替换函数的命名理解

这六个替换函数都是以 exec开头的 我们可以根据它们最后的1~2两个字符来理解它们的参数

  • l (list) 意味着它的参数要使用列表的形式传入以NULL结尾
  • v (vector) 我们传递的参数要以数组的形式传递
  • p (path) 表示能自动搜索环境变量PATH
  • e (env) 表示可以传入自己设置的环境变量

在这里插入图片描述

事实上其实只有execve才是真正的系统调用 其他的函数都是根据execve封装而来的

在这里插入图片描述

思维导图总结

在这里插入图片描述

相关文章:

【Hello Linux】进程控制 (内含思维导图)

作者&#xff1a;小萌新 专栏&#xff1a;Linux 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;简单介绍下进程的控制 包括进程启动 进程终止 进程等待 进程替换等概念 进程控制介绍进程创建fork函数fork函数的返回值fork函数的使用…...

嵌入式linux物联网毕业设计项目智能语音识别基于stm32mp157开发板

stm32mp157开发板FS-MP1A是华清远见自主研发的一款高品质、高性价比的Linux单片机二合一的嵌入式教学级开发板。开发板搭载ST的STM32MP157高性能微处理器&#xff0c;集成2个Cortex-A7核和1个Cortex-M4 核&#xff0c;A7核上可以跑Linux操作系统&#xff0c;M4核上可以跑FreeRT…...

【黄河流域公安院校网络空间安全技能挑战赛】部分wp

文章目录webbabyPHPfunnyPHPEzphp**遍历文件目录的类**1、DirectoryIterator&#xff1a;2、FilesystemIterator:3、**Globlterator**读取文件内容的类&#xff1a;SplFileObjectMisc套娃web babyPHP <?php highlight_file(__FILE__); error_reporting(0);$num $_GET[nu…...

五点CRM系统核心功能是什么

很多企业已经把CRM客户管理系统纳入信息化建设首选&#xff0c;用于提升核心竞争力&#xff0c;改善企业市场、销售、服务、渠道和客户管理等几个方面&#xff0c;并进行创新或转型。CRM系统战略的五个关键要点是&#xff1a;挖掘潜在客户、评估和培育、跟进并成交、分析并提高…...

window.print() 前端实现网页打印详解

目录 前言 一、print()方法 二、打印样式 2.1使用打印样式表 2.2使用媒介查询 2.3内联样式使用media属性 2.4在css中使用import引入打印样式表 三、打印指定区域部分内容 3.1方法一 3.2方法二 3.3方法三 四、强制插入分页 4.1page-break-before&#xff08;指定元素前…...

php程序员应具有的7种能力

php程序员应具有什么样的能力&#xff0c;才能更好的完成工作&#xff0c;才会有更好的发展方向呢&#xff1f;在中国我想您不会写一辈子代码的&#xff0c;那样不可能&#xff0c;过了黄金期&#xff0c;您又怎么办呢&#xff1f;看了本文后&#xff0c;希望对您有所帮助。 一…...

quarkus 生产环境与k8s集成总结

quarkus 生产环境与k8s集成总结 大纲 基础准备quarkus2.13.7脚手架工程配置GraalVM-java11 安装配置配置maven3.8.7linux环境下云原生二进制文件打包环境搭建编译运行quarkus二进制文件quarkus二进制文件制作为docker镜像并运行使用k8s部署quarkus二进制文件 基础准备 生产…...

蓝桥杯训练day2

day21.二分(1)789. 数的范围(2)四平方和&#xff08;1&#xff09;哈希表做法&#xff08;2&#xff09;二分做法(3)1227. 分巧克力&#xff08;4&#xff09;113. 特殊排序(5)1460. 我在哪&#xff1f;2.双指针(1)1238. 日志统计(2)1240. 完全二叉树的权值&#xff08;3&#…...

为什么99%的程序员都做不好SQL优化?

连接层 最上层是一些客户端和链接服务&#xff0c;包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于 TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程 池的概念&#xff0c;为通过认证安全接入的客户端提供线程。同样…...

Jenkins最新版安装调试

清理旧的jenkins&#xff1a; find / -name jenkins* 一项一项的清理&#xff1a;rm -rf /var/log/jenkins* 下载最新版jenkins镜像&#xff1a;jenkins-redhat-stable安装包下载_开源镜像站-阿里云 上传到服务器&#xff1a; 安装命令&#xff1a; yum install -y jenkins…...

简略说一下go的sync.RWMutex锁

在简略的说之前&#xff0c;首先要对RW锁的结构有一个大致的了解 type RWMutex struct {w Mutex // 写锁互斥锁&#xff0c;只锁写锁&#xff0c;和读锁无关writerSem uint32 // sema锁--用于“写协程”排队等待readerSem uint32 // sema锁--用于“读协程”排队…...

软考马上要报名了,出现这些问题怎么办?

目前&#xff0c;四川、山东、山西、辽宁、河北等地已经率先发布了2023年上半年软考报名通知。 四川&#xff1a;2023年3月13日-4月4日 山东&#xff1a;2023年3月17日9:00-4月3日16:00 山西&#xff1a;2023年3月14日9:00-3月28日11:00 辽宁&#xff1a;2023年3月14日8:30…...

单链表(增删查改)

目录一、什么是单链表&#xff1f;二、单链表的增删查改2.1 结构体变量的声明2.2 申请新结点2.2 链表的头插2.3 链表的尾插2.4 链表的头删2.5 链表的尾删2.6 链表的查找2.7 链表的任意位置后面插入2.8 链表的任意位置后面删除2.9 链表的销毁2.10 链表的打印三、代码汇总3.1 SLi…...

端口复用(bind error: Address already in use 问题)

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 端口复用专栏&#xff1a;《Linux从小白到大神》《网络编程》 在前面讲解TCP状态转换中提到过一个2MSL…...

数字化引领乡村振兴,VR全景助力数字乡村建设

一、数字乡村建设加速经济发展随着数字化建设的推进&#xff0c;数字化农业产业正在成为农业产业发展的主导力量&#xff0c;因此数字化技术赋予农业产业竞争力的能力不可小觑。数字化乡村建设背景下&#xff0c;数字化信息技术将全面改造升级农村产业&#xff0c;从农业、养殖…...

【数据结构入门】-链表之双向循环链表

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【数据结构初阶&#xff08;C实现&#xff09;】 文章目录链表初始化打印链表尾插尾删新建一个节点头插头删查找在pos之前插入*删除pos位…...

Jenkins自动化部署入门

Jenkins自动化部署入门 一、简介 Jenkins是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件的持续集成变成可能。 Jenkins自动化部署实现原理 二、Jenkins部…...

Springboot 读取模板excel信息内容并发送邮件, 并不是你想想中的那么简单

Springboot 读取模板excel信息内容并发送邮件 背景技术选型搭建过程数据加密隐藏问题暴露背景追溯解决背景 在我们日常开发中, 会遇到这样一种场景, 就是读取表格中的数据, 并将数据以附件的形式通过邮箱发送到表格中的每个人 即: excel 读取 excel 写入 发送邮件(携带附件), 例…...

蓝桥杯真题31日冲刺 |第一天

蓝桥杯真题31日冲刺 |第一天 一&#xff1a;完全平方数 题目&#xff1a;[链接](完全平方数 - 蓝桥云课 (lanqiao.cn)) 思路&#xff1a; 将 每个 完全平方数都 消掉&#xff0c;剩下的就是 不能构成平方的数 以12 为例&#xff1a; 所以 12 只要再 乘个三 即可满足 代…...

STM32开发(18)----CubeMX配置RTC

CubeMX配置RTC前言一、什么是RTC&#xff1f;RTC时钟源RTC备份域二、实验过程1.CubeMX配置2.代码实现3.实验结果总结前言 本章介绍使用STM32CubeMX对RTC进行配置的方法&#xff0c;RTC的原理、概念和特点&#xff0c;配置各个步骤的功能&#xff0c;并通过实验方式验证。 一、…...

Qt 单例模式第一次尝试

文章目录摘要单例模式如何使用Qt 的属性系统总结关键字&#xff1a; Qt、 单例、 的、 Q_GLOBAL_STATIC、 女神节摘要 世界上第一位电脑程序设计师是名女性&#xff1a;Ada Lovelace (1815-1852)是一位英国数学家兼作家&#xff0c;她是第一位主张计算机不只可以用来算数的人…...

C语言--一维数组

数组概念 数组&#xff1a;是一种构造数据类型&#xff0c;用以处理批量的同种类型的数据。 主要特点&#xff1a;数据量大 &#xff0c;类型相同 一维数组的定义 语法&#xff1a; 类型说明符 数组名[整型常量表达式]&#xff1b; 注意&#xff1a; 方括号里面的内容用于指…...

DataGear 4.5.1 发布,数据可视化分析平台

DataGear 4.5.1 发布&#xff0c;严重 BUG 修复&#xff0c;具体更新内容如下&#xff1a; 修复&#xff1a;修复SQL数据集对于DB2、SQLite等数据源预览时会报错的BUG&#xff1b;修复&#xff1a;修复系统对于MySQL、MariaDB等数据源中无符号数值类型有时报错的BUG&#xff1…...

Springboot——@valid 做字段校验和自定义注解

文章目录前言注意实现测试环境验证自带的注解自定义valid注解自定义注解和处理类创建参数接收类&#xff0c;并增加字段注解接口中使用自测环节正常测试异常测试自定义全局异常监听扩展递归参数下valid不识别的坑前言 再项目开发中&#xff0c;针对前端传递的参数信息&#xf…...

c语言基础练习题详解

&#x1f49e;&#x1f49e; 1.C语言程序的基本单位是&#xff08;C&#xff09;。 A&#xff0e;程序行 B&#xff0e; 语句 C&#xff0e; 函数 D&#xff0e;字符 &#x1f49e;&#x1f49e; 2.已知各变量的类型说明如下&#xff1a; int m6,n,a,b; unsigned long w8;…...

C语言设计模式:实现简单工厂模式和工程创建

目录 一&#xff0c;设计模式概念引入 ① 什么是设计模式 ② 什么是类和对象 ③ 什么是工厂模式 二&#xff0c;C语言工厂模式的实现 ① 普通类和对象的代码实现 ② 工厂模式代码实现 ● cat.c ● dog.c ● person.c ● animal.h ● mainpro.c ● 完善mainpro.c …...

3.6日报

今天进行3.0信号整理工作 做官网后台技术文档 了解grpc gRPC是rpc框架中的一种&#xff0c;是rpc中的大哥 是一个高性能&#xff0c;开源和通用的RPC框架&#xff0c;基于Protobuf序列化协议开发&#xff0c;且支持众多开发语言。 面向服务端和协议端&#xff0c;基于http…...

中文代码88

PK 嘚釦 docProps/PK 嘚釦|,g z docProps/app.xml漅AN??駠(髂v诖m岼侸 魣,g踃$秂D廋Qvf漶x莗笳w?:瘜^?俍欶辇2}?睧汎 t#:?效7治XtA鏊?羄鈋嫿饄攗Tv契"D桷撵vJ鉂?闌 Jg??浱?樱沲gic鋹峡?sū窛葻?]迾?9卑{艏 rk\?洺萹啰N?W??2&quo…...

ElasticSearch 基础(五)之 映射

目录前言一、映射&#xff08;Mapping&#xff09;简介二、动态映射&#xff08;Dynamic mapping&#xff09;1、动态字段映射1.1、日期检测1.1.1、禁用日期检测1.1.2、自定义检测到的日期格式1.2、数值检测2、动态模板三、显示映射&#xff08;Explicit mapping&#xff09;1、…...

【C语言督学训练营 第二天】C语言中的数据类型及标准输入输出

文章目录一、前言二、数据类型1.基本数据类型①.整形②.浮点型③.字符型2.高级数据类型3.数据分类①.常量②.变量三、标准输入输出1.scanf2.printf四、进制转换1.进制转换简介2.十进制转其他进制3.其他进制转换五、OJ网站的使用一、前言 王道2024考研408C语言督学营第二天&…...

甘肃省住房建设厅网站/武汉网站建设优化

目前为止&#xff0c;普通用户只能通过下载Firefoxos Rom刷到自己的android手机上才能进行真实的手机设备端的体验。不过近日&#xff0c;传mozilla已经制造了首批原生搭载firefox os的手机设备&#xff0c;以方便用户和开发者对火狐OS进行更好的评估和协助项目的开发。目前此款…...

建设银行u盾官方网站首页/网站建设推广公司

&#x1f4d6;摘要 今天分享下 —— RSA加密 请求报错&#xff1a;javax.crypto.BadPaddingException: Decryption error&#xff0c;欢迎关注&#xff01; 相关文章阅读&#xff1a;springbootsecurity基于前后端分离的RSA密码加密登录流程 &#x1f302;解决方法 在登录方法里…...

东莞网络营销师培训学校/泰州seo网站推广

数字孪生是一种超出实际的定义&#xff0c;可以被视作一个或好几个主要的、彼此之间依靠的武器装备体系的数据投射系统软件。数字孪生&#xff0c;是以智能化方法为物理学目标建立的虚似实体模型&#xff0c;来仿真模拟其在实际条件中的个人行为。数字孪生是灵活运用概念模型、…...

网站基本功能/seo查询系统

参考大神资料&#xff0c;很赞。 http://www.runoob.com/w3cnote/android-tutorial-decompile-apk-get-code-resources.html 自己找一个APK文件&#xff0c;按照三个小软件工具操作哦 尝试了一个APK里面有几个java代码显示不了。其他的都可以看到...

做网站商城需要什么条件/视频推广

当前位置: 主页 > 编程开发 > Asp.net视频教程 > Asp.net之LINQ入门视频教程 > http://www.xin1234.com/Program/AspnetShiPin/AspNetLINQYmSp/ 1.掌握LINQ中的基本概念 上传日期&#xff1a;2014-09-17 02:57:38 相关摘要&#xff1a; - LINQ有关的语言特性:隐式…...

滨州 网站开发/厉害的seo顾问

[转]do_select()函数分析&#xff0c;理解select(),poll(),poll_wait()函数的关系 2013-10-4阅读282 评论0 Select函数实现原理分析&#xff08;转载&#xff09;select需要驱动程序的支持&#xff0c;驱动程序实现fops内的poll函数。select通过每个设备文件对应的poll函数提供…...