进程控制~

一.进程控制

1.进程创建

我们可以通过./cmd来运行我们的程序,而我们运行的程序就是bash进程常见的子进程。当然我们也可以通过fork()系统调用来创建进程。

NAME
       fork - create a child process

SYNOPSIS
       #include <unistd.h>

       pid_t fork(void);

 fork会对子进程和父进程返回不同的返回值,对子进程返回0,对父进程返回子进程的pid,如果返回值小于0,则说明子进程创建失败。

fork引申出来的一系列问题:fork函数有两个返回值,fork函数对父子进程返回值不同,fork的返回值为什么大于0又等于0导致if if-else同时成立......这些问题已经在进程部分解释,这里就不再赘述了。【Linux】进程-CSDN博客

2.进程终止

进程终止的本质就是释放资源,就是释放进程创建时申请的内核数据结构和内存中的代码和数据。

而进程退出有三种场景:

  • 代码执行完,结果正确
  • 代码执行完,结果错误
  • 代码异常终止

而对于子进程来说,它是由父进程创建的,为了实现某种功能的,所以它也有上述三种退出场景。那么我们怎么区分进程的执行情况呢?

对于我们所写的C程序来说,为什么我们在main函数的最后要返回一个0呢?通常这表示程序正常结束,如果代码执行的结果不正确,此时就会返回一个非0值。所以对于一个C程序来说,我们可以通过返回值来判断代码的执行情况。而这个返回值就是错误码。

我们可以通过strerror()函数将错误码转换成错误信息,而errno则会记录最近的一次错误码。

就比如fopen这个函数,如果打开失败,会返回一个空的文件指针,并且设置错误码。我们可以故意让fopen失败,借助sterror(errno)来观察错误信息。

在C语言中,一共有134个错误码,大家可以用循环打印出每一种来看看。

 对一个C语言程序来说,它用错误码来标记执行情况,那么对于一个进程来说也一样!!!。一个进程的退出码,表示了该进程的执行情况。

我们可以使用echo $?来查看最近一个进程的退出码

对于上图来说,我们首先调用了ll,该进程正常结束,所以退出码为0,接着我们使用cd命令,跳转到不存在的路径,并且bash表示出现了错误,此时退出码就变成了1,在使用pwd命令,退出码就有变成了1. 

在Linux操作系统中,一个进程的退出码如果分为两种,0和非0,0表示成功,非0表示出错

  • 1:一般错误(如参数错误)
  • 2:误用shell命令(如rm删除只读文件)
  • 126:权限不足或命令不可执行
  • 127:命令未找到
  • 130:被CTRL+c终止
  • 139:段错误

 而进程的退出码会在该进程推出的时候写入该进程的pcb中

 我们可以通过main函数的返回值/exit/_exit来设置进程的退出码。

return语句在main函数处执行,才表示进程结束,如果在其他函数内执行,只表示该函数结束。

对于exit函数来说,不论在代码的那个地方执行到该语句,进程都将结束,并将exit设置的退出码写入到进程的pcb中。

而除了c标准库的的exit函数外,还有一个系统调用_exit函数。这两个函数有什么关系呢?

exit其实是对_exit函数的封装,调用exit函数最终还是会调用_exit。当然两者也是有区别的:

  • exit在结束进程之前会进行清理操作,刷新文件缓冲区
  • _exit会立刻结束进程,不进行清理操作

有了这个认识,我们就可以猜想,缓冲区的概念是C语言提出的,而不是系统内的缓冲区。 

3.进程等待

为什么要进行进程等待呢?

之前说过,当子进程结束之后,父进程没有回收子进程资源时,子进程就会处于僵尸状态,进而导致内存泄漏。

而且一旦进程进入僵尸状态,此时kill -9 也无法无能为力,因为kill -9 不能杀死一个已经死掉的进程。

我们父进程创建子进程是为了帮助我们执行任务的,执行的如何父进程得知道吧!

所以,之所以要进行进程等待,就是为了让父进程回收子进程的资源,避免内存泄漏,并且获取子进程的退出信息。最重要的是回收子进程资源,有时候我们并不关系子进程的结束信息。

说的好,那怎么进行进程等待呢? 

 3.1wait

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

wait就可以使父进程进行等待子进程,而wait会等待父进程的任意一个子进程,一旦等到子进程,等待就结束了。而它还有一个输出型参数status,我们稍后再说

我们接着下面这个例子,来观察子进程由Z->X的过程

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if (id == 0){// dhildint cnt = 5;while (cnt){printf("child pid:%d, ppid:%d\n", getpid(), getppid());sleep(1);cnt--;}exit(1);}// father  sleep(10);pid_t wid = wait(NULL);if (wid > 0){printf("success %d\n", wid);}else{printf("failed\n");}sleep(10);return 0;
}

说明:创建子进程后,让子进程循环5秒后,然后退出,此时父进程还在休眠中,还没有执行等待代码,此时子进程状态为Z,接着过了又过了5秒之后,父进程休眠结束,执行等待,我们现在不用参数,可以传NULL,此时子进程状态转为X。接着我们让父进程继续睡眠10秒。

监控脚本,每隔一秒观察父子进程的状态

while :; do ps ajx | head -1 && ps ajx | grep proc | grep -v grep; echo "-----------------------------------------------------------------";  sleep 1 ; done

接下里我们只需要死盯着STAT即可。

 

至此,我们成功验证了父进程等待子进程

通过wait()方法,我们可以使进入僵尸状态的子进程,被父进程回收。

 3.2waitpid

但是今天的主角并不是wait,因为其功能简单,而是waitpid则是最优先考虑使用的!!!

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

wait就好像是waitpid的一个子功能,waitpid是一个完全版的wait,可以进行多种等待过程。

返回值pid_t:

  • > 0 :等待成功
  • < 0 :等待失败

参数pid:

  • >0:是多少,就表示等待pid为该数的子进程
  • -1:表示等待任意一个子进程,类似wait

参数status:输出型参数,与wait的功能一致,获取等待的子进程的退出码。

option:可以控制不同方式的等待过程,默认参数为0,表示阻塞等待......

当我们以waitpid(-1, NULL, 0);的方式调用时,此时waitpid和wait是一样的功能。

上面说了,进程等待主要是为了回收子进程的资源,也可以获取子进程的退出信息,回收子进程资源很好理解,将其的状态从Z->X,操作系统就会回收。那么如何获取子进程的退出信息呢?这就是参数status的事了!!!

0x1参数status: 

status作为一个输出型参数,子进程执行结束后,会将自己的退出信息和退出码写到自己的pcb中,父进程等待到子进程后,操作系统会在子进程的pcb中拿出退出信息和退出码写入到status中,最后将信息带回给父进程。

但是退出码是代码执行完毕,结果正确或不正确时的标志,而进程终止还有可能是异常终止,此时子进程也会有退出码么?

首先,进程异常终止肯定收到了信号,比如我们使用kill -9 的方式杀死进程就是给进程传递了信号。另外,一旦进程异常终止了,此时的退出码就无意义了,此时更关心的是退出信号。

那么也就是说,子进程pcb中,除了要维护退出码,还要维护退出信号,但是我们只传了一个整型,如果获取两个内容?

答案就是位图。对于status来说,它的32个比特位被分为了三个部分,高16位,中8位和低8位。高16位没有被使用,中8位记录着退出码,低8位中的最高位是一个core dump标志位,是程序崩溃时生成的内存快照文件,用于调试分析,这部分与信号有关,这里不做解释,最后的7位存储的就是退出信号的编号了。

下面,我们就验证一个status的作用:

#include <stdio.h>                                                                                                                                      
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if (id == 0){// childint cnt = 5;while (cnt){printf("child pid:%d, ppid:%d\n", getpid(), getppid());sleep(1);cnt--;}exit(66); // 子进程退出码设置为66,方便观察}// fatherint status = 0;pid_t wid = waitpid(id, &status, 0);if (wid > 0){printf("success pid:%d exit_code:%d\n", wid, (status >> 8 & 0xFF));}else{printf("failed\n");}return 0;
}

说明:创建子进程后,我们让子进程执行5秒,子进程退出时,设置其退出码为66,接着父进程等待子进程,等待成功后,打印信息,并打印退出码。因为退出码存储在status的次低8未,所以我们首先右移8位,接着&0xFF,就可以拿出次低8位的内容。

上面是正常退出的结果,我们也可以测试进程异常终止时它的退出信号: 

#include <stdio.h>                                                                                                                                      
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if (id == 0){// childwhile (1){printf("child pid:%d, ppid:%d\n", getpid(), getppid());sleep(1);}exit(66); // 子进程退出码设置为66,方便观察}// fatherint status = 0;pid_t wid = waitpid(id, &status, 0);if (wid > 0){printf("success pid:%d exit_code:%d exit_signal:%d\n", wid, (status >> 8 & 0xFF), status & 0x7F);}else{printf("failed\n");}return 0;

说明:我们让子进程死循环,让父进程进行等待,接着使用kill -9 杀死子进程,观察退出码和退出信号。

自此,我们就验证了wait和waitpid的status参数的用途。当进程被信号杀死时,status的次低8位就被置为了0.

但是我们每一次获取子进程退出码都要进行位运算么? 不,操作系统为我们提供了宏接口,我们可以直接使用宏来获取退出码和退出信号。

获取子进程退出码

WEXITSTATUS(status)

如果自己是正常结束的,则返回true,反之返回false

WIFEXITED(status)

获取子进程退出信号 

WTERMSIG(status)

通过上述三个宏,我们就可以让我们的等待过程变得更加完整健壮:

// father
pid_t rid = waitpid(id, &status, 0);
if(rid>0) // 等待成功
{if(WIFEXITED(status)) // 正常退出{printf("terminated normally exit_code:%d\n", WEXITSTATUS(status));}else{printf("terminated by signal exit_signal:%d\n", WTERMSIG(status));}
}

了解了status之后,我们在了解下一个参数option,它可以设置等待的行为。

0x2参数option:

wait等待和waitpid的第三个参数为0时都表示阻塞等待,什么是阻塞等待呢?

 阻塞等待就像c里面的scanf和c++里面的cin。当父进程执行到等待语句时,父进程就卡住了,除非子进程死亡,否则父进程什么也做不了。

当然,除了阻塞等待,还有非阻塞等待。第三个参数我们可以传WNOHANG,来使父进程不阻塞等待,可以执行自己的事。它表示的是return immediately if no child has exited.即父进程等待子进程时,发现没有一个子进程结束,它立刻返回,接着执行自己的代码。

但是归根结底父进程还是得等待子进程结束,如果非阻塞等待询问一次直接结束,执行自己的代码去了,这不还是会造成僵尸状态,内存泄漏。

所以非阻塞等待主要的使用场景是结合循环来实现非阻塞轮询,再结合waitpid的返回值,就可以实现父进程在等待过程中,执行自己的逻辑:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){// childwhile(1){printf("child pid:%d, ppid:%d\n", getpid(), getppid());sleep(1);}exit(66);}// fatherwhile(1){int status = 0;pid_t rid = waitpid(id, &status, WNOHANG);if(rid>0) // 等待成功{if(WIFEXITED(status)) // 正常退出{printf("terminated normally exit_code:%d\n", WEXITSTATUS(status));}else// 异常退出{printf("terminated by signal exit_signal:%d\n", WTERMSIG(status));}break;}else if(rid == 0){// 询问结束,子进程未退出,执行自己的逻辑// ......printf("非阻塞轮询执行逻辑......\n");}else{printf("等待失败\n");break;}}return 0;
}

说明:这里采用非阻塞等待的方式,一创建子进程后,父进程即可进入非阻塞轮询状态,首先调用一次,判断子进程是否结束,如果返回值>0表示等待成功,此时退出循环,如果返回值 == 0,表示调用结束,子进程未退出,如果返回值小于0,表示等待失败,也退出循环。在非阻塞轮询期间内,每进行一次调用,如果子进程未退出,此时父进程就可以执行自己的代码逻辑。待下一次继续等待。

4.进程程序替换

fork()之后,父子进程各自执行代码的一部分,那如果子进程想要执行一个全新的程序呢?进程程序替换来实现!!!

4.1程序替换的原理

进程 = 内核数据结构pcb+代码和数据,当我们进行程序替换的时候,操作系统会从磁盘中,将要替换的程序的代码和数据覆盖式的放在原代码和数据的位置上。

那么照上面所说,我们程序替换之后,原代码和数据就被覆盖了,是不是替换上来的程序执行完了,那么整个进程就结束了?

没错!程序替换结束后,替换上来的程序结束了,整个进程就结束了。 

看个例子:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>int main()
{printf("进程开始执行,pid:%d, ppid:%d\n", getpid(), getppid());execl("/usr/bin/ls", "ls", "-a", "-l", NULL); // 进行程序替换,执行ls命令printf("进程结束,pid:%d, ppid:%d\n", getpid(),getppid());return 0;
}

说明:进程一开始便打印信息,接着我们就进行程序替换,执行ls命令,按照上面所说,最后面的打印语句将不再执行。

总结:在进行程序替换的时候,并不会创建新的进程,而是用待替换的程序的代码和数据覆盖原来程序的代码和数据,并且替换之后,原代码的后半部分就不存在了,替换完的程序执行完毕,进程就结束了。 

4.2exec系列接口

0x1exec系列函数返回值

exec系列函数的接口在成功调用时没有返回值,只有失败才有返回值-1,并且设置错误码。

The exec() functions return only if an error has occurred.  The return value is -1, and errno is set to indicate the error.

当你程序都已经替换成功了,原来的进程上下文都已经不在了,你要返回值干嘛呢?返回值谁接受呢? 

失败返回-1,这是非常明确的,所以我们只需要判断返回值是否为-1即可,如果不是-1就说明成功,失败了,我们在根据错误码来判断错误的原因。

0x2execl

exec系列函数都有相同的前缀,那么不同的后缀,就能体现出不同函数的特点。

就比如execl来说,l可以理解为list,即我们传入的参数就好像在一个一个链表的节点中存储的一样,而链表最后一个节点的next指针为NULL,所以对于execl函数来说,它的参数也要以NULL结尾。

#include <unistd.h>int execl(const char *path, const char *arg,...);

参数path表示要替换的程序所在的位置即路径+程序名,接下来的参数我们在命令行怎么写,在这就怎么传。

就比如,我们要执行ls命令,写法如下

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

我们不仅可以让父进程进行程序替换,也可以创建出来一个子进程,让其执行要替换的程序: 

int main()
{printf("程序开始\n");if(fork() == 0){sleep(1);execl("/usr/bin/ls", "ls", "-l", NULL);exit(22);}int status = 0;waitpid(-1, &status, 0);printf("%d\n", WEXITSTATUS(status));printf("程序结束\n");return 0;
}

首先,子进程进行替换之后,不会执行exit函数,因为后序代码已经被覆盖了,所以我们父进程打印子进程的退出码是不是设置的22,而是0.

其次,为什么子进程进行程序替换没有影响父进程呢?

第一,进程具有独立性;第二,父子进程本来共享代码和数据,子进程进行程序替换,对代码和数据进行了修改,此时会进行写时拷贝。

综上,所以子进程进行程序替换是不会影响父进程的!!! 

 那么可不可以替换我们自己写的程序呢?

当然也是可以的!! 

首先我们写一段C++程序,并编译成可执行文件。

接着便是修改execl的参数

execl("./other", "other", NULL); // 第二个参数可以带./,也可以不带

看结果:

 0x2execlp
#include <unistd.h>int execlp(const char * file, const char *arg ,...);

该函数多了一个后缀p,这个p的意思是环境变量PATH,所以使用这个接口时,第一个参数如果不带路径只是文件名,该函数就会从PATH环境变量里面去搜索该文件。

所以,execlp会自动在PATH环境变量中搜素指定的命令,如果找不到,就会失败返回-1并设置错误码!!!

int main()
{printf("程序开始\n");if(fork() == 0){sleep(1);//execlp("ls", "ls", "-l", NULL);  // 成功替换execlp("other", "other", NULL);    // 替换失败 , 并设置错误码为22exit(22);}int status = 0;waitpid(-1, &status, 0);printf("%d\n", WEXITSTATUS(status));printf("程序结束\n");return 0;
}

说明:对于ls命令来说,他所处的路径/usr/bin/ls,在PATH环境变量中,所以不带路径是可以找到的。但是我们的other是在当前目录下,函数找不到,就会失败!!!

0x3execv

不一样的来了

#include <unistd.h>int execv(const char *path, char *const argv[]);

第一个参数依旧是可执行文件的路径+文件名,表示我要执行谁;

第二个参数则不一样了,不在像命令行那样传参数了,而是传了一个命令行参数表!!!所以这里后缀的v其实就是vector的意思。

只需要将命令行参数放入放入一个指针数组里面即可,当然也要以NULL结尾。

int main()
{printf("程序开始\n");if(fork() == 0){sleep(1);char* const argv[] = {(char* const)"ls",(char* const)"-l",(char* const)"-a",NULL};execv("/usr/bin/ls", argv);exit(22);}int status = 0;waitpid(-1, &status, 0);printf("%d\n", WEXITSTATUS(status));printf("程序结束\n");return 0;
}

 0x4execvp
#include <unistd.h>int execvp(const char *file, char *const argv[]);

有了上面的基础,这个就很好理解了,第一个参数会默认在PATH中搜索命令,第二个参数表示将命令行参数以指针数组的方式传过去,并且以NULL结尾。

这个就不做示例了,相信大家没问题

0x5execvpe 
#include <unistd.h>int execvpe(const char *file, char *const argv[],char *const envp[]);

这个就又不一样了,多了一个后缀e,表示的是环境变量envion。使用该接口除了传递文件名和命令行参数表外,还要传一个环境变量表。传了该环境变量表后就会将原来全局的环境变量表给覆盖掉,子进程只会看到传的环境变量了。

// proc.c
int main()
{printf("程序开始\n");if(fork() == 0){sleep(1);char* const argv[] = {(char* const)"other",NULL};char* const envp[] = {(char* const)"MYENV = 112233445566778899",NULL};execvpe("./other", argv, envp);exit(22);}int status = 0;waitpid(-1, &status, 0);printf("程序结束\n");return 0;
}// other.cc#include <iostream>
#include <cstdio>int main()
{extern char** environ;for(int i=0; environ[i]; ++i){printf("env[%d] -> %s\n", i, environ[i]);}return 0;
}

说明:为了测试execvpe接口对环境变量的影响,我们利用proc替换我们自己的.cc程序。该.cc程序主要工作就是打印环境环境变量。我们让该接口替换我们自己的程序,并且在替换前设置了环境变量envp,按照上面所说,会将替换程序的环境变量给替换掉,所有.cc打印出来的环境变量应该只有我们自己设置的。

我们看结果,确实和我们预料的一样。 

对于第三个参数来说,如果envp为空的话,即只有一个NULL,新程序就会继承原来的环境变量,不会改变。

但是我们使用该接口时想给子进程新增环境变量,而不是覆盖原来的环境变量,怎么实现呢?

我们可以借助putenv函数来实现新增环境变量

#include <string.h>int putenv(char *string);

在每一个进程的进程地址空间上,有一段空间专门用来存储当前进程的环境变量,putenv就是将指定环境变量加载到该进程地址空间上的指定位置。这样,我们再使用该接口时,第三个参数传NULL,新程序就会继承原来的环境变量,当然也包含我们新增的环境变量。

// proc.c#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>int main()
{printf("程序开始\n");if(fork() == 0){sleep(1);char* const argv[] = {(char* const)"other",NULL};// 设置新增环境变量char* const addNewEnv[] = {(char* const)"MYENV1=11111111",(char* const)"MYENV2=22222222",(char* const)"MYENV3=33333333",NULL};// 加载新增环境变量for(int i=0; addNewEnv[i]; i++){putenv(addNewEnv[i]);}extern char** environ; // 指向环境变量表的全局指针execvpe("./other", argv, environ);exit(22);}int status = 0;waitpid(-1, &status, 0);printf("程序结束\n");return 0;
}// other.cc#include <iostream>
#include <cstdio>int main()
{extern char** environ;for(int i=0; environ[i]; ++i){printf("env[%d] -> %s\n", i, environ[i]);}return 0;
}

说明:我们先定义出想要新增的环境变量,然后通过putenv将其加载到子进程的环境变量上,然后我们将全局的environ传给execvpe函数,让其替换程序,此时替换上来的程序就有了我们新增的环境变量。但是这个新增的环境变量只有子进程可以看到,父进程看不到。

 

0x6execle 

经过上面的分析,相比大家已经知道了该接口的用法了

#include <unistd.h>int execle(const char *path, const char *arg,..., char * const envp[]);

4.3execve

通过对上面exec家族的了解,我们发现,按照规律应该有一个execve的函数啊,为什么找不到呢?

因为,这个函数是系统调用,而上面的exec家族都是对其在语言层上的封装

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

但是我们以及可以用上面了解到的知识来解读它,v表示我们要传一个命令行参数表,e表示我们得传一个环境变量表!!!


上述,便是进程控制的全部内容~~~

本章完~

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

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

相关文章

经历过的IDEA+Maven+JDK一些困惑

注意事项&#xff1a;由于使用过程中是IDEA绑定好另外2个工具&#xff0c;所以报错统一都显示在控制台&#xff0c;但要思考和分辨到底是IDEA本身问题导致的报错&#xff0c;还是maven导致的 标准配置 maven Java Compiler Structure 编辑期 定义&#xff1a;指的是从open pr…

将bin文件烧录到STM32

将bin文件烧录到STM32 CoFlash下载生成hex文件hex2bin使用下载bin到单片机 CoFlash下载 选择需要安装的目录 在Config中可以选择目标芯片的类型 我演示的是 stm32f103c8t6 最小系统板 Adapter&#xff1a;烧录器类型 Max Clock&#xff1a;下载速度 Por&#xff1a;接口类型&am…

硬件基础(5):(2)二极管分类

文章目录 &#x1f4cc; 二极管的分类与详细介绍1. **整流二极管&#xff08;Rectifier Diode&#xff09;**特点&#xff1a;选型依据&#xff1a;补充说明&#xff1a; 2. **快恢复二极管&#xff08;Fast Recovery Diode&#xff09;**特点&#xff1a;选型依据&#xff1a;…

【MySQL】MySQL如何存储元数据?

目录 1.数据字典的作用 2. MySQL 8.0 之前的数据字典 3. MySQL 8.0 及之后的数据字典 4.MySQL 8 中的事务数据字典的特征 5.数据字典的序列化 6. .sdi文件的作用&#xff1a; 7..sdi的存储方式 在 MySQL 中&#xff0c;元数据&#xff08;Metadata&#xff09; 是描述数…

瑞萨RA系列使用JLink RTT Viewer输出调试信息

引言 还在用UART调试程序么?试试JLINK的RTT Viewer吧!不需占用UART端口、低资源暂用、实时性高延时微秒级,这么好的工具还有什么理由不用了! 目录 一、JLink RTT Viewer 简介 二、软件安装 三、工程应用 3.1 SEGGER_RTT驱动包 3.2 手搓宏定义APP_PRINT 3.3 使用APP_…

Ranger 鉴权

Apache Ranger 是一个用来在 Hadoop 平台上进行监控&#xff0c;启用服务&#xff0c;以及全方位数据安全访问管理的安全框架。 使用 ranger 后&#xff0c;会通过在 Ranger 侧配置权限代替在 Doris 中执行 Grant 语句授权。 Ranger 的安装和配置见下文&#xff1a;安装和配置 …

LabVIEW烟气速度场实时监测

本项目针对燃煤电站烟气流速实时监测需求&#xff0c;探讨了静电传感器结构与速度场超分辨率重建方法&#xff0c;结合LabVIEW多板卡同步采集与实时处理技术&#xff0c;开发出一个高效的烟气速度场实时监测系统。该系统能够在高温、高尘的复杂工况下稳定运行&#xff0c;提供高…

【系统架构设计师】操作系统 - 特殊操作系统 ③ ( 微内核操作系统 | 单体内核 操作系统 | 内核态 | 用户态 | 单体内核 与 微内核 对比 )

文章目录 一、微内核操作系统1、单体内核 操作系统2、微内核操作系统 引入3、微内核操作系统 概念4、微内核操作系统 案例 二、单体内核 与 微内核 对比1、功能对比2、单体内核 优缺点3、微内核 优缺点 一、微内核操作系统 1、单体内核 操作系统 单体内核 操作系统 工作状态 : …

人工智能之数学基础:线性方程组

本文重点 线性方程组是由两个或两个以上的线性方程组成的方程组,其中每个方程都是关于两个或两个以上未知数的线性方程。 记忆恢复 我们先从小学学习的线性方程组找到感觉 解答过程: 将第二个方程乘以2,得到: 2x−2y=2 将第一个方程减去新得到的方程,消去x: (2x+y)−…

​第十一届传感云和边缘计算系统国际会议

重要信息 时间地点&#xff1a;2025年4月18-20日 中国-珠海 会议官网&#xff1a;www.scecs.org 简介 第十一届传感云和边缘计算系统 (SCECS 2025&#xff09;将于2025年4月18-20日在中国珠海召开。将围绕“传感云”、“边缘计算系统”的最新研究领域&#xff0c;为来自国…

MDM设备管控,企业移动设备管理方案

目录&#xff1a; 目录 目录&#xff1a; 1. MDM&#xff1a;含义与定义 2. MDM如何工作&#xff1f; 3. BYOD与MDM&#xff1a;挑战与解决方案 4. 移动设备管理的主要优势 5. 移动设备管理的基本要素 6. 移动设备管理最佳实践 --地平线-- 移动设备管理 (MDM)历经多年…

S32k3XX MCU时钟配置

今天想从头开始配置S32K312中EB中的MCU模块&#xff0c;以下是我的配置思路与理解。 关键是研究明白&#xff0c;这些频率是如何通过一个总时钟&#xff0c;一步步分频得到的。 参考时钟&#xff0c;供外设模块使用&#xff0c;不同外设需要配置合理的参考时钟。 clock genera…

GitHub 超火的开源终端工具——Warp

Warp 作为近年来 GitHub 上备受瞩目的开源终端工具&#xff0c;以其智能化、高性能和协作能力重新定义了命令行操作体验。以下从多个维度深入解析其核心特性、技术架构、用户评价及生态影响力&#xff1a; 一、背景与核心团队 Warp 由前 GitHub CTO Jason Warner 和 Google 前…

SpringBoot 第二课(Ⅰ) 整合springmvc(详解)

目录 一、SpringBoot对静态资源的映射规则 1. WebJars 资源访问 2. 静态资源访问 3. 欢迎页配置 二、SpringBoot整合springmvc 概述 Spring MVC组件的自动配置 中央转发器&#xff08;DispatcherServlet&#xff09; 控制器&#xff08;Controller&#xff09; 视图解…

八股学习-JUC java并发编程

本文仅供个人学习使用&#xff0c;参考资料&#xff1a;JMM&#xff08;Java 内存模型&#xff09;详解 | JavaGuide 线程基础概念 用户线程&#xff1a;由用户空间程序管理和调度的线程&#xff0c;运行在用户空间。 内核线程&#xff1a;由操作系统内核管理和调度的线程&…

C++基础 [八] - list的使用与模拟实现

目录 list的介绍 List的迭代器失效问题 List中sort的效率测试 list 容器的模拟实现思想 模块分析 作用分析 list_node类设计 list 的迭代器类设计 迭代器类--存在的意义 迭代器类--模拟实现 模板参数 和 成员变量 构造函数 * 运算符的重载 运算符的重载 -- 运…

VScode的debug

如果有命令行参数的话&#xff1a; 打开调试配置&#xff1a; 在 VS Code 中&#xff0c;按下Ctrl Shift D打开调试面板。点击面板顶部的齿轮图标&#xff0c;选择“添加配置…” (Add Configuration...)。 创建新的调试配置&#xff1a; 选择Python&#xff0c;然后选择…

工作记录 2017-02-08

工作记录 2017-02-08 序号 工作 相关人员 1 修改邮件上的问题。 更新RD服务器。 郝 更新的问题 1、CPT的录入页面做修改 1.1、Total 改为 Price 1.2、当删除行时&#xff0c;下面的行自动上移。 2、Pending Payments、Payment Posted、All A/R Accounts页面加了CoIns…

Java SE 面经

1、Java 语言有哪些特点 Java 语言的特点有&#xff1a; ①、面向对象。主要是&#xff1a;封装&#xff0c;继承&#xff0c;多态。 ②、平台无关性。一次编写&#xff0c;到处运行&#xff0c;因此采用 Java 语言编写的程序具有很好的可移植性。 ③、支持多线程。C 语言没…

springboot基于session实现登录

文章目录 1.理解session2.理解ThreadLocal2.1 理解多线程2.2 理解lambda表达式2.3 ThreadLocal 3.基于session登录流程图4.具体登录的代码实现4.1短信发送功能4.2 短信验证码登录注册功能4.登录校验功能4.1 配置登录拦截器LoginInterceptor4.1.1 ThrealLocal类实现 4.2登录拦截…