Linux:进程(环境变量、程序地址空间)

目录

冯诺依曼体系结构

操作系统

设计操作系统的目的 

操作系统的管理

进程

PCB

fork

进程状态

进程状态查看

僵尸进程

孤儿进程 

进程优先级 

查看、修改进程优先级命令

竞争、独立、并行、并发

进程切换

活动队列和运行队列 

活动队列

过期队列

active指针和expired指针 

环境变量 

查看环境变量

相关指令 

环境变量的组织方式

代码获取环境变量

程序地址空间

写时拷贝

mm_struct

为什么要有虚拟地址空间? 

进程终止

_exit函数

exit函数

进程等待 

wait

waitpid

进程程序替换


冯诺依曼体系结构

我们常见的计算机、服务器等大多数都遵守冯诺依曼体系

输入设备:键盘、鼠标、摄像头、话筒、网卡、扫描仪等

输出设备:显示器、磁盘、网卡、打印机等

中央处理器(CPU):含有运算器和控制器等

存储器:内存

关于冯诺依曼:

不考虑特殊情况;所有设备都只能和内存打交道


正是因为有了冯诺依曼体系,让当代计算机成为了性价比的产物

一般存储设备

如CPU:存储量小,访问效率快,成本高

如磁盘:存储量大,访问效率相对较慢,成本低

所以有了内存这个中存储,中速度来调节二者,从而让计算机速度更快

如果不考虑经济问题当然可以全部都用CPU级别的速度来造计算机,正是因此冯诺依曼让当代计算机成为了性价比的产物

操作系统

任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)

操作系统是一个管理软硬件资源的软件

操作系统包括:
内核(进程管理,内存管理,文件管理,驱动管理)
其他程序(函数库,shell程序等等)

设计操作系统的目的 

对下:与硬件交互,管理所有的软硬件资源

对上:为软件(应用程序)提供一个良好的执行环境


  • 软硬件体系结构是层状结构
  • 访问操作系统必须使用系统调用(就是函数,只不过是系统提供的函数)
  • 只要一个程序访问了硬件,那么它就必须贯穿整个软硬件体系结构

操作系统不相信任何用户,但我们之所以使用它是因为它会暴露自己的部分接口(系统调用),供上层开发者使用

操作系统的管理

先描述,再组织

例如要对硬件进行管理

我们可以先对硬件进行描述
用一个结构体struct将各个信息进行描述到结构体中

再组织:
用我们学习过的数据结构将n个结构体变量组织起来
具体选择哪个数据结构需要根据实际情况考虑,查找可以使用哈希表,快速插入删除可以使用链表

进程

概念:程序的一个执行实例,正在执行的程序等

内核: 担当分配系统资源(CPU时间,内存)的实体

PCB

进程信息被放在一个进程控制块的数据结构中,是进程属性的结合,我们称之为PCB,Linux操作系统下的PCB是:task_struct

Linux中进程控制块PCB-------task_struct结构体结构 - 童嫣 - 博客园

在Linux中描述进程的结构体叫做task_struct

task_struct是Linux内核的一种数据结构,它会被装在到RAM(内存)里并且包含着进程的信息

  • 标⽰符: 描述本进程的唯⼀标⽰符,⽤来区别其他进程。
  • 状态: 任务状态,退出代码,退出信号等。
  • 优先级: 相对于其他进程的优先级。
  • 程序计数器: 程序中即将被执⾏的下⼀条指令的地址。
  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
  • 上下⽂数据: 进程执⾏时处理器的寄存器中的数据。
  • I∕O状态信息: 包括显⽰的I/O请求,分配给进程的I∕O设备和被进程使⽤的⽂件列表。
  • 记账信息: 可能包括处理器时间总和,使⽤的时钟数总和,时间限制,记账号等。
  • 其他信息

所有运行在系统里的进程都以task_struct链表的形式存在内核里


进程id:PID

父进程id:PPID

我们可以在代码中用getpid和getppid来查看当前进程的pid和ppid

它需要 sys/types.h 和 unistd.h两个头文件 

例如:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{printf("pid: %d\n", getpid());printf("ppid: %d\n", getppid());return 0;
}

这里可以看出当前进程运行后它的pid为32752,ppid为32488


查看进程 

进程信息可以通过/proc系统文件夹查看

这些蓝色的数字就是当前Linux系统下各个进程的pid

我们可以通过ps命令查看我们想要看的进程信息

ps ajx

作用是显示系统中所有用户的所有进程 

我们可以先把头第一行的头过滤出来,然后通过grep过滤专门来看我们想要查看的进程信息

// filename为文件名
ps ajx | head -1 && ps axj | grep filename
// pid为进程id
ps ajx | head -1 && ps axj | grep pid

我们也可以循环查看

// filename为文件名
while :; do ps ajx | head -1 && ps axj | grep filename; sleep 1; done
// pid为进程id
while :; do ps ajx | head -1 && ps axj | grep pid; sleep 1; done

fork

fork是一个系统调用

 

它的作用是创建一个子进程

fork有两个返回值

首先fork是有返回值的

在fork函数内部return之前就已经完成了子进程的创建,子进程会接着fork接下来的语句和父进程一起执行,所以就出现了一句代码有两个返回值的情况

如果是父进程,则返回值为子进程的pid

如果是子进程,则返回值为0

若fork出错,则返回负数


父子进程代码共享,数据各自开辟一份,私有一份(采用写时拷贝)

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{int ret = fork();printf("hello proc : %d!, ret: %d\n", getpid(), ret);sleep(1);return 0;
}

这里的printf语句执行了两次,足以证明有两个进程同时执行了这个printf语句

 fork之后我们可以利用返回值进行用if语句进行分流

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{int ret = fork();if(ret < 0) // 出错{perror("fork");return 1;}else if(ret == 0) //child{    printf("I am child : %d!, ret: %d\n", getpid(), ret);}else //father{ printf("I am father : %d!, ret: %d\n", getpid(), ret);}return 0;
}

进程状态

一个进程在运行时可能会有多种状态

static const char* const task_state_array[] = {"R (running)", /*0 */"S (sleeping)", /*1 */"D (disk sleep)", /*2 */"T (stopped)", /*4 */"t (tracing stop)", /*8 */"X (dead)", /*16 */"Z (zombie)", /*32 */
};

R运行状态(running):表明进程要么是在运行中,要么在运行队列里

S睡眠状态(sleeping):意味着进程在等待事件完成(也叫做可中断睡眠状态)

D磁盘休眠状态(Disk sleep):也叫做不可中断睡眠状态,在这个状态的进程通常会等待IO的结束

T停止状态(stopped):可以通过发送SIGSTOP信号给进程来停止T状态下的进程。这个进程可以通过发送SIGCONT信号让进程继续运行

X死亡状态(dead):这个状态只是一个返回状态,不会在任务列表中看到这个状态

Z僵死状态(Zombies):当进程退出并父进程没有读取到子进程退出的返回代码就会产生僵死进程

进程状态查看

ps aux / ps axj 命令
  • a:显⽰⼀个终端所有的进程,包括其他用户的进程。
  • x:显⽰没有控制终端的进程,例如后台运⾏的守护进程。
  • j:显⽰进程归属的进程组ID、会话ID、⽗进程ID,以及与作业控制相关的信息
  • u:以用户为中⼼的格式显⽰进程信息,提供进程的详细信息,如用户、CPU和内存使⽤情况等

僵尸进程

只要子进程还在退出,父进程还在运行,并且父进程也没有读取子进程的状态,则子进程进入Z状态,成为僵尸进程

僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码

如果父进程一直不回收子进程,就会造成内存资源的浪费,因为它的PCB资源需要一直维护,C中定义一个结构体变量也是需要占用内存的

孤儿进程 

如果父进程先退出,那么这个子进程就称为孤儿进程

孤儿进程会被1号init进程领养,被1号进程回收

进程优先级 

cpu资源分配的先后顺序,就是指进程的优先权

ps -l

使用该命令可以查看系统进程信息

UID:代表执行者的身份

PID:代表进程的代号

PPID:代表父进程的代号

PRI:代表这个进程可被执行的优先级,值越小越早被执行

NI:代表这个进程的nice值

nice值表示进程可被执行的优先级的修正数值

PRI(new) = PRI(old) + nice

当这个PRI(new)越小就会越早被执行

所以,调整进程优先级,就是调整进程的nice值

nice值的取值范围是-20 ~ 19,一共40个级别

查看、修改进程优先级命令

先用top可以查看各进程的优先级

进入top后按 "r" ,输入进程pid,输入nice值即可完成修改优先级

也可以使用nice,renice命令、系统调用调整优先级

竞争、独立、并行、并发

竞争性:系统进程数目众多,而CPU资源只有少量,甚⾄1个,所以进程之间是具有竞争属性的。
为了⾼效完成任务,更合理竞争相关资源,便具有了优先级
独⽴性:多进程运行,需要独享各种资源,多进程运行期间互不⼲扰
并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行
并发:多个进程在⼀个CPU下采⽤进程切换的⽅式,在⼀段时间之内,让多个进程都得以推进,称之为并发

进程切换

一个进程一旦占有了CPU,它的运行时间是有限的,这个时间可以叫做时间片

时间片:当代计算机都是分时操作系统,没有进程都有它合适的时间片(其实就是⼀个计数
器)。时间片到达,进程就被操作系统从CPU中剥离下来。

当进程切换的时候,操作系统会保存进程的上下文数据,当下一次轮到该进程运行时再将该数据恢复

保存到了task_struct里

活动队列和运行队列 

活动队列

时间片还没有结束的所有进程都按照优先级放在活动队列

nr_active:总共有多少个运行状态的进程

queue[140]:一个元素就是一个进程队列,下标就是优先级,相同优先级按照FIFO规则进行排队调度

bitmap[5]:为了提高查找非空队列的效率,用5*32个比特位表示队列是否为空,5*32 > 140可以表示每一个下标是否为空

过期队列

过期队列和活动队列的结构一模一样

过期队列上放置的进程都是时间片耗尽的进程

当活动队列上的进程都被处理完毕后,对过期队列的进程进行时间片重新计算(过期队列变活动队列,活动队列变过期队列)

active指针和expired指针 

active指针永远指向活动队列,expired指针永远指向过期队列

所以当活动队列的进程都被处理完毕后, 过期队列变活动队列,活动队列变过期队列的本质就是交换active和expired指针

环境变量 

环境变量一般指在操作系统中用来指定操作系统运行环境的一些参数

我们在编写代码的时候,链接时,从来都不知道我们所链接的动静态库在哪,但是照样可以链接成功,原因就是因为有相关环境变量帮助编译器进行查找

查看环境变量

env查看全部环境变量

echo $NAME(NAME是指定环境变量的名称)

相关指令 

echo $NAME:显示某个环境变量值

export:设置一个新的环境变量

env:显示所有环境变量

unset:清楚环境变量

set:显示本地定义的shell变量和环境变量

环境变量的组织方式

每个程序都会收到一张环境变量表,环境表是一个字符指针数组,每个指针指向一个以'\0'为结尾的环境字符串

代码获取环境变量

1. 命令行第三个参数 

#include <stdio.h>int main(int argc, char *argv[], char *env[])
{int i = 0;for(; env[i]; i++){printf("%s\n", env[i]);}return 0;
}

 2. 通过第三方变量environ获取

#include <stdio.h>int main(int argc, char *argv[])
{extern char **environ;int i = 0;for(; environ[i]; i++){printf("%s\n", environ[i]);}return 0;
}

libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时要用extern声明

3. 通过系统调用获取环境变量

#include <stdio.h>
#include <stdlib.h>int main()
{printf("%s\n", getenv("PATH"));return 0;
}

程序地址空间

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int g_val = 0;int main()
{pid_t id = fork();if(id < 0){perror("fork");return 0;}else if(id == 0){ //childg_val = 100;printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);}else{ //parentprintf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);}sleep(1);return 0;
}

当我们执行上面的代码时,我们会存在一对父子进程

但是我们在子进程将g_val的值改成100时,父进程的g_val没有任何变化,说明了进程的独立性

但是它们的g_val的地址却一样的?地址一样值却不相同,能说明:

  • 变量内容不一样,父子进程输出的变量绝对不是同一个变量
  • 地址值一样,说明绝对不是物理地址

我们在用C/C++语言所看到的地址,全部都是虚拟地址,物理地址用户都看不到,由操作系统OS同一管理

OS必须负责将虚拟地址转化为物理地址

每个进程都有自己独立的虚拟地址空间

操作系统会通过物理内存对虚拟地址空间在页表中进行映射关系,这样就能通过页表找到该存放的物理内存

所以即使两个程序的虚拟地址相同,但通过页表映射后它们对应的物理内存空间的地址是不一样的,所以就会出现虚拟地址相同但值不同的情况

写时拷贝

父子进程的代码共享,父子不再写入时,数据也是共享的,当任意一方试图写入时,便以写时拷贝的方式各自一份副本 

 因为有写时拷贝技术的存在,所以父子进程得以彻底分离,保证了进程的独立性

mm_struct

描述Linux下进程的地址空间的所有信息的结构体都是mm_struct(内存描述符)

 mm_struct结构是对整个用户空间的描述,每一个进程都会有自己独立的mm_struct,这样每一个进程都会有自己独立的地址空间能够互不干扰

为什么要有虚拟地址空间? 

如果没有虚拟地址空间,那进程就是直接在物理内存中操作

1. 安全风险

每个进程都可以访问任意的内存空间,这也就意味着任意一个进程都能够读写系统相关内存区域,如果是一个木马病毒,就能直接让设备瘫痪

2. 地址不确定

当运行时直接使用物理地址,我们无法确定内存现在使用到了哪里,也就是说拷贝的实际内存地址每一次运行都是不确定的

3. 效率低下 

如果直接使用物理地址,出现物理内存不够用的情况,我们一般是将不常用的进程拷贝到磁盘的交换分区中腾出内存,如果是物理地址的话就需要整个进程一起拷走这样时长太高

进程终止

进程终止的本质是释放系统资源,就是释放进程申请的相关内核数据结构和对应的数据和代码

进程退出我们可以通过 echo $? 查看进程退出码

_exit函数

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

status定义了进程的终止状态,父进程通过wait来获取该值 

_exit是系统调用,它刷新的是系统级缓冲区 

exit函数

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

exit最后也会调用_exit函数,但在调用_exit之前,还做了其他工作

关闭所有打开的流,所有在应用层级的缓存数据均被写入

最后调用_exit函数

进程等待 

wait

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);

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

参数是个输出型参数,可以获取子进程的退出状态,不关心可以设置为NULL

调用wait的父进程会随机等待任意一个子进程

waitpid

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

成功则返回被等待进程的pid,失败则返回-1,如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0

参数:

pid表示需要等待的子进程,若为-1则表示等待任意一个子进程,与wait等效

status输出型参数,WIFEXITED:若为正常终止子进程返回的状态,则为真(查看进程是否正常退出),WEXITSTATUS:若WIFEXITED非零,提取子进程退出码(查看进程的退出码)

options:默认为0,表示阻塞等待。WNOHANG:若pid指定的子进程没有结束,则waitpid函数返回0,不予等待。若正常结束,则返回该子进程的ID


status参数不能简单的当作整形来看待,可以当作位图来看待

若正常退出,低7位比特位为0,8-15表示退出状态

若异常退出,低7位表示终止进程的信号,这时候的退出码则毫无意义

进程程序替换

fork之后,父子进程会执行同一个程序,但是我们也可以通过程序替换让子进程执行其他程序的代码

子进程往往要调用一种exec函数以执行另一个程序,当用户执行一种exec函数时,该进程的用户空间代码和数据会被新进程替换,从新进程启动例程开始执行

调用exec并不创建新进程,所以进程id不会改变

#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[]);

返回值:

调用出错则返回-1,成功直接执行新代码,没有成功返回值

这些函数非常多,但是只要掌握了命名风格就很容易记住了,除了exec之外,还有l、p、e、v,分别具有不同含义

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

#include <unistd.h>int main()
{char *const argv[] = {"ps", "-ef", NULL};char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};execl("/bin/ps", "ps", "-ef", NULL);// 带p的,可以使⽤环境变量PATH,⽆需写全路径execlp("ps", "ps", "-ef", NULL);// 带e的,需要⾃⼰组装环境变量execle("ps", "ps", "-ef", NULL, envp);execv("/bin/ps", argv);// 带p的,可以使⽤环境变量PATH,⽆需写全路径execvp("ps", argv);// 带e的,需要⾃⼰组装环境变量execve("/bin/ps", argv, envp);exit(0);
}

这些函数中,只有execve是系统调用,其它的五个函数最终都是调用的execve


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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ansible自动化运维(一)简介及清单,模块

相关文章ansible自动化运维&#xff08;二&#xff09;playbook模式详解-CSDN博客ansible自动化运维&#xff08;三&#xff09;jinja2模板&&roles角色管理-CSDN博客ansible自动化运维&#xff08;四&#xff09;运维实战-CSDN博客 ansible自动化运维工具 1.什么是自…

DuckDB快速入门教程

DuckDB是一个用c编写的进程内OLAP DBMS&#xff0c;太复杂了。我们从简单的开始&#xff0c;好吗&#xff1f;DuckDB是用于分析的SQLite。它没有依赖关系&#xff0c;非常容易设置&#xff0c;并且经过优化可以对数据执行查询。本文将介绍什么是DuckDB&#xff0c;如何使用它&a…

【橘子容器】如何构建一个docker镜像

你肯定打过docker镜像是吧&#xff0c;作为一个开发这很正常&#xff0c;那么你用的什么打包方式呢&#xff0c;这里我们来梳理几种常用的docker镜像构建方式。 ps&#xff1a;这里不是太讲原理&#xff0c;更多的是一种科普和操作。因为讲原理的东西网上已经够多了。 一、Dock…

操作系统(8)死锁

一、概念 死锁是指在一个进程集合中的每个进程都在等待只能由该集合中的其他进程才能引起的事件&#xff0c;而无限期地僵持下去的局面。在多任务环境中&#xff0c;由于资源分配不当&#xff0c;导致两个或多个进程在等待对方释放资源时陷入无限等待的状态&#xff0c;这就是死…

el-table行合并及合并后序号处理

效果图 <el-tableclass"ncky-detail-table"v-loading"tableLoading"border:data"tableDataVo":span-method"objectSpanMethod"row-key"uniqueFlag":row-class-name"tablerowclassname"><el-table-column…

【Python】使用Selenium的find_element模块获取网页上的大段文字和表格的方法(建议收藏!)

发现了一个使用Selenium的find_element模块&#xff0c;快速获取文字和表格的方法&#xff0c;很实在&#xff0c;以后爬网的时候&#xff0c;就不用beautifulSoup 和 pandas的read_html 混起来用了&#xff01; 文字部分&#xff1a;实现网络节点下&#xff0c;某个节点下的其…

24秋:模式识别:答题第一波除解析

判断题&#xff1a; T 模式识别中的人脸识别问题是根据已知数据类别预测未知数据类别的问题。F 人脸图像在计算机中是以矩阵的方式存储的。F 训练集和测试集有交集。T 算法&#xff08;模型&#xff09;是以样本所包含的信息为基础&#xff0c;对总体的某些特征进行判断、预测…

【蓝桥杯每日一题】推导部分和——带权并查集

推导部分和 2024-12-11 蓝桥杯每日一题 推导部分和 带权并查集 题目大意 对于一个长度为 ( N ) 的整数数列 ( A 1 , A 2 , ⋯ , A N A_1, A_2, \cdots, A_N A1​,A2​,⋯,AN​ )&#xff0c;小蓝想知道下标 ( l ) 到 ( r ) 的部分和 ∑ i l r A i A l A l 1 ⋯ A r \su…

<项目代码>YOLOv8 车牌识别<目标检测>

项目代码下载链接 &#xff1c;项目代码&#xff1e;YOLOv8 车牌识别&#xff1c;目标检测&#xff1e;https://download.csdn.net/download/qq_53332949/90121387YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题…

STM32 CubeMx HAL库 独立看门狗IWDG配置使用

看门狗这里我就不多介绍了&#xff0c;能搜到这篇文章说明你了解 总之就是一个单片机重启程序&#xff0c;设定好超时时间&#xff0c;在超时时间内没有喂狗&#xff0c;单片机就会复位 主要应用在单片机异常重启方面&#xff0c;比如程序跑飞&#xff08;注意程序跑飞时你就…

实现某海外大型车企(T)Cabin Wi-Fi 需求的概述

最近参与某海外大型车企&#xff08;T&#xff09;的 Wi-Fi 功能需求开发&#xff0c;T 提出了一个 Cabin Wi-Fi 的概念&#xff0c;首先我们先对 Cabin Wi-Fi 进行一个较全面的了解。 1. Cabin Wi-Fi 概念概述 Cabin Wi-Fi 通常指用于飞机客舱、火车车厢、豪华巴士或船舶上的无…

OpenAI直播发布第4天:ChatGPT Canvas全面升级,免费开放!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;专注于分享AI全维度知识&#xff0c;包括但不限于AI科普&#xff0c;AI工…

【Linux课程学习】:第二十一弹---深入理解信号(中断,信号,kill,abort,raise,larm函数)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;Linux课程学习 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 ​ Linux学习笔记&#xff1a; https://blog.csdn.…

2021 年 6 月青少年软编等考 C 语言四级真题解析

目录 T1. 数字三角形问题思路分析T2. 大盗思路分析T3. 最大子矩阵思路分析T4. 小球放盒子思路分析T1. 数字三角形问题 上图给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,你的任务就是找到最大的和。 注…

购物车案例--分模块存储数据,发送请求数据渲染,底部总计数量和价格

shift鼠标右键&#xff0c;打开powershell&#xff0c;新建项目 自定义 只有一个页面&#xff0c;不涉及路由&#xff0c;勾选vuex,css,babel 无需保存预设 回车项目开始创建 项目用vscode打开 将src里的内容全部清空 将第七天的课程准备代码复制粘贴到src中 刷新页面&…

内网是如何访问到互联网的(华为源NAT)

私网地址如何能够访问到公网的&#xff1f; 在上一篇中&#xff0c;我们用任意一个内网的终端都能访问到百度的服务器&#xff0c;但是这是我们在互联网设备上面做了回程路由才实现的&#xff0c;在实际中&#xff0c;之前也说过运营商是不会写任何路由过来的&#xff0c;那对于…

C++编程: 基于cpp-httplib和nlohmann/json实现简单的HTTP Server

文章目录 0. 引言1. 完整实例代码2. 关键实现3. 运行与测试 0. 引言 本文基于 cpp-httplib 和 nlohmann/json 实现简单的 HTTPS Server 实例代码&#xff0c;这两个库均是head-only的。 1. 完整实例代码 如下实例程序修改自example/server.cc #include <httplib.h>#i…

收银pos源代码(Win版+安卓版)

1.收银pos版本 支持市面上主流系统版本&#xff0c;如支持win版&#xff08;exe安装包&#xff09;、安卓版&#xff08;apk安装包&#xff09;&#xff1b; 2.多样化收银 支持Windows收银机、安卓收银机、ai智能称重、收银称重一体机、无人自助收银、手机端收银等&#xff…

springboot项目如何运行起来

时常开发好的springboot项目是如何运行起来的&#xff1f; 经常会使用到打包插件spring-boot-maven-plugin SpringBoot提供了一个插件spring-boot-maven-plugin用于把程序打包成一个可执行的jar包。在pom文件里加入这个插件即可&#xff1a; org.springframework.boot spring-b…

ubuntu18.04配置实时内核

ubuntu系统&#xff1a;18.04 当前内核&#xff1a;5.4.0-84-generic 待安装实时内核&#xff1a; 5.6.19-rt11 1、查看当前版本 uname -r 2、下载内核与补丁 一种方式从官网自己下载 官方内核下载地址官方补丁下载地址阿里镜像内核下载地址&#xff08;速度快&#xff0…