深入了解linux系统—— 进程控制
进程创建
fork
函数
在Linux
操作系统中,我们可以通过fork
函数来创建一个子进程;
这是一个系统调用,创建子进程成功时,返回0
给子进程,返回子进程的pid
给父进程;创建子进程失败则返回-1
给父进程。
我们就可以通过返回值来对父子进程进行分流:
#include <stdio.h>
#include <unistd.h>
int main()
{int id = fork();if(id < 0){//创建子进程失败perror("fork");return 1;}else if(id == 0){//子进程printf("子进程, pid : %d\n",getpid());}else{//父进程printf("父进程, pid : %d\n",getpid());}return 0;
}
使用fork
函数创建子进程,这里简单复习一下就OK了;
我们来看通过fork
创建子进程,操作系统内核做了什么:
- 首先就是分配新的内存块和内核数据结构给子进程
- 然后就是将父进程部分数据结构内容拷贝给子进程
- 将子进程添加到系统进程列表当中
fork
返回,调度器开始调度
这里fork
之前我们的父进程独立执行,fork
创建子进程之后,父子进程两个执行流就分别执行。
写时拷贝
我们知道,当创建子进程之后,父子进程的代码和数据是共享的,而当有一个进程(父进程/子进程)要进行数据修改时,我们的操作系统就会进行写时拷贝,重新开辟一块空间给这个进程,然后修改它的页表(虚拟地址和物理地址)的对应关系;这样就完成了写时拷贝。
那操作系统是如何知道一段代码和数据现在是共享的呢?
这个问题就比较简单了,在我们的页表中不仅存放了虚拟地址和物理地址的对应关系,还存在对这一块内存的权限(r
、w
等)
当我们通过
fork
创建子进程之后,我们的父子进程共享代码和数据,操作系统就会将我们父子进程对代码和数据的权限修改为只读;当我们应该进程想要修改数据时,操作系统通过页表发现进程对数据的权限为只读,操作系统就会报错,然后给我们进程重新开辟一块空间,然后将数据拷贝过来,再修改我们进程页表的地址映射关系;
而写时拷贝之后,操作系统就会将我们父子进程对代码和数据的权限修改为可读可写。这样我们进程之间就不会相互影响了。
写时拷贝:是一种延时申请技术,可以提高整机内存的使用率
fork
函数的常规使用
我们之前使用fork
来创建子进程,然后让父子进程分流执行不同的代码,这是fork
常规使用的一种,也就是:父子进程执行不同的代码段。
除此之外呢,我们还可以通过fork
创建进程,然后通过调用exec
系列函数,让子进程执行不同的程序。
- 创建子进程,让子进程执行不同的代码段
- 创建子进程,让子进程执行不同的程序
fork
失败的原因
我们知道fork
创建子进程成功,返回0
给子进程,返回子进程的pid
给父进程;而创建子进程失败则返回-1
给父进程。
那fork
为什么会失败呢?
简单来说就是:
- 系统中存在太多的进程
- 实际用户的进程数量超过了限制
进程终止
进程退出
在我们执行一个程序时,这个程序可能代码运行完了,且结果符合我们的预期;也可能代码运行完了,但是结果不符合我们的预期;当然也可能我们的程序代码根本就没有运行完,而是中途退出了。
所以进程退出,也就存在三种可能:
- 代码运行完,结果正确;
- 代码运行完,结果不正确;
- 代码异常终止;
进程常见退出方法
那我们知道了进程退出有三种可能;那我们进程如何退出呢?
- 从
main
函数返回 exit
函数_exit
当然我们使用Ctrl + C
杀死一个进程属于进程的异常退出;通过信号让进程终止也属于异常退出。
exit
函数
通过查看手册,可以发现exit
函数是3
号手册,也就是C
标准库的库函数;
那它的作用就是,退出一个进程,然后并返回退出码;像我们之前在C
中执行的exit(1)
就是退出程序并返回1
。
_exit
系统调用
通过查看手册,我们发现_exit
位于2
号手册,也就是系统调用;
它的作用也是退出进程,并且返回退出码。
exit
和_exit
的区别
那这两个函数都是退出一个进程,那有什么不同之处呢?
首先,
_exit
是操作系统提供的进程退出的函数,而exit
是库函数,exit
可以说是对_exit
的封装,在进程退出之前做了一些其他的事情。这里直白一点,直接说了:
exit
底层也会调用_exit
函数,但是在调用_exit
函数之前,还做了:
- 执行用户通过
atexit/on_exit
定义的清理函数;- 关闭所有打开的流,所有缓存数据均被写入。(简单理解就是刷新缓冲区)
- 然后再调用
_exit
。
这里我们不懂atexit/on_exit
这些,但是我们好像知道缓冲区,现在来看下面代码,通过exit
和_exit
退出的区别:
#include <stdio.h>
#include <stdlib.h>
int main()
{printf("hello linux");exit(1);//_exit(1);return 0;
}
我们知道,在输出时,缓冲区是按行刷新的,这里我们输出hello linux
没有换行,数据就在缓冲区当中;
return
退出和exit
退出的区别
我们知道在main
函数中,return
也是退出进程,exit
也可以退出进程,那它们有什么区别呢?
在
main
函数中,没有什么区别,都是进程退出;在其他函数中,
return
是退出当前函数,而exit
是直接退出进程。
退出码
退出码,它可以告诉我们最后一次执行的命令(程序)的状态;
我们可以使用echo $?
来查看最近一次程序退出的退出码:
#include <stdio.h>
int main()
{return 1;
}
当程序退出时,退出码为
0
通常表示程序执行成功,没有问题;如果退出码不是
0
就认为程序运行不成功。
这里也要记住一个点:当程序异常终止时,程序的退出码是没有意义的。
进程等待
为什么要等待
- 在进程状态中,我们了解到了僵尸进程,但是我们只是知道子进程比父进程先退出,会造成子进程僵尸状态,但是我们并不知道如何去解决僵尸进程;(这里我们需要让父进程等待,去解决僵尸进程问题,且获取子进程退出时的退出信息)
- 此外,当一个进程进入僵尸状态,我们是无法杀死一个僵尸进程的;因为这个进程已经退出了,只不过保留了
task_struct
等待父进程获取退出信息。 - 创建子进程是让子进程去执行父进程派给子进程的任务,在子进程退出后,无论是正常退出还是异常退出,都要让父进程知道子进程的运行结果。
所以我们要让父进程通过进程等待,来回收子进程的资源,获取子进程的退出信息。
如何等待
那我们如何让父进程等待呢?
通过系统调用wait
和waitpid
;
wait
方法
wait
只有一个参数,这个status
是一个输出型参数,简单来说就是:我们想要让父进程获得子进程的退出信息,就传递一个int*
指针,这样在函数调用结束后,父进程就拿到了子进程的退出信息;(如果不需要获取子进程退出信息,可以传NULL
)。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{int id = fork();if(id < 0)return 1;else if(id == 0){//子进程int cnt = 3;while(cnt--){printf("子进程, pid : %d, ppid : %d\n",getpid(),getppid());sleep(1);}exit(1);}//父进程sleep(5);wait(NULL);while(1){ }return 0;
}
看上述代码,我们让子进程循环进行三次,每次打印一句话,然后
sleep
1秒钟;循环运行结束直接退出;父进程先
seep
5秒钟,然后再进程等待wait
(这里我们先不获取子进程退出信息,传NULL
)。我们预期的结果是:子进程运行三秒,然后子进程退出,而我们的父进程先睡眠
5
秒,所以子进程有两秒是处于僵尸状态的;在父进程睡眠结束时,进入等待,然后子进程结束僵尸状态。这里为了方便我们查看,父进程等到结束之后,我们让父进程进入死循环。
这里对于返回值,如果wait
执行成功则返回子进程的pid
给父进程,失败则返回-1
给父进程。
waitpid
方法
waitpid
进程等待的方法,与wait
不同的是:waitpid
多了两个参数:pid
和option
;
首先是pid
这里我们主要就看
-1
和<0
的部分:
-1
:当我们传-1
时,就表示我们当前父进程要等待任意个子进程结束。
>0
:我们父进程等待某一个子进程时,我们可以传子进程的pid
,让父进程等待某一个子进程
现在来看waitpid
的第三个参数:options
options
参数默认为0
,它表示阻塞等待;什么意思呢?简答来说,就是父进程在调用
waitpid
时,如果要等待在子进程还没有结束,那父进程就在waitpid
中阻塞掉,直到子进程退出,waitpid
再返回。而我们也可以传
WNOHANG
,当我们传WNOHANG
时,如果要等待的子进程还没有结束,waitpid
就直接0
,我们的父进程不会在waitpid
中阻塞掉;如果子进程结束,那就返回子进程的pid
。
这里举个例子:
最近周末,你要和女朋友出去旅游,现在要出发了,你已经准备好了,你来到了女朋友的宿舍楼下,要等你女朋友下楼;
你给她打了电话过去,询问她好了没有,她说没有,然后你就挂了;拿起手机玩起来了游戏了;玩了一局游戏,你又给你女朋友打了过去,然后她说还没有,然后你又挂了,又开了一局游戏。打完又给你女朋友打去了电话询问她好没好,她是快了马上,说让你不用挂电话,她准备好了给你说。
然后你没有挂电话,就拿着手机等待你女朋友给你说她准备好了。
这里你打一次电话询问你女朋友好了没有,没有然后你就挂了电话;这本质上不就是一次非阻塞等待吗。(你女朋友没有好,然后你就挂了电话)
而当你打电话询问你女朋友好了没有,知道了没有你并没有挂断电话,而是拿着手机等待你女朋友准备好。这不就是一次阻塞等待吗。
而体现到我们父进程等待上面就是,父进程调用
waitpid
,如果子进程没有结束,waitpid
没有返回,而是等待子进程退出后再返回,再次期间父进程就阻塞在了waitpid
中;这不就是阻塞等待吗。而父进程调用
waitpid
,如果子进程没有结束,函数直接返回0
,父进程可以去做自己的事情,这不就是非阻塞等待吗。
阻塞等待这里就不演示了,现在看一下非阻塞等待:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{int id = fork();if(id == 0){//子进程int cnt = 3;while(cnt--){printf("子进程, pid : %d, ppid : %d\n",getpid(),getppid());sleep(1);}exit(1);}//父进程printf("wait begin\n");while(waitpid(-1,NULL,WNOHANG) == 0){printf("父进程: pid : %d\n",getpid());sleep(1);}printf("wait end\n");sleep(10); return 0;
}
一般情况下,我们使用非阻塞调用要轮循调用waitpid
,再等待过程中,父进程可以执行自己的代码(比如要完成一个或多个任务)。
获取子进程的status
在上述中,我们进程等待解决了僵尸进程的问题;但是我们进程等待不止可以解决子进程的僵尸问题,还可以让父进程获取子进程的退出信息。
在上述中,我们没有获取子进程的退出信息,所以传的参数是
NULL
,现在我们来获取一下子进程的退出信息。
我们先来看以下代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{int id = fork();if(id < 0)return 1;else if(id == 0){//子进程printf("子进程, pid : %d, ppid : %d\n",getpid(),getppid());exit(1);}//父进程int status = 0;wait(&status);//阻塞等待//waitpid(-1,&status,0);printf("child status : %d\n",status);return 0;
}
我们发现,我们子进程的退出码是
1
,但是我们拿到的status
是256
;这是为何?
这是因为,虽然status
是类型是int
;但是我们不能将它当做一个整形来看待,而是当做一个位图;
int
类型4
字节,也就是32
个bit
位;我们要将这32
个bit
位划分成以下三部分:
其中,高
16
位没有使用,我们不考虑而使用到的低
16
位中的高8
位表示进程退出时的退出码;低8
位表示进程的退出信号
退出码:
先来看退出码的区域:
退出信号:
现在来看退出信号部分,在退出信号部分中,还存在着一个标识位core dump
;
这里注意:如果进程是异常退出(被信号杀死),那它的退出码就没有任何意义了。
就好比考试作弊被发现了,考试成绩就没有意义了。
进程异常退出,程序都没有执行完,那退出码就没有什么意义了。
我们了解了status
,那我们如果想要通过status
获得进程的退出码或者退出信号呢?
这里就要涉及到位操作了;
在获取退出码和退出信号之前,我们要先判断一下进程是否是被信号杀死的。
我们只需判断
status
按位与上ox7F
(0111 1111
),判断退出信号是否为0
即可(因为没有0
号信号)。
首先获取退出码:
我们只需让
status>>8
然后再按位与&
上0xFF
(1111 1111
)即可获得退出码。
获取退出信号:
在上述中其实已经描述了,
status
按位与&
上0x7F
(0111 1111
)即可获得进程退出信号。如果进程退出信号为
0
那就表示进程是正常退出的,因为不存在0
号信号。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{int id = fork();if(id < 0)return 1;else if(id == 0){//子进程printf("子进程, pid : %d, ppid : %d\n",getpid(),getppid());exit(1);}//父进程int status = 0;wait(&status);printf("child status : %d\n",status);printf("status exit code : %d\n",(status)&0xFF);printf("status exit signal : %d\n",status&0x7F);return 0;
}
可以看到,这样我们的确获得了进程的退出码和退出信息。
当然在操作系统中还存在一些宏,我们可以直接使用这些宏来获取退出码和退出信息:
退出码:
WIFEXITED(status)
:判断程序是否正常退出;返回
true
就表示程序正常退出;
WEXITSTATUE(status)
:当进程正常退出时,可以通过WEXITSTATUE
获取进程的退出码。退出信号:
WTERMSIG(status)
:当进程异常退出时,可以使用WTERMSIG
获取当前进程的退出信号。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{int id = fork();if(id < 0)return 1;else if(id == 0){//子进程printf("子进程, pid : %d, ppid : %d\n",getpid(),getppid());exit(1);}//父进程int status = 0;pid_t rid = wait(&status);if(WIFEXITED(status)){//进程正常退出printf("wait succes,rid : %d, exit code : %d\n",rid,WEXITSTATUS(status));}else{ //进程异常退出printf("status exit signal : %d\n",WTERMSIG(status));} return 0;
}
进程切换
在我们之前创建子进程时,我们创建的子进程执行的代码,都是父进程代码的一部分;
如果我们想要让我们子进程执行不同的代码,执行新的程序呢?
此时,我们就要一个进程切换,让子进程执行新的程序;
先来看一段进程切换的简单代码:
#include <stdio.h>
#include <unistd.h>
int main()
{printf("进程切换 begin\n");execl("/usr/bin/ls","ls","-a","-l",NULL);printf("进程切换 end\n");return 0;
}
原理
程序替换是通过特定的接口,加载磁盘上的一个全新的程序(代码和数据)然后让进程执行全新的程序。
听起来进程替换好高级,但是它是如何实现的呢?
当我们的一个进程想要进行进程切换时,原理非常简单,就是将磁盘中全新的程序(代码和数据)覆盖式的加载到当前程序代码和数据的位置,然后修改页表即可。
而我们如果是子进程想要进行进程切换:
我们知道当我们父进程通过
fork
创建子进程时,操作系统会给子进程分配新的内存块和内核数据结构给子进程,然后将父进程的部分数据结构内容拷贝到子进程中,其中就包含进程地址空间
,以及页表;这样我们父子进程就指向同一个代码和数据;那我们现在要进行进程替换,也就是修改子进程指向的代码;
此时操作系统就会报错,发生写时拷贝,给子进程重新开辟一块空间,然后将新的程序的(代码和数据)加载到这块空间内,然后让子进程指向这一块新的空间,并修改页表;
这样就完成了子进程的程序替换。
替换函数
理解了进程切换的原理,我们现在来看进程切换exec
系列函数。
exec
系列函数一共有6
个,记起来非常麻烦;但是这些命名都是有规律的。
注意:exec
系列函数如果替换成功是没有返回值的,因为替换成功之后,我们原来代码后面的部分就不会被执行了。
如果替换失败,则返回-1
命名规律
l
(list):表示参数使用列表的形式(可变参数列表)v
(vector):表示参数使用数组的形式p
(path):表示可以自动去PATH
环境变量的路径中找对应指令(简单来说就去执行系统命令不需要带路径)。e
(env):表示环境变量表;
现在来依次看一下这些函数的简单使用:
execl
execl
函数参数存在两个
- 一个表示要执行新的程序所在的路径
- 另一个则是参数列表,表示要怎们执行这个新的程序
execl
函数命名只有l
表示以参数列表的形式调用,执行系统指令时也要带上路径。注意:参数列表要以
NULL
结尾;(命令行参数表以NULL
结尾)
执行系统命令
#include <stdio.h>
#include <unistd.h>
int main()
{printf("replace begin\n");execl("/usr/bin/ls","ls","-a","-l",NULL);printf("replaxe end\n");return 0;
}
执行自己写的程序(这里我们自己写一个程序,输出一下命令行参数)
//test.c
#include <stdio.h>
int main(int argc, char* argv[])
{for(int i = 0;i < argc;i++){printf("argv[%d] : %s\n", i, argv[i]);}return 0;
}
//code.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types>
#include <sys/wait.h>
int main()
{printf("replace begin\n");execl("./test","./test","-a","-b","-c",NULL);printf("replaxe end\n");return 0;
}
execlp
这个函数就非常简单了,和execl
相比唯一的不同就是,执行系统指令时不需要带路径(会通过PATH
环境变量去寻找)
#include <stdio.h>
#include <unistd.h>
int main()
{execl("ps","ps","-a","-l",NULL);return 0;
}
当然使用
execlp
也可以执行自己写的程序,不过要带上路径。
execle
这个函数就存在三个参数了:
- 新的程序所在的路径(系统指令也要带路径)
- 参数列表,以
NULL
结尾- 环境变量表。
我们知道全局变量
environ
它执行环境变量表,所以我们如果不使用我们自己的环境变量表,传environ
即可。
这里我们让test
输出一下环境变量表
//test.c
#include <stdio.h>
int main(int argc, char* argv[], char* env[])
{for(int i = 0; env[i];i++){printf("env[%d] : %s\n",i,env[i]);}return 0;
}
#include <stdio.h>
#include <unistd.h>
int main()
{extern char** environ;execle("./test","./test",NULL,environ);return 0;
}
execv
execv
第一个参数和execl
相同,这里就不描述了;
看第二个参数:char* const argv[]
,简单来说就是指针数组;也就是命令行参数表;
(注意:这里的命令行参数列表要以NULL
结尾)
#include <stdio.h>
#include <unistd.h>
int main()
{char* const argv[] = {(char* const)"ls",(char* const)"-a",(char* const)"-l",NULL};execv("/usr/bin/ls",argv);return 0;
}
execvp
execvp
和execv
的区别就是,在执行系统命令时,我们可以不带路径;execvp
会在环境变量PATH
中找指定程序。
#include <stdio.h>
#include <unistd.h>
int main()
{char* const argv[] = {(char* const)"ls",(char* const)"-a",(char* const)"-l",NULL};execpv("ls",argv);return 0;
}
execvpe
execvpe
存在三个参数,file
、argv
和env
file
:第一个参数,和上面一样,指的是程序所在的路径。
argv
:第二个参数指的是命令行参数表。
env
:第三个参数指的是环境变量表。
这里我们还是使用test
来测试,让test
输出命令行参数表和环境变量表;
//test.c
#include <stdio.h>
int main(int argc, char* argv[], char* env[])
{for(int i = 0;i < argc;i++){printf("argv[%d] : %s\n", i, argv[i]);}printf("\n");for(int i = 0; env[i];i++){printf("env[%d] : %s\n",i,env[i]);}return 0;
}
//code.c
#include <stdio.h>
#include <unistd.h>
int main()
{char* const argv[] = {(char* const)"./test",(char* const)"-a",(char* const)"-b",(char* const)"-c",NULL};//使用自己的环境变量表char* const env[] = {(char* const)"I=LXB",(char* const)"MYVAL=666",NULL};execvpe("./test",argv,env);//extern char** environ;//execvpe("ls",argv,environ);return 0;
}
这里也可以执行系统指令,不需要带路径
这里简单总结一下这些函数:
函数名 | 路径 | 参数格式 | 环境变量表 |
---|---|---|---|
execl | 需要带路径 | 列表 | 使用当前环境变量表 |
execlp | 系统命令不需要带路径 | 列表 | 使用当前环境变量表 |
execle | 需要带路径 | 列表 | 可以使用自己的环境变量表 |
execv | 需要带路径 | 数组 | 使用当前环境变量表 |
execvp | 系统命令不需要带路径 | 数组 | 使用当前环境变量表 |
execvpe | 系统命令不需要带路径 | 数组 | 可以使用自己的环境变量表 |
execve
函数
与上面的exec
系列的函数不同,execve
是一个系统调用
这里简单来说上面的exec
系列是库函数,而execve
是操作系统提供的系统调用;
exec
系列函数对系统调用做了封装;
那也就是说,我们使用
execlp
、execvp
执行系统命令不带路径时,最后也会调用execve
时也会带上路径吗?我们在使用
execl
、execv
等,不传递环境变量表时,最后调用execve
也会传递当前环境变量表吗?我我们在使用
execl
系列时,传递的参数列表,也会被转化为参数数组,然后传递给execve
吗?
对的,当我们使用execlp
执行系统命令不带路径时,execl
会根据环境变量PATH
找到对应程序的路径,然后调用execve
传程序的路径。
当我们使用execl
、execv
等没有传环境变量表时,exec
系列在调用execve
系统调用时会传当前环境变量表environ
。
当我们使用execl
系列,传递的参数列表,都会被转化成参数数组,然后再将参数值数组传递给execve
。
到这里,本篇文章内容就结束了,干货满满!!!
简单总结:
进程创建
fork
进程退出,退出时的退出码
进程等待
解决僵尸进程的问题;
获取子进程退出时的退出信息
退出信息
status
进程切换:原理
exec
系列函数
感谢各位的支持!
相关文章:
深入了解linux系统—— 进程控制
进程创建 fork函数 在Linux操作系统中,我们可以通过fork函数来创建一个子进程; 这是一个系统调用,创建子进程成功时,返回0给子进程,返回子进程的pid给父进程;创建子进程失败则返回-1给父进程。 我们就可…...
【前端基础】7、CSS的字体属性(font相关)
一、font-size:设置字体大小 设置方法: 具体数值单位 例如:100px 也可以用em为单位:1em代表100%,2em代表200%……0.5em代表50%。 px方式: em方式: 但是设置em的时候具体是多大呢?…...
学习整理使用php将SimpleXMLElement 对象解析成数组格式的方法
学习整理使用php将SimpleXMLElement 对象解析成数组格式的方法 要将 SimpleXMLElement 对象解析成数组格式,您可以使用 PHP 的 json_decode 和 json_encode 函数。首先,将 SimpleXMLElement 对象转换为 JSON 字符串,然后将这个字符串解码成数…...
MSF(3)免杀混淆
声明!本文章所有的工具分享仅仅只是供大家学习交流为主,切勿用于非法用途,如有任何触犯法律的行为,均与本人及团队无关!!! 一、前言 前面说了木马的捆绑,dll,exe,hta等密…...
经典密码学算法实现
# AES-128 加密算法的规范实现(不使用外部库) # ECB模式S_BOX [0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B,0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0,0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0x…...
idea里maven自定义的setting.xml文件不生效问题
问题描述: 内网环境中:maven选择选择自定义的maven文件夹时,使用的是自定义的setting.xml和本地仓库,怎么都读取不到仓库的依赖; 分析: 1.可能是setting.xml文件里没有配置本地仓库的路径; 2…...
注意力机制(Attention)
1. 注意力认知和应用 AM: Attention Mechanism,注意力机制。 根据眼球注视的方向,采集显著特征部位数据: 注意力示意图: 注意力机制是一种让模型根据任务需求动态地关注输入数据中重要部分的机制。通过注意力机制&…...
【java】使用iText实现pdf文件增加水印功能
maven依赖 <dependencies><dependency><groupId>com.itextpdf</groupId><artifactId>itext7-core</artifactId><version>7.2.5</version><type>pom</type></dependency> </dependencies>实现代码 前…...
TextIn ParseX重磅功能更新:支持切换公式输出形式、表格解析优化、新增电子档PDF去印章
ParseX重要版本更新内容速读 - 新增公式解析参数 formula_level,支持 LaTeX / Text 灵活切换; - 表格解析优化单元格内换行输出; - 导出excel时,图片链接放在单元格内; - 新增电子档pdf去印章功能。 体验文档解析…...
禁止idea联网自动更新通过防火墙方式
防火墙方式禁止idea更新检测,解决idea无限循环触发密钥填充流程。 1.首先打开控制面板找到高级设置 2.点击出站规则 3.新建规则 4.选择程序 5.找到idea路径 6.下一步 7.阻止连接 8.全选 9.输入禁止idea的名称 10.至此idea自动更新禁用完成...
面向智能体开发的声明式语言:可行性分析与未来图景
面向智能体开发的声明式语言:可行性分析与未来图景 一、技术演进的必然性:从“脚本化AI”到“声明式智能体” 当前AI开发仍停留在“脚本化AI”阶段:开发者通过Python/Java编写条件判断调用LLM API,如同用汇编语言编写操作系统。…...
【Bug经验分享】SourceTree用户设置必须被修复/SSH 主机密钥未缓存(踩坑)
文章目录 配置错误问题原因配置错误问题解决主机密钥缓存问题原因主机密钥缓存问题解决 更多相关内容可查看 配置错误问题原因 电脑太卡,曾多次强制关机,在关机前没有关闭SourceTree,导致配置错误等问题 配置错误问题解决 方式一ÿ…...
http Status 400 - Bbad request 网站网页经常报 HTTP 400 错误,清缓存后就好了的原因
目录 一、HTTP 400 错误的常见成因(一)问题 URL(二)缓存与 Cookie 异常(三)请求头信息错误(四)请求体数据格式不正确(五)文件尺寸超标(六)请求方法不当二、清缓存为何能奏效三、其他可以尝试的解决办法(一)重新检查 URL(二)暂时关闭浏览器插件(三)切换网络环…...
六个仓库合并为一个仓库,保留master和develop分支的bat脚本
利用git subtree可以实现多个仓库合并为一个仓库,手动操作起来太麻烦了,今天花了点时间写了一个可执行的脚本,现在操作起来就方便多了。 1、本地新建setup.bat文件 2、用编辑器打开(我用的是Notepad) 3、把下面代码…...
新能源汽车中的NVM计时与RTC计时:区别与应用详解
在新能源汽车的电子控制系统中,时间管理至关重要,而NVM计时(Non-Volatile Memory Timing)和RTC计时(Real-Time Clock)是两种不同的时间记录机制。虽然它们都与时间相关,但在工作原理、应用场景和…...
✨WordToCard使用分享✨
家人们,今天发现了一个超好用的工具——WordToCard!😜 它可以把WordToCard文档转换成漂亮的知识卡片,学习笔记、知识整理和内容分享都变得超轻松~🤗 支持各种WordToCard语法,像标题、列表、代…...
内网和外网怎么互通?外网访问内网的几种简单方式
在企业或家庭网络中,经常会遇到不同内网环境下网络互通问题。例如,当公司本地局域网内有个办公OA网站,在办公室内电脑上网可以登录使用,但在家带宽下就无法直接通信访问到。这就需要我们采取一些实用的内外网互通技巧来解决这个问…...
Mac中Docker下载与安装
目录 Docker下载安装配置 版本查询以及问题处理配置国内镜像在Docker中安装软件Nginx Docker 下载 官网:https://www.docker.com/get-started/ 或者 安装 配置 这里我们选择 Accept 选择默认配置就行,Docker 会自动设置一些大多数开发人员必要的配…...
固件测试:mac串口工具推荐
串口工具对固件测试来说非常重要,因为需要经常看日志,Windows上有Xshell和secureCRT,用起来很方便,尤其可以保存日志,并且可以进行日志分割。 mac上用什么串口工具呢,今天给大家推荐CoolTerm。 CoolTerm …...
41.防静电的系列措施
静电干扰的处理措施 1. ESD放电特征2. 静电防护电路设计措施3. ESD防护结构措施4. 案例分析 1. ESD放电特征 (1)放电电流tr≈1nS,ESD保护器件响应时间应小于1nS; (2)频率集中在几十MHz到500MHz;…...
Jmeter进行http接口测试
🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 1、jmeter-http接口测试脚本 jmeter进行http接口测试的主要步骤(1.添加线程组 2.添加http请求 3.在http请求中写入接口的URL,路径&#x…...
Ubuntu也开始锈化了?Ubuntu 计划在 25.10 版本开始引入 Rust Coreutils
上个月,jnsgruk发表了《未来20年的Ubuntu工程》(Engineering Ubuntu For the Next 20 Years)一文,其中概述了打算在未来几年中如何发展Ubuntu的四个关键主题。在这篇文章中,重点讨论 了“现代化”。在很多方面对Ubuntu…...
C++命名空间、内联与捕获
命名空间namespace 最常见的命名空间是std,你一定非常熟悉,也就是: using namespace std;命名空间的基本格式 注意,要在头文件里面定义! namespace namespace_name{data_type function_name(data_type parameter){data_type result;//function contentreturn result;}…...
PostgreSQL 系统管理函数详解
PostgreSQL 系统管理函数详解 PostgreSQL 提供了一系列强大的系统管理函数,用于数据库维护、监控和配置。这些函数可分为多个类别,以下是主要功能的详细说明: 一、数据库配置函数 1. 参数管理函数 -- 查看所有配置参数 SELECT name, sett…...
mdadm 报错: buffer overflow detected
最近跑 blktest (https://github.com/osandov/blktests) 时发现 md/001 的测试失败了 单独执行,最后定位到是 mdadm 命令报错: buffer overflow detected 这个 bug 目前已经修复: https://git.kernel.org/pub/scm/utils/mdadm/mdadm.git/commit/?id827e1870f3205…...
java ReentrantLock
线程同步工具。可以替代 synchronized . private final ReentrantLock reentrantLock new ReentrantLock();void testTask1 () {reentrantLock.lock(); // 获取锁try {System.out.println(Thread.currentThread().getName() " 进入临界区");// 模拟执行业务逻辑Th…...
kettle从入门到精通 第九十六课 ETL之kettle Elasticsearch 增删改查彻底掌握
场景: 群里有小伙伴咨询kettle从Elasticsearch中抽取数据,群里老师们纷纷响应,vip小伙伴是不是有中受宠若惊的感觉。 今天我们使用kettle通过es的原生rest接口来进行操作es,开整。 前提:本篇文章基于elasticsearch:7.…...
Kafka的核心组件有哪些?简要说明其作用。 (Producer、Consumer、Broker、Topic、Partition、ZooKeeper)
Kafka 核心组件解析 1. 基础架构图解 ┌─────────┐ ┌─────────┐ ┌─────────┐ │Producer │───▶ │ Broker │ ◀─── │Consumer │ └─────────┘ └─────────┘ └────────…...
Missashe考研日记-day34
Missashe考研日记-day34 1 专业课408 学习时间:3h学习内容: 今天是学习I/O管理第二小节的内容,听了课也做了题,这是操作系统倒数第二节知识了,还差最后一节就完结了。知识点回顾: 1.I/O核心子系统&#x…...
机器人跑拉松是商业噱头还是技术进步的必然体现
一、机器人跑拉松是商业噱头还是技术进步的必然体现 机器人参与马拉松赛事究竟是营销噱头还是技术进步的必然要求,需要从技术验证、行业推动、公众认知以及争议焦点等多个维度综合分析。基于全球首场人形机器人半程马拉松(2025年北京亦庄赛事࿰…...
传输层协议 1.TCP 2.UDP
传输层协议 1.TCP 2.UDP TCP协议 回顾内容 传输层功能:定义应用层协议数据报文的端口号,流量控制对原始数据进行分段处理 传输层所提供服务 传输连接服务数据传输服务:流量控制、差错控制、序列控制 一、传输层的TCP协议 1.面向连接的…...
LLM :Function Call、MCP协议与A2A协议
LLM 的函数调用、模型上下文协议 (MCP) 和 Agent to Agent (A2A) 协议:概念、区别与实例对比 引言:LLM 不断演进的格局 大型语言模型 (LLM) 的日益精进,使其能力已超越简单的文本生成,迈向与现实世界进行复杂交互的新阶段。为了…...
当当狸智能天文望远镜 TW2 | 用科技触摸星辰,让探索触手可及
当科技邂逅星空,每个普通人都能成为宇宙的追光者 伽利略用望远镜揭开宇宙面纱的 400 年后,当当狸以颠覆传统的设计,让天文观测从专业领域走入千家万户。当当狸智能天文望远镜 TW2,重新定义「观星自由」—— 无需专业知识ÿ…...
白杨SEO:如何查看百度、抖音、微信、微博、小红书、知乎、B站、视频号、快手等7天内最热门话题及流量关键词有哪些?使用方法和免费工具推荐以及注意事项【干货】
大家好,我是白杨SEO,专注SEO十年以上,全网SEO流量实战派,AI搜索优化研究者。 (温馨提醒:本文有点长,看不完建议先收藏或星标,后面慢慢看哈) 最近,不管是在白…...
Spring AI 之 AI核心概念
模型 人工智能(AI)模型是用于处理和生成信息的算法,通常旨在模拟人类的认知功能。这些模型通过从大规模数据集中学习模式和规律,能够生成预测结果、文本、图像或其他形式的输出,从而增强各行业应用的效能。 AI 模型种类繁多,每种模型都适用于特定的应用场景。虽然以 Ch…...
微软输入法常用快捷键介绍以及调教技巧
微软输入法(Microsoft Pinyin Input Method)是 Windows 系统内置的中文输入工具,以其高效、智能化的特点广受用户喜爱。掌握其常用快捷键和特殊模式可以显著提升输入效率。本文将介绍微软输入法在 Windows 10/11 环境下的常用快捷键及 U 模式…...
基于大模型的输卵管妊娠全流程预测与治疗方案研究报告
一、引言 1.1 研究背景与意义 输卵管妊娠作为异位妊娠中最为常见的类型,严重威胁着女性的生殖健康和生命安全。受精卵在输卵管内着床发育,随着胚胎的生长,输卵管无法提供足够的空间和营养支持,极易引发输卵管破裂、大出血等严重并发症,若救治不及时,甚至会导致孕产妇死…...
16.Excel:打印技巧
一 区域打印 不用打印整个表格,比如只想打印框选出来的信息。 选中区域调整列宽。 二 整表打印 选中整个工作表, 如果调完边距后仍然打印不完全,就用缩放功能。 三 居中打印 打印部分区域的时候,预览图不在居中。 四 行号打印 五 …...
AI驱动的Kubernetes管理:kubectl-ai 如何简化你的云原生运维
AI驱动的Kubernetes管理:kubectl-ai 如何简化你的云原生运维 kubectl-ai 项目概览核心能力:AI 如何赋能 kubectl自然语言的魔力:从繁琐命令到简单对话智能的命令生成与执行不仅仅是执行:结果的可解释性广泛的 AI 模型支持…...
maven基本介绍
Maven是一个常用的项目构建工具,用于管理Java项目的构建、依赖管理和项目信息管理。它可以帮助开发人员自动化构建过程,统一项目结构和构建规范,并管理项目所需的外部依赖库。 Maven通过一个项目对象模型(Project Object Model&a…...
SPL量化 BBIC(多空指标)
BBIC 是一种将不同天数移动平均线加权平均之后的综合指标,属于均线型指标,一般选用 3 日、6 日、12 日、24 日等 4 条平均线。BBIC 越小股价越强势,BBIC < 1 为多头行情, BBIC>1 为空头行情。 计算公式: 1. 3 日…...
【ArcGIS Pro微课1000例】0068:Pro原来可以制作演示文稿(PPT)
文章目录 一、新建演示文稿二、插入页面1. 插入地图2. 插入空白文档3. 插入图像4. 插入视频三、播放与保存一、新建演示文稿 打开软件,新建一个地图文档,再点击【新建演示文稿】: 创建的演示文档会默认保存在目录中的演示文稿文件夹下。 然后可以对文档进行简单的设计,例如…...
【论文阅读】Reconstructive Neuron Pruning for Backdoor Defense
我们的主要贡献包括: 我们引入了在相同样本集上进行神经元“遗忘”和“恢复”的新技术,并揭示了这种简单的基于重构的学习过程可以帮助暴露DNNs中的后门神经元。我们提出了一个新的防御方法——重构神经元剪枝(RNP),它…...
[数据处理] 3. 数据集读取
👋 你好!这里有实用干货与深度分享✨✨ 若有帮助,欢迎: 👍 点赞 | ⭐ 收藏 | 💬 评论 | ➕ 关注 ,解锁更多精彩! 📁 收藏专栏即可第一时间获取最新推送🔔…...
Ceph 原理与集群配置
一、Ceph 工作原理 1.1.为什么学习 Ceph? 在学习了 NFS 存储之后,我们仍然需要学习 Ceph 存储。这主要是因为不同的存储系统适用于不同的场景,NFS 虽然有其适用之处,但也存在一定的局限性。而 Ceph 能够满足现代分布式、大规模、…...
【C++】类和对象
文章目录 1. 为什么引入类?1.1 C类的设计目标1.2 类的核心特性1.3 类与结构体的区别 2. 类的定义2.1 类定义格式2.2 访问限定符2.3 类域 3. 实例化3.1 实例化概念3.2 对象大小 4. this指针5. 类的默认成员函数6. 构造函数7. 析构函数8. 拷贝构造函数9. 赋值运算符重…...
【计算机视觉】OpenCV项目实战:OpenCV_Position 项目深度解析:基于 OpenCV 的相机定位技术
OpenCV_Position 项目深度解析:基于 OpenCV 的相机定位技术 一、项目概述二、技术原理(一)单应性矩阵(Homography)(二)算法步骤(三)相机内参矩阵 三、项目实战运行&#…...
【Linux系列】如何区分 SSD 和机械硬盘
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...
【AI提示词】双系统理论专家
提示说明 专注于认知心理学领域的专家,研究快思考(直觉)与慢思考(理性)的切换机制及其在认知科学中的应用。 提示词 # Role: 双系统理论专家## Profile - language: 中文 - description: 专注于认知心理学领域的专家…...
CI/CD与DevOps流程流程简述(给小白运维提供思路)
一 CI/CD流程详解:代码集成、测试与发布部署 引言 在软件开发的世界里,CI/CD(持续集成/持续交付)就像是一套精密的流水线,确保代码从开发到上线的整个过程高效、稳定。我作为一名资深的软件工程师,接下来…...