Linux之多进程

文章目录

    • c程序获取进程pid和ppid
    • 进程相关命令
    • 进程的创建
    • 多进程
    • 进程退出
      • exit()函数
      • _exit函数
    • 进程的等待
      • wait函数
      • waitpid函数
    • 进程的替换
    • 进程间的通信
      • 一、无名管道
      • 二、有名管道
      • 三、信号
        • kill函数
        • raise函数
        • pause() 函数
        • 自定义信号处理函数
        • SIGALARM信号
        • 子进程退出信号SIGCHLD
      • 四、消息队列
        • 创建
        • 删除
        • 发送
        • 接收
      • 五、共享内存
        • 创建
        • 删除
        • 映射
        • 解除映射
    • 进程间同步
      • 信号量
        • 创建信号量集合
        • 设置信号量的数量
        • 操作信号量

c程序获取进程pid和ppid

在 Linux 系统中管理进程使用树型管理方式每个进程都需要与其他某一个进程建立
父子关系, 对应的进程则叫做 父进程
Linux 系统会为每个进程分配 id , 这个 id 作为当前进程的唯一标识, 当进程结束, 则会回收
进程的 id 与 父进程的 id 分别通过 getpid() 与 getppid() 来获取

函数头文件

#include <sys/types.h>
#include <unistd.h>

函数原型

pid_t getpid(void);
pid_t getppid(void);

函数功能:
获取当前进程 id 与 父进程 id

函数返回值:
成功 : getpid() 返回当前进程 id ,getppid() 返回父进程 id
pid_t 的类型 实际为 int , 在系统中采用 typedef 的形式

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

进程相关命令

功能:显示当前进程的状态 (Process Status)
语法

ps [options]

常用语法选项
-A : 列出所有的进程
-e : 与 -A 功能类似
-w : 显示加宽可以显示较多的资讯
-au : 显示较详细的信息
-aux :显示所有包含其他使用者的进程

在这里插入图片描述

示例 : ps -ef 列出所有的进程,相比 ps -aux 信息要少一些

在这里插入图片描述

示例 : ps -ef | grep “可执行文件名” 根据名称查找指定名字

在这里插入图片描述

top [-] [d delay] [q] [c] [S] [s] [i] [n] [b]

选项
d : 改变显示的更新速度,或是在交谈式指令列 (interactive command) 按 s
q : 没有任何延迟的显示速度,如果使用者是有 superuser 的权限,则 top 将会以最高的优先序执行
c : 切换显示模式,共有两种模式,一是只显示执行档的名称,另一种是显示完整的路径与名称
S : 累积模式,会将己完成或消失的子进程 (dead child process) 的 CPU time 累积起来
s : 安全模式,将交谈式指令取消, 避免潜在的危机
i : 不显示任何闲置 (idle) 或无用 (zombie) 的进程
n : 更新的次数,完成后将会退出 top
b : 批次档模式,搭配 “n” 参数一起使用,可以用来将 top 的结果输出到档案内

在这里插入图片描述

仅显示指定进程ID的信息

top -p <进程 id>

在这里插入图片描述

pstree 命令是将所有的进程以树型结构的方式进行展示

在这里插入图片描述

功能:kill 命令是用于结束进程的命令或者用于显示相关信号

kill -9  <pid号>

进程的创建

创建进程的函数需要调用 fork() 函数, 则会产生一个新的进程

函数头文件

#include <sys/types.h>
#include <unistd.h>

函数原型

pid_t fork(void);

函数功能:创建一个子进程

函数返回值
成功 : 返回给父进程是子进程 的 pid , 返回给子进程的是 0
失败 : 返回 -1, 并设置 errno

示例 : 创建一个子进程,并打印 HelloWorld

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main(){
pid_t pid;
pid=fork();
if(pid==-1){
perror("[ERROR fork():]");
return -1;
}
printf("hello\n");
return 0;
}

通过 fork() 函数创建子进程之后,有如下特点:

1、父子进程的执行顺序由操作系统算法决定的,不是由程序本身决定

2、子进程会拷贝父进程地址空间的内容, 包括缓冲区、文件描述符等

eg:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main(){write(1,"hello",5);
fputs("hello",stdout);pid_t pid;
pid=fork();
if(pid==-1){
perror("[ERROR fork():]");
return -1;
}
return 0;
}

结果打印了三个hello

因为fputs是有缓冲区,fork子进程后会拷贝父进程地址空间的内容

多进程

在创建多个进程时, 最主要的原则为 由父进程统一创建,统一管理,
不能进行递归创建

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main(){pid_t pid;pid=fork();if(pid==-1){perror("[ERROR fork():]");exit(EXIT_FAILURE);}else if(pid==0){printf("process son A pid=%d\n",getpid());sleep(2);exit(EXIT_SUCCESS);}else if(pid>0){pid=fork();if(pid==-1){perror("[ERROR fork():]");exit(EXIT_FAILURE);}else if(pid==0){printf("process son B pid=%d\n",getpid());sleep(3);exit(EXIT_SUCCESS);}else if(pid>0){}}
return 0;
}

进程退出

exit()函数

函数头文件

#include <stdlib.h>

函数原型

void exit(int status);

函数功能:结束进程,并刷新缓冲区

函数参数
status : 退出状态值
在系统中定义了两个状态值 :
EXIT_SUCCESS: 正常退出
EXIT_FAILURE: 异常退出, 具体定义在 stdlib.h 中

#define EXIT_FAILURE 1 
#define EXIT_SUCCESS 

_exit函数

函数头文件

#include <unistd.h>

函数原型
void _exit(int status);
函数参数
status : 进程退出的状态值

函数功能:结束进程,不会刷新缓冲区

函数实例1 : exit.c

#include<stdio.h>
#include<stdlib.h>int main()
{printf("using exit----\n");printf("This is the content in buffer\n");exit(0);
}

执行结果为:

using exit----
This is the content in buffer

函数实例2:_exit.c

#include<stdio.h>
#include<stdlib.h>int main()
{printf("using _exit--\n");printf("This is the content in buffer");_exit(0);
}

执行结果为 :

using _exit-- 

printf函数就是使用缓冲I/O的方式,该函数在遇到“\n”换行符时自动的从缓冲区中将记录读出。所以exit()将缓冲区的数据写完后才退出,而_exit()函数直接退出

进程的等待

父进程 调用wait()与waitpid()函数等待子进程退出后,释放子进程遗留的资源

wait函数

函数头文件

#include <sys/types.h>
#include <sys/wait.h>

函数原型

pid_t wait(int *wstatus);

函数功能:让函数调用者进程进入到睡眠状态, 等待子进程进入僵死状态后,释放相关资源并返回

函数参数:

wstatus : 保存子进程退出状态值变量的指针,获取具体值需要使用WEXITSTATUS(status)宏定义

函数返回值:
成功 : 返回退出子进程的 pid
失败 :返回 -1

示例:创建一个子进程,延时3s后退出,父进程等待子进程后退出

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/wait.h>
int main(){
pid_t pid;
pid=fork();if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){printf("the child process pid=%d\n",getpid());
sleep(3);
exit(66);
}
else if(pid>0){
int status=0;
pid_t cpid=wait(&status);
if(cpid==-1){
perror("[ERROR] wait():");
exit(EXIT_FAILURE);
}
printf("the child process pid=%d died status=%d\n",cpid,WEXITSTATUS(status));}return 0;
}

打印结果:

the child process pid=17596
the child process pid=17596 died status=66

在 wait 存储在 satus 变量的值, 存储了很多信息, 通过一系列 W 开头的宏来解析获取

WIFEXITED(status) : 进程是否正常结束

WEXITSTATUS(wstatus) : 获取进程退出状态值, 即exit 函数的参数

WIFSIGNALED(wstatus) : 表示该子进程是否被信号结束的, 返回真,则表示被信号结束的

WTERMSIG(wstatus) : 返回结束该子进程的那个信号的信号值

WCOREDUMP(wstatus) : 表示该子进程被信号唤醒的

WIFSTOPPED(wstatus) : 表示该子进程是否被信号中止 (stop) 的 , 返回真,则表示是被信号中止的

waitpid函数

函数头文件

#include <sys/types.h>
#include <sys/wait.h>

函数原型

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

函数参数:
pid : 进程 id
​ -1 : 可以等待任意子进程
​ >0 : 等待 id 为 pid 的进程

wstatus : 保存子进程退出状态值变量的指针

options : 选项

  • 0:阻塞选项
  • WNOHANG: 非阻塞选项

函数返回值
成功 :

  • >0 : 退出进程的 pid
  • = 0 : 在非阻塞模式下,没有进程退出

失败:
返回-1 并设置 errno

​ 示例 : 创建一个子进程, 子进程运行后 3s 退出, 父进程等待子进程退出

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/wait.h>
int main(){
pid_t pid;
pid=fork();if(pid==-1){perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){printf("the child process pid=%d\n",getpid());
sleep(3);
exit(66);
}
else if(pid>0){
int status=0;
//使用阻塞方式等待进程结束=wait(status)
pid_t cpid=waitpid(-1,&status,0);if(cpid==-1){
perror("[ERROR] wait():");
exit(EXIT_FAILURE);
}
printf("the child process pid=%d died status=%d\n",cpid,WEXITSTATUS(status));}return 0;
}
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<sys/wait.h>
int main(){
pid_t pid;
pid=fork();if(pid==-1){perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){printf("the child process pid=%d\n",getpid());
sleep(3);
exit(66);
}
else if(pid>0){
int status=0;
pid_t cpid;
//使用非阻塞方式,等待子进程结束后结束循环
while((cpid=waitpid(-1,&status,WNOHANG))==0){}if(cpid==-1){
perror("[ERROR] wait():");
exit(EXIT_FAILURE);
}
printf("the child process pid=%d died status=%d\n",cpid,WEXITSTATUS(status));}
return 0;
}

进程的替换

函数原型

int execl(const char *pathname, const char arg, … / (char *) NULL */);int execlp(const char *file, const char
arg, … /(char *) NULL */);int execle(const char *pathname, const char
arg, … /, (char *) NULL, char *const envp[] */);int execv(const char *pathname, char *const argv[]);int execvp(const char *file, char *const argv[]);int execvpe(const char *file, char *const argv[], char *const envp[]);

函数参数

  • pathname:可执行文件的路径名
  • file : 可执行文件名,可以通过 path 环境变量指定的路径
  • arg : 参数列表,以 NULL 结尾
  • argv[] : 参数数组
  • envp[] : 环境变量数组

函数返回值:

  • 成功 : 0
  • 失败 : -1

示例 : 通过 execl 函数族执行 ls -l 命令

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){int result;
result=execl("/bin/ls","ls","-l",NULL);
if(result==-1){
perror("[ERROR] execl:");
}
return 0;
}

通过execv执行ls -l命令

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){int result;
char *argv[]={"ls","-l",NULL};//参数数组
result=execv("/bin/ls",argv);
if(result==-1){
perror("[ERROR] execl:");
}
return 0;
}

通过execvp执行ls -l命令

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){int result;
char *argv[]={"ls","-l",NULL};
result=execvp("ls",argv);//只需要指定 可执行文件名,但是只能通过path环境可以找到的
if(result==-1){
perror("[ERROR] execl:");
}
return 0;
}

进程间的通信

管道分为无名管道与有名管道

  • 无名管道用于父子进程之间通讯
  • 有名管道用于任意进程之间通讯

一、无名管道

无名管道的特点:

  • 无名管道属于单向通讯
  • 无名管道只能用于 父子进程通讯
  • 无名管道发送端叫做写端, 接收端叫做读端
  • 无名管道读端与写端抽象成两个文件进行操作,在无名管道创建成功之后,则会返回读端与写端的文件描述符

创建无名管道需要调用pipe()函数
函数头文件

#include <unistd.h>

函数原型

int pipe(int pipefd[2]);

函数参数
pipefd : 用于存储无名管道读端与写端的文件描述符的数组
pipefd[0] : 读端文件描述符
pipefd[1] : 写端文件描述符
函数返回值:
成功 : 0
失败 :-1, 设置 errno

代码示例:创建子进程,父进程通过管道向子进程发送 “Hello,pipe”

#include<stdio.h>
#include<unistd.h>
#include<wait.h>
#include<stdlib.h>
int main(){pid_t pid;int ret;
int pipefd[2];
ret=pipe(pipefd);//先创建管道,在fork进程
if(ret==-1){
perror("[ERROR] pipe():");
exit(EXIT_FAILURE);
}pid=fork();
if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
ssize_t rbytes;
char buffer[64]={0};
close(pipefd[1]);//关闭写端
//当管道为空时,读管道会阻塞读进程
rbytes=read(pipefd[0],buffer,sizeof(buffer));if(rbytes==-1){
perror("[ERROR] read():");
exit(EXIT_FAILURE);
}
printf("%s\n",buffer);
close(pipefd[0]);
}
else if(pid>0){
close(pipefd[0]);//关闭读端
ssize_t wbytes;
char buffer[64]={"hello pipe"};
wbytes=write(pipefd[1],buffer,sizeof(buffer));
if(wbytes==-1){perror("[ERROR] write():");
wait(NULL);//等待子进程结束
close(pipefd[1]);
exit(EXIT_FAILURE);
}
close(pipefd[1]);
wait(NULL);
}return 0;
}

当管道为空时,读管道会阻塞读进程

当管道的写端被关闭了,从管道中读取剩余数据后,read 函数返回 0

在写入管道时,确保不超过 PIPE_BUF 字节的操作是原子的

当写入的数据达到 PIPE_BUF 字节时,write() 会在必要的时候阻塞直到管道中的可用空间足以原子地完成操作

当写入的数据大于 PIPE_BUF 字节时,write() 会尽可能多传输数据以充满这个管道

管道的大小是有限的, 不能让父/子进程同时对管道进行读/写操作

当一个进程试图想一个管道中写入数据但没有任何进程拥有该管道的打开着的读取描述符, 内核向写入进程发送一个 SIGPIPE 信号

二、有名管道

  • 有名管道是在文件系统中可见的文件, 但是不占用磁盘空间, 仍然在内存中, 可以通过 mkfifo命令创建有名管道
  • 有名管道与无名管道一样,在应用层是基于文件接口进行操作
  • 有名管道用于任意进程之间的通讯, 当管道为空时, 读进程会阻塞.

函数mkfifo()
函数头文件

#include <sys/types.h>
#include <sys/stat.h>

函数原型:

int mkfifo(const char *pathname, mode_t mode);

函数参数:
pathname : 有名管道路径名
mode : 有名管道文件访问权限

函数返回值:
成功 : 返回 0
失败 : 返回 -1, 并设置 errno
创建两个没有血缘关系的进程,使用有名管道进行进程间通讯

读程序

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#define FIFO_NAME "./fifo"
int main(){int fd=open(FIFO_NAME,O_RDONLY);
ssize_t rbytes;
char buffer[64]={0};
if(fd==-1){
perror("[ERROR ]open():");
exit(EXIT_FAILURE);
}
rbytes=read(fd,buffer,sizeof(buffer));
if(rbytes==-1){perror("[ERROR] read():");
exit(EXIT_FAILURE);
}printf("%s\n",buffer);
close(fd);return 0 ;
}

写程序

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<string.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>#define FIFO_NAME "./fifo"
int main(){
int fd;
ssize_t wbytes;
char buffer[64]={"hello fifo"};
mkfifo(FIFO_NAME,0644);
fd=open(FIFO_NAME,O_WRONLY);
if(fd==-1){
perror("[ERROR] open():");
exit(EXIT_FAILURE);
}
wbytes=write(fd,buffer,sizeof(buffer));
if(wbytes==-1){
perror("[ERROR] write:");
exit(EXIT_FAILURE);
}
close(fd);return 0;
}

注意:
如果有名管道的一端以只读方式打开,它会阻塞到另一端以写的方式 (只写,读写)
如果有名管道的一端以只写方式打开,它会阻塞到另一端以读的方式 (只读,读写)

有名管道的优缺点:
优点:可以实现任意进程间通信(包括父子进程),操作起来和文件操作一样
缺点:

  1. 打开的时候需要读写一起进行否则就会阻塞,管道大小是 4096 个字节
  2. 半双工的工作模式,如果和多个进程通信则需要创建多个管道

三、信号

在 Linux 系统可以通过

kill -l

命令查看, 常用的信号列举如下:
SIGINT
该信号在用户键入 INTR 字符 (通常是 Ctrl-C) 时发出,终端驱动程序发送此信号并送到前台进>程中的每一个进程。
SIGQUIT
该信号和 SIGINT 类似,但由 QUIT 字符 (通常是 Ctrl-) 来控制。
SIGILL
该信号在一个进程企图执行一条非法指令时 (可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时) 发出。
SIGFPE
该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数 > 为 0 等其它所有的算术的错误。
SIGKILL
该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略。
SIGALRM
该信号当一个定时器到时的时候发出。
SIGSTOP
该信号用于暂停一个进程,且不能被阻塞、处理或忽略。
SIGTSTP
该信号用于交互停止进程,用户可键入 SUSP 字符时 (通常是 Ctrl-Z) 发出这个信号。
SIGCHLD
子进程改变状态时,父进程会收到这个信号
SIGABRT
进程异常中止

当由进程来发送信号时, 则可以调用kill()函数与raise ()函数

kill函数

函数头文件

#include <sys/types.h>
#include <signal.h>

函数原型

int kill(pid_t pid, int sig);

函数功能:向指定的进程发送一个信号

函数参数
pid : 进程的 id
sig : 信号的 id

函数返回值:

成功: 返回 0
失败: 返回 -1, 并设置 errno

raise函数

函数头文件

#include <sys/types.h>
#include <signal.h>

函数原型

int raise(int sig);

函数参数:
sig : 信号编号

函数返回值
成功 : 返回 0
失败 : 返回 -1, 并设置 errno

示例:创建一个子进程,子进程通过信号暂停,父进程发送 终止信号

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<wait.h>
int main(){
pid_t pid;
pid=fork();
if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
printf("child process  is running pid=%d\n",getpid());
raise(SIGSTOP);//给自己发送阻塞信号
printf("child process is stop pid=%d\n",getpid());
}
else if(pid>0){int ret;
sleep(1);
ret=kill(pid,SIGKILL);//给子进程发送终止信号if(ret==0){
printf("parent pid=%d kill child pid=%d\n",getpid(),pid);
}
wait(NULL);
exit(EXIT_SUCCESS);}
return 0;
}
pause() 函数

功能:pause() 函数会让调用它的进程挂起(暂停执行),直到接收到一个信号。这意味着程序会一直暂停,直到有外部事件(如用户输入或者其他进程发送的信号)唤醒它。它没有参数,也不允许指定暂停的时间长度。
头文件:

#include <unistd.h>

用法: pause(); // 程序暂停,等待信号唤醒。
返回值:接收到信号后,pause() 会返回接收到的信号的编号。如果没有信号到来,它就不会返回,也就是说正常情况下这个函数的调用不会返回到调用点

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<wait.h>
int main(){
pid_t pid;
pid=fork();
if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
printf("child process  is running pid=%d\n",getpid());
pause();
printf("child process is stop pid=%d\n",getpid());
}
else if(pid>0){int ret;
sleep(3);
ret=kill(pid,SIGUSR1);//SIGUSR1也是终止信号printf("parent pid=%d kill child pid=%d\n",getpid(),pid);
wait(NULL);
exit(EXIT_SUCCESS);}
return 0;
}
自定义信号处理函数

对于每种信号都有相应的默认处理方式
进程退出:
SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
进程忽略
SIGCHLD,SIGPWR,SIGURG,SIGWINCH
进程暂停
SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU

通过signal函数设置信号处理方式
函数头文件

#include <signal.h>

函数原型

sighandler_t signal(int signum, sighandler_t handler);

函数功能:
设置信号的处理方式, 如果是自定义处理方式,提供函数地址,注册到内核中
函数参数
signum : 信号编号

函数返回值
成功 : 返回信号处理函数地址
失败 : 返回 SIG_ERR , 并设置 errno

示例:创建一个子进程, 父进程给子进程发送 SIGUSR1 信号,并使用自定义的处理方式

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<wait.h>
#include<string.h>
void do_signal_handler(int sig){printf("Receive signal %s\n",strsignal(sig));
}
int main(){
pid_t pid;
pid=fork();
__sighandler_t h;
h=signal(SIGUSR1,do_signal_handler);
if(h==SIG_ERR){
perror("[ERROR] signal():");
exit(EXIT_FAILURE);
}if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else if(pid==0){
printf("child process  is running pid=%d\n",getpid());
pause();
printf("child process is stop pid=%d\n",getpid());
}
else if(pid>0){int ret;
sleep(3);
ret=kill(pid,SIGUSR1);printf("parent pid=%d kill child pid=%d\n",getpid(),pid);
wait(NULL);
exit(EXIT_SUCCESS);}
return 0;
}
SIGALARM信号

函数头文件

#include <unistd.h>

函数原型

unsigned int alarm(unsigned int seconds);

函数功能:设置定时器的秒数
函数参数:
seconds : 定时的时间秒数
函数返回值:
返回上一次进程设置定时器剩余的秒数

要点:
定时器的定时任务由内核完成, alarm 函数值负责设置定时时间, 并告诉内核启动定时器
当定时时间超时后,内核会向进程发出 SIGALRM 信号

#include<stdio.h>
#include<string.h>
#include<unistd.h>
int main(){
int ret;
ret=alarm(5);
printf("ret=%d\n",ret);//返回上一次进程设置定时器剩余的秒数
sleep(2);
ret=alarm(4);
printf("ret=%d\n",ret);
return 0;}

打印结果:

ret=0
ret=3
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
void do_handler_alarm(int sign){printf("Receice <%s>\n",strsignal(sign));
}
int main(){
int ret;
ret=alarm(5);
__sighandler_t sigret;
sigret=signal(SIGALRM,do_handler_alarm);
pause();//等待计时器结束
return 0;}
子进程退出信号SIGCHLD

问题:在使用 wait() 函数时,由于阻塞或者非阻塞都非常消耗资源,并且在阻塞情况下,父进程不能执行其他逻辑

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
#include<wait.h>
#include<stdlib.h>
void do_handler_chld(int sign){printf("Receice <%s>\n",strsignal(sign));
wait(NULL);//不会阻塞,立即释放子进程的资源
}
int main(){pid_t pid;
__sighandler_t sigret;
sigret=signal(SIGCHLD,do_handler_chld);
pid=fork();
if(pid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}else if(pid==0){
printf("chlid is running pid=%d\n",getpid());
sleep(2);
exit(EXIT_SUCCESS);
}
else if(pid>0){
while(1){}
}return 0;}

默认处理方式是忽略的

四、消息队列

System V IPC 对象共有三种

  • 消息队列
  • 共享内存
  • 信号量

每个 IPC 对象都有一个唯一的 ID, 可以通过ftok()函数生成,ftok函数具体说明如下:

函数头文件

#include <sys/types.h>
#include <sys/ipc.h>

函数原型

key_t ftok(const char *pathname, int proj_id);

函数参数

  • pathname : 文件路径名
  • proj_id : 8 bit 的 id 整数

函数返回值:

成功: 返回合成的 key(key 由 文件的 inode 节点号 与 proj_id 构成,inode 节点号 :每个存在的文件操作系统都会有唯一的编号, 通过 ls - i 命令查看)
失败 : -1, 并设置 errno

ls - i 命令查看inode节点号

在这里插入图片描述

创建

函数头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

函数原型

int msgget(key_t key, int msgflg);

函数参数
key : 由 ftok 函数合成
msgflg : 消息队列标志

  • IPC_CREAT : 创建标志
  • IPC_EXCL : 如果消息队列存在,则报错, errno 设置为 EEXIST
    权限控制标志

函数返回值
成功 : 返回 消息队列 id
失败 : 返回 -1,并设置 errno

#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<stdlib.h>
#include<string.h>
#define PATH "."
#define PRO_ID 88//8bit整数int main(){
key_t key;
int mid;
key=ftok(PATH,PRO_ID);
if(key==-1){
perror("[ERROR] ftok():");
exit(EXIT_FAILURE);
}
mid=msgget(key,IPC_CREAT|0644);
if(mid==-1){
perror("[ERROR] msgget():");
exit(EXIT_FAILURE);
}
printf("message id=%d\n ",mid);return 0;
}

在这里插入图片描述

消息队列可以通过下面的命令查看

ipcs -q

删除消息队列

ipcrm -q [msqid]
删除

删除消息队列需要调用msgctl函数, 具体信息如下

函数头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

函数原型

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

函数参数

msqid : 消息队列 id
cmd : 命令字

  • IPC_STAT:获取消息队列属性
  • IPC_SET : 设置消息队列属性
  • IPC_RMID : 删除消息队列属性 ,用此命名时,第三个参数为 NULL

buf : 消息队列属性结构体对象指针

消息队列属性结构体定义如下:

struct ipc_perm {
key_t __key; /* Key supplied to msgget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};

在上一个示例的基础上,加上删除队列的代码

#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<stdlib.h>
#include<string.h>
#define PATH "."
#define PRO_ID 88//8bit整数int main(){
key_t key;
int mid;
int ret;
key=ftok(PATH,PRO_ID);
if(key==-1){
perror("[ERROR] ftok():");
exit(EXIT_FAILURE);
}
mid=msgget(key,IPC_CREAT|0644);
if(mid==-1){
perror("[ERROR] msgget():");
exit(EXIT_FAILURE);
}
printf("message id=%d\n ",mid);
ret=msgctl(mid,IPC_RMID,NULL);
if(ret==-1){
perror("[ERROR] msgctl():");
exit(EXIT_FAILURE);
}return 0;
}
发送

函数头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

函数原型

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

函数参数:

1.msqid : 消息队列 ID
2.msgp : 消息结构体指针
3.msgsz : 消息内容的长度
4.msgflg : 消息队列标志,默认可以填 0
IPC_NOWAIT: 可以设置非阻塞

函数返回值
成功 : 返回 0
失败 : -1, 并设置 errno

消息结构定义形式如下:

struct msgbuf {//结构体名称自定义即可
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
接收

函数头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

函数原型

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

函数参数:

msqid : 消息队列 id
msgp : 消息结构指针
msgsz : 消息内容的长度
msgtyp : 消息类型
msgflg : 消息队列标志,默认可以填 0
IPC_NOWAIT: 可以设置非阻塞

函数返回值:
成功 : 返回实际读取消息内容的字节数
失败 : -1, 并设置 errno

示例: 创建两个没有血缘关系的进程, 使用 消息队列进行通讯

write_message.c

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>#define PATHNAME "."
#define PRO_ID 10#define MSG_TYPE 100
#define MSG_SZ 64
struct msgbuf{
long mtype;
char mtext[MSG_SZ];
};int main(){
key_t key;
key=ftok(PATHNAME,PRO_ID);
int msg_id=msgget(key,IPC_CREAT|0666);
if(msg_id==-1){
perror("[ERROR] msgget():");
exit(EXIT_FAILURE);
}
struct msgbuf message={MSG_TYPE,"hello msg queue"};
int ret=msgsnd(msg_id,&message,strlen(message.mtext),0);
if(ret==-1){
perror("[ERROR] msgsnd():");
exit(EXIT_FAILURE);
}
printf("message send success!");return 0;
}

read_message.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<errno.h>
#define PATHNAME "."
#define PRO_ID 10#define MSG_TYPE 100
#define MSG_SZ 64
struct msgbuf{
long mtype;
char mtext[MSG_SZ];
};int main(){
struct msgbuf message;
ssize_t rbytes;
key_t key;
key=ftok(PATHNAME,PRO_ID);
int msqid=msgget(key,IPC_CREAT|0666);//接收方也需要mesget一次
if(msqid == -1){ perror("ftok(): "); exit(EXIT_FAILURE); }msgrcv(msqid,&message,MSG_SZ,MSG_TYPE,0);
printf("Receice message %s\n types=%ld\n",message.mtext,message.mtype);
return 0;}

五、共享内存

共享内存是将分配的物理空间直接映射到进程的用户虚拟地址空间中, 减少数据在内核空间缓存

共享内存是一种效率较高的进程间通讯的方式

在 Linux 系统中通过 ipcs -m 查看所有的共享内存

创建

函数头文件

#include <sys/ipc.h>
#include <sys/shm.h>

函数原型

int shmget(key_t key, size_t size, int shmflg);

函数功能
创建一个共享内存, 并返回 ID

函数参数
key : 由 ftok() 函数返回
size : 共享内存的大小
shmflg : 共享内存标志

  • IPC_CREAT : 创建标志
  • IPC_EXCL : 如果消息队列存在,则报错, errno 设置为 EEXIST
    权限控制标志

函数返回值

成功 : 返回 共享内存 id
失败 : 返回 -1, 并设置 errno

删除

函数头文件

#include <sys/ipc.h>
#include <sys/shm.h>

函数原型

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

函数功能

1.共享内存控制函数, 功能由具体的功能命令字决定
2.函数参数

  • shmid : 共享内存 id

  • cmd : 控制命令字

    ​ IPC_STAT: 获取 消息队列属性

    ​ IPC_SET : 设置消息队列属性

    ​ IPC_RMID : 删除消息队列属性 ,用此命名时,第三个参数为 NULL

3.buf : 共享内存属性结构体指针

消息队列属性结构体定义如下:

struct ipc_perm {
key_t __key; /* Key supplied to msgget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};

函数返回值

  • 成功 : 返回 0 , 特殊命令字除外
  • 失败 : 返回 -1

创建一个共享内存后,输出共享内存 id, 删除共享内存

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#define PATHNAME "."
#define PRO_ID 100
#define SZ 256
int main(){key_t key;
key=ftok(PATHNAME,PRO_ID);
if(key==-1){
perror("[ERROR] ftok:");
exit(EXIT_FAILURE);
}
int shmid=shmget(key,SZ,IPC_CREAT|0666);
if(shmid==-1){
perror("[ERROR] shmid():");
exit(EXIT_FAILURE);
}
printf("shmid=%d\n",shmid);
int ret=shmctl(shmid,IPC_RMID,NULL);
if(ret==-1){perror("[ERROR] shmctl():");  exit(EXIT_FAILURE);
}return 0;}
映射

函数头文件

#include <sys/types.h>
#include <sys/shm.h>

函数原型:

void *shmat(int shmid, const void *shmaddr, int shmflg);

函数功能:将进程地址空间映射到共享内存上

函数参数:
shmid : 共享内存 id
shmaddr : 指定映射的到进程地址空间的起始地址,指定为 NULL 时, 由系统选择映射的地址
shmflg : 共享内存标志, 一般设置为 0

函数返回值:
成功 : 返回映射到进程地址空间的起始地址
失败 : (void *) -1, 并设置 errno

解除映射

函数头文件

#include <sys/types.h>
#include <sys/shm.h>

函数原型

int shmdt(const void *shmaddr);

函数功能:解除进程地址空间与共享内存的映射

函数参数:

shm_addr: 由shmat返回的地址指针

函数返回值:
成功 : 返回 0
失败 : 返回 -1, 并设置 errno

示例:使用共享内存进行进程间通讯

share_write.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>#define PATHNAME "."
#define PRO_ID 101
#define SZ 256int main(){
key_t key;
int shmid,ret;
void *addr=NULL;
key=ftok(PATHNAME,PRO_ID);
if(key==-1){
perror("[ERROR] ftok():");
exit(EXIT_FAILURE);
}shmid=shmget(key,SZ,IPC_CREAT|0666);
if(shmid==-1){
perror("[ERROR] shmget():");
exit(EXIT_FAILURE);
}
printf("shmid=%d",shmid);
addr=shmat(shmid,NULL,0);
if(addr==(void *)-1){
perror("[ERROR] shmat():");
exit(EXIT_FAILURE);
}
memset(addr,'A',10);//给addr放10个A
shmdt(addr);
return 0;}

share_read.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>#define PATHNAME "."
#define PRO_ID 101
#define SZ 256int main(){
key_t key;
int shmid,ret;
char buffer[10]={0};
void *addr=NULL;
key=ftok(PATHNAME,PRO_ID);
if(key==-1){
perror("[ERROR] ftok():");
exit(EXIT_FAILURE);
}shmid=shmget(key,SZ,IPC_CREAT|0666);
if(shmid==-1){
perror("[ERROR] shmget():");
exit(EXIT_FAILURE);
}
addr=shmat(shmid,NULL,0);
if(addr==(void *)-1){
perror("[ERROR] shmat():");
exit(EXIT_FAILURE);
}
memcpy(buffer,addr,10);
printf("Receive %s\n",buffer);
shmdt(addr);
return 0;}

进程间同步

信号量

互斥: 同一时刻只有一个进程访问临界资源
同步: 在互斥的基础上增加了进程对临界资源的访问顺序
进程主要的同步与互斥手段是信号量
信号量: 由内核维护的整数, 其值被限制为大于或等于 0

信号可以执行如下操作:
将信号量设置成一个具体的值
在信号量当前值的基础上加上一个数值
在信号量当前值的基础上减上一个数值
等待信号量的值为 0

一般信号量分为 二值信号量 与 计数信号量

  • 二值信号量: 一般指的是信号量 的值为 1, 可以理解为只对应一个资源
  • 计数信号量: 一般指的是值大于等于 2 , 可以理解为对应多个资源

在 Linux 系统中查询信号量使用

ipcs -s
创建信号量集合

函数头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

函数原型

int semget(key_t key, int nsems, int semflg);

函数功能:创建一个信号量集合

函数参数
key : 由 ftok() 函数生成
nsems : 信号量的数量
semflg : 信号量集合的标志

  • IPC_CREAT : 创建标志
  • IPC_EXCL : 与 IPC_CREAT 标志一起使用, 如果信号量集合存在就报错
    权限标志

函数返回值
成功 : 返回信号量集合的 id
失败 : -1, 并设置 errno

设置信号量的数量

函数头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

函数原型

int semctl(int semid, int semnum, int cmd, …);

函数功能:信号集合控制函数,根据 cmd 决定当前函数的功能

函数参数

  • semid : 信号量集合的 id
  • semnum : 信号量的编号,信号量的编号从 0 开始
  • cmd : 命令控制字
    SETVAL: 设置信号量的值
    GETVAL: 获取信号量的值
  • …: 后面是属于可变参参数列表, 根据不同的命令有不同的参数

信号量集合调用semctl函数,设置命令为IPC_RMID(删除信号量)时
注意 : 在使用 IPC_RMID 时,第 三个参数会被忽略

具体使用方法如下:

ret = semctl(semid,IPC_RMID,NULL);

函数返回值
成功 : 根据不同的命令有不同的返回值, 可以查看帮助文档关于 RETURN 的说明
GETNCNT the value of semncnt
GETPID the value of sempid
GETVAL the value of semval
GETZCNT the value of semzcnt.
All other cmd values return 0 on success.
失败 : 返回 -1, 并设置 errno

在使用命令时需要使用 union semun 共用体, 具体定义如下:

union semun {int val; /* Value for SETVAL */
struct semid_ds   buf; /*Buffer for IPC_STAT, IPC_SET */unsigned short array; /*Array for GETALL, SETALL */struct seminfo__buf; /*Buffer for IPC_INFO
(Linux-specific) */};

示例:创建一个信号量集合,集合中包含一个信号量, 并设置信号量的值为 1

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#define SEM_PATHNAME "."
#define SEM_PRO_ID 101
union semun{
int val;
};
int main(){
key_t key=ftok(SEM_PATHNAME,SEM_PRO_ID);
int sem_id,ret;
union semun s;
if(key==-1){
perror("[ERROR] ftok():");
exit(EXIT_FAILURE);
}
sem_id=semget(key,1,IPC_CREAT|0666);
if(sem_id==-1){
perror("[ERROR] semget():");
exit(EXIT_FAILURE);
}s.val=1;
ret=semctl(sem_id,0,SETVAL,s);//初始化信号量的值,设置 第一个信号量的值为1
if(ret==-1){
perror("[ERROR] semctl():");
exit(EXIT_FAILURE);
}return 0;
}
操作信号量

函数头文件

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

函数原型

int semop(int semid, struct sembuf *sops, size_t nsops);

函数功能:信号量操作函数,用于占用信号量、释放信号量、设置信号量等待
函数参数:

  • semid : 信号量集合 id
  • sops : 信号量操作结构体指针
  • nsops : 操作的信号量的数量

函数返回值:
成功 : 返回 0
失败 : 返回 -1, 并设置 errno

struct sembuf 结构体

struct sembuf{
unsigned short sem_num;//信号量编号, 从 0 开始
short sem_op;/*信号量操作
-1 : 占用资源
+1 : 释放资源
0 : 等待资源
*/
short sem_flg;
/*
信号量操作标志
IPC_NOWAIT : 非阻塞,在信号量的值为 0 时, 会立即返回
SEM_UNDO : 在进程终止时, 会自动释放信号量
*/
};

示例:使用信号量解决父子进程对终端的竞争

sem.h

#ifndef __SEM_H__
#define __SEM_H__#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<stdlib.h>
//创建信号量 nsems:信号量的个数,对应信号量的值values
extern int sem_create(int nsems,unsigned short values[]);
//占用信号量 semid信号量集合的id ,对应要占用的信号量 eg:0 ,1,2
extern int sem_p(int semid,int semnum);
//释放信号量 semid信号量集合的id ,对应要占用的信号量 eg:0 ,1,2
extern int sem_v(int  semid,int semnum);
//删除信号量集合
extern int sem_del(int semid);#endif

sem.c

#include "sem.h"
#define SEM_PATHNAME "."
#define SEM_PRO_ID 102
union semun{
unsigned short *array;
};int sem_create(int nsems,unsigned short values[]){key_t key;int sem_id,ret;union semun s;key=ftok(SEM_PATHNAME,SEM_PRO_ID);//生成keyif(key==-1){perror("[ERROR] ftok():");exit(EXIT_FAILURE);}sem_id=semget(key,nsems,IPC_CREAT|0666);//创建信号量集合if(sem_id==-1){perror("[ERROR] semget():");exit(EXIT_FAILURE);}s.array=values;ret=semctl(sem_id,0,SETALL,s);//按位初始化(使用SETALL时,第二位参数设置0)if(ret==-1){perror("[ERROR] semctl():");exit(EXIT_FAILURE);}return sem_id;}
int sem_p(int semid,int semnum){
struct sembuf sops;
sops.sem_num=semnum;
sops.sem_op=-1;
sops.sem_flg=SEM_UNDO;
return semop(semid,&sops,1);
}
int sem_v(int  semid,int semnum){
struct sembuf sops;
sops.sem_num=semnum;
sops.sem_op=1;
sops.sem_flg=SEM_UNDO;
return semop(semid,&sops,1);
}int sem_del(int semid){return semctl(semid,0,IPC_RMID,NULL);}

main.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>#include"sem.h"
int main(){
pid_t cpid;
int semid;
unsigned short values[]={1};
semid=sem_create(1,values);
cpid=fork();
if(cpid==-1){
perror("[ERROR] fork():");
exit(EXIT_FAILURE);
}
else  if(cpid==0){
while(1){
sem_p(semid,0);
printf("----------------\n");
printf("C start\n");
sleep(1);
printf("C End \n");
printf("-----------------\n");
sem_v(semid,0);
}
}
else if(cpid>0){
sleep(1);
while(1){
sem_p(semid,0);
printf("----------------\n");
printf("P start\n");
sleep(1);
printf("P End \n");
printf("-----------------\n");
sem_v(semid,0);
}
}return 0;
}

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

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

相关文章

基于mybatis-plus的多语言扩展

概览 对于表中字段&#xff0c;需要实现多语言的方案探讨&#xff1a; 1.表中横向扩展多个字段分别存储中文&#xff0c;英文&#xff0c;俄语等语言字段&#xff0c;查询时&#xff0c;根据需要查询的语言&#xff0c;进行查询 2.增加一张多语言表&#xff0c;存储多语言信…

IC开发——VCS基本用法

1. 简介 VCS是编译型verilog仿真器&#xff0c;处理verilog的源码过程如下&#xff1a; VCS先将verilog/systemverilog文件转化为C文件&#xff0c;在linux下编译链接生成可执行文件&#xff0c;在linux下运行simv即可得到仿真结果。 VCS使用步骤&#xff0c;先编译verilog源…

STM32--ADC

一、简介 *ADC&#xff08;Analog-Digital Converter&#xff09;模拟-数字转换器 *ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量&#xff0c;建立模拟电路到数字电路的桥梁 *12位逐次逼近型ADC&#xff0c;1us转换时间 *输入电压范围&#xff1a;0~3.3V&…

redisson 释放分布式锁 踩坑

java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 48c213c9-1945-4c1b-821e-6d32e347eb44 thread-id: 69 出错代码&#xff1a; private void insertHourLog(Timestamp lastHourStartTimeStamp) {RLock lock red…

leetcode 1241每个帖子的评论数(postgresql)

需求 编写 SQL 语句以查找每个帖子的评论数。 结果表应包含帖子的 post_id 和对应的评论数 number_of_comments 并且按 post_id 升序排列。 Submissions 可能包含重复的评论。您应该计算每个帖子的唯一评论数。 Submissions 可能包含重复的帖子。您应该将它们视为一个帖子。…

BI工具如何为金融行业带来变革?金融行业营销管理策略大揭秘

当今数字化时代&#xff0c;金融行业正经历着前所未有的变革。随着大数据、人工智能、区块链等新兴技术的兴起&#xff0c;金融机构正面临着重新定义服务模式、风险管理和客户体验的挑战。商业智能&#xff08;BI&#xff09;作为这一变革的关键驱动力&#xff0c;已经成为金融…

ComfyUI工作流网站

https://openart.ai/home https://comfyworkflows.com/ https://civitai.com/

claude3国内API接口对接

众所周知&#xff0c;由于地理位置原因&#xff0c;Claude3不对国内开放&#xff0c;而国内的镜像网站使用又贵的离谱&#xff01; 因此&#xff0c;团队萌生了一个想法&#xff1a;为什么不创建一个一站式的平台&#xff0c;让用户能够通过单一的接口与多个模型交流呢&#x…

视频营销的智能剪辑:Kompas.ai如何塑造影响力视频内容

引言&#xff1a; 在当今数字化的营销领域&#xff0c;视频内容已经成为品牌吸引用户注意力、建立品牌形象和提升用户参与度的重要方式。然而&#xff0c;要想制作出具有影响力的视频内容&#xff0c;并不是一件容易的事情。这就需要借助先进的技术和工具&#xff0c;如人工智能…

2024开放式蓝牙耳机推荐,五款性价比最高的耳机推荐

在我们的日常生活中&#xff0c;无论是上下班通勤还是锻炼身体&#xff0c;耳机都是我们放松心情、驱散无聊的好伙伴。不过&#xff0c;面对市场上不断涌现的开放式蓝牙耳机&#xff0c;挑选一款既符合个人喜好又满足需求的产品&#xff0c;确实需要一些技巧。今天&#xff0c;…

springboot实现多开发环境匹配置

首先logbok-spring.xml里面的内容 <?xml version"1.0" encoding"UTF-8"?> <configuration><!-- 开发、测试环境 --><springProfile name"dev,test"><include resource"org/springframework/boot/logging/log…

【国信华源:以专业服务,协助水利厅抵御强暴雨】

5月18日-19日&#xff0c;广西出现入汛以来最强暴雨天气过程&#xff0c;钦州、防城港、北海、南宁等地出现特大暴雨&#xff0c;多地打破降雨量极值。国信华源技术团队积极行动驻守一线&#xff0c;为打好山洪灾害防御的提前战、主动战提供了技术支撑。 5月17日18时&#xff0…

六.逼格拉满-Prometheus+Grafana微服务监控告警

前言 微服务架构是一个分布式系统&#xff0c;由多个独立的服务组成&#xff0c;每个服务可能运行在不同的容器、虚拟机或物理机上&#xff0c;那么在生产环境中我们需要随时监控服务的状态&#xff0c;以应对各种突发情况&#xff0c;比如&#xff1a;内存爆满&#xff0c;CP…

【全开源】Java养老护理助浴陪诊小程序医院陪护陪诊小程序APP源码

打造智慧养老服务新篇章 一、引言&#xff1a;养老护理的数字化转型 随着老龄化社会的到来&#xff0c;养老护理需求日益凸显。为了更好地满足老年人及其家庭的需求&#xff0c;我们推出了养老护理助浴陪诊小程序系统源码。该系统源码旨在通过数字化技术&#xff0c;优化养老…

Apache Doris 基础 -- 数据表设计(数据模型)

Versions: 2.1 1、模型概览 本主题从逻辑角度介绍了Doris中的数据模型&#xff0c;以便您可以在不同的业务场景中更好地使用Doris。 基本概念 本文主要从逻辑的角度描述Doris的数据模型&#xff0c;旨在帮助用户在不同的场景更好地利用Doris。 在Doris中&#xff0c;数据在…

基于Android Studio图书管理,图书借阅系统

目录 项目介绍 图片展示 运行环境 获取方式 项目介绍 用户 书架&#xff1a;搜索书籍&#xff0c;查看书籍&#xff0c;借阅书籍&#xff0c;收藏书籍&#xff0c;借阅书籍必须在一个月之内还书&#xff1b; 我的&#xff1a;可以修改密码&#xff0c;退出登录&#xff…

Fwknop:单包授权与端口试探工具

介绍 fwknop实现了一种称为单包授权&#xff08;SPA&#xff09;的授权方案&#xff0c;用于隐藏服务。SPA将单个数据包经过加密&#xff0c;不可重放&#xff0c;并通过HMAC进行身份验证&#xff0c;以便在传达到隐藏在防火墙后面的服务。 SPA的主要应用场景是防火墙来过滤一…

ssm球场计费管理系统-计算机毕业设计源码77275

摘 要 大数据时代下&#xff0c;数据呈爆炸式地增长。为了迎合信息化时代的潮流和信息化安全的要求&#xff0c;利用互联网服务于其他行业&#xff0c;促进生产&#xff0c;已经是成为一种势不可挡的趋势。在球馆计费管理的要求下&#xff0c;开发一款整体式结构的球场计费管理…

三品软件:打造高效安全的图文档管理体系

在数字化转型的浪潮中&#xff0c;工程设计单位和企业设计部门面临着电子图文档管理的巨大挑战。随着电子图纸和文档数量的激增&#xff0c;如何有效组织、管理和共享这些资源&#xff0c;成为提升工作效率和保障信息安全的关键。本文将探讨当前图文档管理面临的问题&#xff0…