Linux系统编程 day06 进程间通信
进程间通信
- 1. 进程间通信的概念
- 2. 匿名管道pipe
- 3. 命名管道FIFO
- 4. 内存映射区
1. 进程间通信的概念
在Linux的环境下,进程地址空间是相互独立的,每个进程有着各自不同的用户地址空间。一个进程不能访问另一个进程中的内容,要进行数据交换必须要通过内核,在内核中开辟块缓冲区,一个进程将数据从用户空间拷贝到内核缓冲区中,另一个进程从内核缓冲区中将数据读走。内核提供的这种机制称为进程间通信(IPC)。
在进程间完成数据传输需要借助操作系统提供的特殊方法,比如文件、管道、FIFO、内存映射区、消息队列、信号、套接字等。如今常用的进程间通信主要有管道(最简单)、信号(开销最小)、内存映射区(无血缘关系)、本地套接字(最稳定)。
2. 匿名管道pipe
管道是一种最基本的进程间通信机制,也称为匿名管道,应用于有血缘关系的进程间进行通信。管道的本质是一块内核缓冲区,内部使用的环形队列实现,由两个文件描述符进行引用,其中一个表示读端,另一个表示写端。管道的数据从管道的写端流入管道,从读端流出。当两个进程都死亡的时候,管道也会自动消失。管道不管读端还是写端默认都是阻塞的。管道的默认缓冲区大小为4K,可以使用ulimit -a
命令获取大小。
管道的数据一旦被读走,便不在管道中存在,不可以反复读取。管道的数据只能在一个方向上流动,如果需要实现双向流动则需要使用两个管道。匿名管道只能在有血缘关系的进程中使用。我们用pipe
函数来创建管道。
pipe
函数的原型如下:
#include <unistd.h>int pipe(int pipefd[2]); // 创建管道
其中pipefd
为管道写端和读端的文件描述符,其中pipefd[0]
为管道读端的文件描述符,pipefd[1]
为管道写端的文件描述符。当函数调用成功创建了管道返回0,失败则返回-1并设置errno。
在使用匿名管道进行通信的时候,一般是先用pipe
函数创建管道,再使用fork
函数创建子进程。这样父子进程就具有了相同的文件描述符,就会指向同一个管道。
在管道的通信中,读写数据也是使用read
和write
。管道的示例代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>int main()
{// 创建管道//int pipe(int pipefd[2]);int fd[2];int ret = pipe(fd);if(ret < 0){perror("pipe error");return -1;}// 创建子进程pid_t pid = fork();if(pid < 0){perror("fork error");return -1;}else if(pid == 0){// 子进程关闭写端close(fd[1]);char buf[1024];memset(buf, 0x00, sizeof(buf));sleep(5);read(fd[0], buf, sizeof buf);printf("child: read over, pid = [%d], fpid = [%d], buf = [%s]\n", getpid(), getppid(), buf);}else{// 父进程关闭读端close(fd[0]);write(fd[1], "helloworld", strlen("helloworld"));printf("father: write over, pid = [%d], fpid = [%d]\n", getpid(), getppid());pid_t wpid = wait(NULL);printf("child [%d] is dead!\n", wpid);}return 0;
}
在shell中我们查询某一个进程的时候我们会使用ps -ef | grep --color=auto bash
,这里的|
就是管道。我们使用父进程进程去执行ps -ef
命令,由于我们需要交给grep
去作为输入,所以在父进程中需要将输出重定向到管道的写端。子进程执行grep
的时候会从输入进行读取内容,因此我们需要将输入重定向到管道读端。重定向的时候需要使用dup2
函数,需要执行命令则需要使用execl
和execlp
函数,示例程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>int main()
{// 创建管道int pipefd[2];int ret = pipe(pipefd);if(ret < 0){// 创建失败perror("pipe error");return -1;}// 创建子进程pid_t pid = fork();if(pid < 0){// 创建失败perror("fork error");return -1;}else if(pid == 0){// 子进程关闭写端close(pipefd[1]);// 将标准输入重定向到管道读端dup2(pipefd[0], STDIN_FILENO);execlp("grep", "grep", "--color=auto", "bash", NULL);perror("execlp error");}else {// 父进程关闭读端close(pipefd[0]);// 将标准输出重定向到管道写端dup2(pipefd[1], STDOUT_FILENO);execlp("ps", "ps", "-ef", NULL);perror("execlp error");}return 0;
}
当管道有数据的时候,read
可以正常读,并返回读出来的字节数;当管道没有数据的时候,若写端全部关闭,则read
函数解出阻塞状态,返回0,相当于读文件读到了尾部。若写端没有关闭,则read
阻塞。
若读端全部关闭,进行写操作的时候则管道会破裂,进程终止,内核会给当前进程发送SIGPIPE
信号。若读端没有完全关闭,缓冲区写满了则write
会阻塞,缓冲区没有满则可以继续write
。
管道默认两端都是阻塞的,若要设置为非阻塞,则可以使用前面提过的fcntl
函数。首先使用F_GETFL
获取flags,然后在添加O_NONBLOCK
使用F_SETFL
设置即可。当读端设置为非阻塞状态的时候,会有以下四种情况:
- 写端没有关闭,管道中没有数据可读,则
read
返回-1。 - 写端没有关闭,管道中有数据可读,则
read
返回实际读到的字节数。 - 写端已经关闭,管道中有数据可读,则
read
返回实际读到的字节数。 - 写端已经关闭,管道中没有数据可读,则
read
返回0。
设置的流程为:
int flags = fcntl(fd[0], F_GETFL, 0);
flags = flags | O_NONBLOCK;
fcntl(fd[0], F_SETFL, flags);
除了前面使用的ulimit -a
可以查看到管道缓冲区的大小之外,也可以使用fpathconf
函数,该函数的原型为:
#include <unistd.h>long fpathconf(int fd, int name);
这个函数会根据name
的参数设置返回文件描述符fd
对应文件的配置内容。比如获取管道缓冲区则需要把name
设置为_PC_PIPE_BUF
。如下面查看管道的缓冲区大小。
printf("pipe size = [%ld]\n", fpathconf(fd[0], _PC_PIPE_BUF));
printf("pipe size = [%ld]\n", fpathconf(fd[1], _PC_PIPE_BUF));
更多的name
参数设置可以使用man fpathconf
查询帮助文档。
3. 命名管道FIFO
使用pipe
管道只能实现有血缘关系的两个进程间的通信,那么对于两个没有血缘关系的进程又应该怎么实现呢?则需要这一节的命名管道FIFO
。
FIFO
是Linux上基于文件类型的一种通信方式,文件类型为p
,但是FIFO
在磁盘上并没有数据块,文件大小为0,仅仅用于标识内核中的一条通道。进程可以通过read
或者write
函数去对这一条通道进行操作,也就是内核缓冲区,这样就实现了进程间的通信。
要使用命名管道,就得先创建命名管道文件,创建管道文件使用mkfifo
函数,该函数的原型为:
#include <sys/types.h>#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);
其中参数第一个pathname
表示管道的路径名,mode
表示权限,使用一个三位八进制数表示。创建成功函数返回0
,创建失败则函数返回-1
并设置errno
。
FIFO
严格遵守先进先出的规则,也就是对于管道的读总是从管道的开始处返回数据,而对于管道的写则是添加到末尾。因此命名管道不支持使用lseek
等对文件定位的操作。
使用FIFO
完成进程间通信的示意图如下:
既然是两个进程,也就是说两个进程中的程序都需要打开管道,也就是需要找到文件。若写进程没有打开就去使用读进程,则可能因为管道的文件不存在而报错。因此我们在使用管道或者创建管道之前要先判断文件是否存在。这个可以使用access
函数实现,该函数的原型为:
#include <unistd.h>int access(const char *pathname, int mode);
该函数的第一个参数pathname
是文件的路径,第二个参数mode
表示要测试的模式,有四个参数可以传,分别为:
F_OK
: 文件存在R_OK
:有读权限W_OK
:有写权限X_OK
:有执行权限
当有对应的权限或者文件存在的时候,该函数的返回值为0
,若没有对应权限或者文件不存在则该函数返回-1
。
接下来我们实现两个无血缘关系之间的进程间通信。代码如下:
fifo_write.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>// FIFO 写进程
int main()
{// 创建FIFO文件// int access(const char *pathname, int mode);int ret = access("./myfifo", F_OK);if(ret < 0){// int mkfifo(const char *pathname, mode_t mode);ret = mkfifo("./myfifo", 0777);if(ret < 0){perror("mkfifo error");return -1;}}// 打开FIFOint fd = open("./myfifo", O_RDWR);if(fd < 0){perror("open error");return -1;}// 传输数据char buf[1024] = "hello world";write(fd, buf, strlen(buf));sleep(1);// 关闭FIFOclose(fd);return 0;
}
fifo_read.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>// FIFO 读进程端
int main()
{// 判断创建FIFOint ret = access("./myfifo", F_OK);if(ret < 0){int ret = mkfifo("./myfifo", 0777);if(ret < 0){perror("mkfifo error");return -1;}}// 打开FIFOint fd = open("./myfifo", O_RDWR);if(fd < 0){perror("open error");return -1;}// 接收数据char buf[1024];memset(buf, 0x00, sizeof buf);read(fd, buf, sizeof buf);printf("%s\n", buf);// 关闭FIFOclose(fd);return 0;
}
在上述代码中,只要运行,不管是读进程还是写进程先运行,只要写进程没有往管道中写数据,读进程都会阻塞在read
处。直到写进程调用write
函数往管道中写数据之后读进程才会读出来管道中的数据输出。
4. 内存映射区
内存映射区是将一个磁盘文件与内存空间的一个缓冲区向映射。当我们在缓冲区中读取数据的时候,就相当于在文件中读取相应的字节。若我们向缓冲区中写数据,则会把数据写入对应的文件之中。这样就可以在不使用read
函数和write
函数啊的情况下使用指针完成IO操作。
映射这个过程可以使用mmap
函数实现。解除映射可以使用munmap
实现。函数的原型以及参数如下:
#include <sys/mman.h>// 函数作用: 建立存储映射区// 返回值: 成功:返回创建的映射区首地址// 失败: 返回MAP_FAILED宏, 实际上就是(void *)-1// 参数: addr: 指定映射的起始地址,通常设为NULL,由系统指定// length: 映射到内存的文件长度// prot: 映射区的保护方式,最常用的有// PROT_READ 读// PROT_WRITE 写// PROT_READ | PORT_WRITE 读写// flags: 映射区的特性,可以设置以下// MAP_SHARED 写入映射区的数据会写回文件,且允许// 其它映射改文件的进程共享。// MAP_PRIVATE 对映射区的写入操作会产生一个映射区的复// 制,对此区域所做的修改不会写回原文件。// MAP_ANONYMOUS 匿名映射区,需要结合MAP_SHARED使用。// fd: 代表要映射的文件,由open函数返回的文件描述符// offset: 以文件开始出的偏移量,必须是4K的整数倍,通常为0,表示// 从文件头开始映射void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);// 函数作用: 释放由mmap函数建立的存储映射区// 返回值: 成功返回0,失败返回-1并设置errno// 参数: addr: 调用mmap函数成功返回的映射区首地址// length: 映射区的大小int munmap(void *addr, size_t length);
需要注意到的是,mmap
函数不能开辟长度为0的存储映射区。在使用mmap
函数创建映射区的过程中,隐含着一次对映射文件的读操作,将文件读取到映射区。当我们将flags
设置为MAP_SHARED
的时候,要求映射区的权限应该小于或者等于文件打开的权限,这是出于对映射区的保护。而对于MAP_PRIVATE
,则是没有这个必要,因为mmap
中的权限是对内存的限制。
在映射区建立之后,映射区的释放是与文件的关闭无关的,所以在映射区建立之后就可以关闭文件。在创建映射区的时候,使用mmap
函数常常会出现总线错误,通常是因为共享文件存储空间大小引起的。所以创建映射区的时候出错的概率比较高,因此一定要检查函数的返回值,确保映射区建立成功了再进行后续的操作。使用munmap
函数的时候,传入的地址一定要是mmap
函数的返回值,一定不要改变指针的指向。其中函数的参数中offset
也是需要注意的,不能随便指定,必须要是4K的整数倍才行。
下面给出一个关于关于有血缘关系的进程间的通信示例。
//使用mmap完成有血缘关系进程间的通信
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>int main()
{//使用mmap函数建立共享映射区//void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);int fd = open("test.log", O_RDWR);if(fd < 0){perror("open error");return -1;}int len = lseek(fd, 0, SEEK_END);void *addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if(addr == MAP_FAILED){perror("mmap error");return -1;}close(fd);//创建子进程pid_t pid = fork();if(pid < 0){perror("fork error");return -1;}else if(pid == 0){//子进程sleep(2);char *p = (char *)addr;printf("[%s]\n", p);}else{//父进程memcpy(addr, "hello world", strlen("hello world"));wait(NULL);}return 0;
}
共享存储映射区也可以用于没有血缘关系的两个进程。
写进程
// 使用mmap完成没有血缘关系的进程间的通信
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>// 写进程
int main()
{// 使用mmap函数建立共享映射区int fd = open("test.log", O_RDWR);if(fd < 0){perror("open error");return -1;}int len = lseek(fd, 0, SEEK_END);// 建立共享映射区void *addr = mmap(NULL, len, PROT_WRITE, MAP_SHARED, fd, 0);if(addr == MAP_FAILED){perror("mmap error");return -1;}close(fd);memcpy(addr, "Good morning", strlen("Good morning"));return 0;
}
读进程
// 使用mmap完成没有血缘关系的两个进程间的通信
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>// 读进程端
int main()
{// 使用mmap函数建立共享映射区int fd = open("test.log", O_RDONLY);if(fd < 0){perror("open error");return -1;}int len = lseek(fd, 0, SEEK_END);// 建立共享映射区void *addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);if(addr == MAP_FAILED){perror("mmap error");return -1;}char *p = (char *)addr;printf("[%s]\n", p);return 0;
}
上面的共享存储映射区都是有名字的,我们也可以创建匿名的共享存储映射区。匿名映射区是不需要使用文件去创建的,因此没有血缘关系的两个进程不能使用匿名映射区通信。在使用匿名共享映射区的时候,文件的描述符一般传为-1
。关于匿名共享映射区的示例代码如下:
// 建立匿名共享映射区
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <fcntl.h>int main()
{void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);if(addr == MAP_FAILED){perror("mmap error");return -1;}pid_t pid = fork();if(pid < 0){perror("fork error");return -1;}else if(pid == 0){sleep(2);char *p = (char *)addr;printf("[%s]\n", p);}else{memset(addr, 0x00, 4096);memcpy(addr, "hello world", strlen("hello world"));wait(NULL);}return 0;
}
后续博客关于函数原型以及作用均以注释的形式写在代码中以便直观
相关文章:

Linux系统编程 day06 进程间通信
进程间通信 1. 进程间通信的概念2. 匿名管道pipe3. 命名管道FIFO4. 内存映射区 1. 进程间通信的概念 在Linux的环境下,进程地址空间是相互独立的,每个进程有着各自不同的用户地址空间。一个进程不能访问另一个进程中的内容,要进行数据交换必…...

血的教训--redis被入侵之漏洞利用复现--总览
血的教训–redis被入侵之漏洞利用复现–总览 相信大家对于自己的服务器被入侵,还是比较憎恨的,我的就被攻击了一次,总结经验,自己也是整理了这一个系列,从最基础到最后面的自己总结被攻破的步骤,非常清晰的…...

C语言矩阵乘积(ZZULIOJ1127:矩阵乘积)
题目描述 计算两个矩阵A和B的乘积。 输入第一行三个正整数m、p和n,0<m,n,p<10,表示矩阵A是m行p列,矩阵B是p行n列;接下来的m行是矩阵A的内容,每行p个整数,用空格隔开;最后的p行是矩阵B的内…...
用windows自带的FTP服务器实现同一局域网建立ftp服务器实现文件共享的详细步骤
原理 Windows自带的FTP服务器是Internet Information Services(IIS)组件的一部分,可以用于同一局域网建立FTP服务器以实现文件共享。下面是使用Windows自带的FTP服务器实现文件共享的详细步骤: 安装IIS组件: 打开控制…...

SpringBoot——模板引擎及原理
优质博文:IT-BLOG-CN 一、模板引擎的思想 模板是为了将显示与数据分离,模板技术多种多样,但其本质都是将模板文件和数据通过模板引擎生成最终的HTML代码。 二、SpringBoot模板引擎 SpringBoot推荐的模板引擎是Thymeleaf语法简单࿰…...
java开发中各个环境的适用场景
java开发中各个环境的适用场景 一.开发环境 在系统开发的经典模型,一般会分成 2 类 5 种环境: 【线下】本地环境(local)、开发环境(dev)、测试环境(test) 【线上】预发布环境(stage)、生产环境(prod) 每个环境、每个项目使用独立的二级域名 线下、线…...

【Java程序员面试专栏 专业技能篇】Java SE核心面试指引(二):面向对象思想
关于Java SE部分的核心知识进行一网打尽,包括四部分:基础知识考察、面向对象思想、核心机制策略、Java新特性,通过一篇文章串联面试重点,并且帮助加强日常基础知识的理解,全局思维导图如下所示 本篇Blog为第二部分:面向对象思想,子节点表示追问或同级提问 面向对象基…...
Redis 反序列化失败
文章目录 问题原序列化配置修改配置解决方法 问题 com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of org.springframework.security.core.authority.SimpleGrantedAuthority (although at least one Creator exists): cannot deser…...

uniapp 导航分类
商品分类数据,包括分类名称和对应的商品列表点击弹出 列表的内容 展示效果如下: 代码展示 ①div部分 <view class"container"><view class"menu-bar"><view class"menu"><view class"menu-sc…...

Vue + Element UI 实现复制当前行数据功能及解决复制到新增页面组件值不更新的问题
文章目录 引言第一部分:复制当前行数据功能的实现1.1 环境准备1.2 创建表格并渲染数据1.3 解决复制的数据不更新问题 第二部分:拓展知识2.1 Vue的响应性原理2.2 Element UI的更多用法 结语 Vue Element UI 实现复制当前行数据功能及解决复制到新增页面组…...

智慧化工~工厂设备检修和保全信息化智能化机制流程
化工厂每年需要现场检修很多机器,比如泵、压缩机、管道、塔等等,现场检查人员都是使用照相机,现场拍完很多机器后,回办公室整理乱糟糟的照片,但是经常照了之后无法分辨是哪台设备,而且现场经常漏拍…...
【LeetCode热题100】【哈希】字母异位词分组
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。 示例 1: 输入: strs ["eat", "tea", "tan", "ate", "nat", …...

基于C#实现Bitmap算法
在所有具有性能优化的数据结构中,我想大家使用最多的就是 hash 表,是的,在具有定位查找上具有 O(1)的常量时间,多么的简洁优美,但是在特定的场合下: ①:对 10 亿个不重复的整数进行排序。 ②&am…...

科学与工程计算基础(数值计算)知识点总结
数值计算 第1章 概论1.2 数值计算中的误差1.2.1 误差的来源和分类1.2.2 误差与有效数字1.2.3 数值运算的误差估计 1.3 误差定性分析和避免误差危害1.3.1 算法的数值稳定性1.3.3 避免误差危害 1.4 数值计算中算法设计的技术1.5 习题1.5.1 判断题1.5.2 计算题 第2章 插值法2.2 拉…...

oracle查询开始时间和结束时间之间的连续月份
SELECT TO_CHAR(ADD_MONTHS(TO_DATE(2023-01,YYYY-MM), ROWNUM - 1), YYYY-MM) AS fmonth FROM DUALCONNECT BY ROWNUM < CEIL(MONTHS_BETWEEN(TO_DATE(2023-11, YYYY-MM), TO_DATE(2023-01,YYYY-MM))1)...
通过 python 脚本迁移 Redis 数据
背景 需求:需要将的 Redis 数据迁移由云厂商 A 迁移至云厂商 B问题:云版本的 Redis 版本不支持 SYNC、MIGRATE、BGSAVE 等命令,使得许多工具用不了(如 redis-port) 思路 (1)从 Redis A 获取所…...

nodejs之express学习(1)
安装 npm i express使用 // 导入 const express require(express) // 创建应用 const app express() // 创建路由 app.get(/home,(req,res)>{res.end("hello express") }) app.listen(3000,()>{console.log("服务已启动~") })路由的介绍 什么是…...
【LeetCode】121. 买卖股票的最佳时机
121. 买卖股票的最佳时机 难度:简单 题目 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获…...

Vue3-VueRouter4路由语法解析
1.创建路由实例由createRouter实现 2.路由模式 1)history模式使用createWebHistory():地址栏不带# 2)hash模式使用createWebHashHistory():地址栏带# 3)参数是基础路径,默认/ 括号里的就是设置路径的前…...

ChromeDriver最新版本下载与安装方法
关于ChromeDriver最新下载地址:https://googlechromelabs.github.io/chrome-for-testing/ 下载与安装 setp1:查看Chrome浏览器版本 首先,需要检查Chrome浏览器的版本。请按照以下步骤进行: 打开Chrome浏览器。 点击浏览器右上角…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...

HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...

如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...
人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent
安全大模型训练计划:基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标:为安全大模型创建高质量、去偏、符合伦理的训练数据集,涵盖安全相关任务(如有害内容检测、隐私保护、道德推理等)。 1.1 数据收集 描…...

客户案例 | 短视频点播企业海外视频加速与成本优化:MediaPackage+Cloudfront 技术重构实践
01技术背景与业务挑战 某短视频点播企业深耕国内用户市场,但其后台应用系统部署于东南亚印尼 IDC 机房。 随着业务规模扩大,传统架构已较难满足当前企业发展的需求,企业面临着三重挑战: ① 业务:国内用户访问海外服…...

对象回调初步研究
_OBJECT_TYPE结构分析 在介绍什么是对象回调前,首先要熟悉下结构 以我们上篇线程回调介绍过的导出的PsProcessType 结构为例,用_OBJECT_TYPE这个结构来解析它,0x80处就是今天要介绍的回调链表,但是先不着急,先把目光…...

归并排序:分治思想的高效排序
目录 基本原理 流程图解 实现方法 递归实现 非递归实现 演示过程 时间复杂度 基本原理 归并排序(Merge Sort)是一种基于分治思想的排序算法,由约翰冯诺伊曼在1945年提出。其核心思想包括: 分割(Divide):将待排序数组递归地分成两个子…...
Java并发编程实战 Day 11:并发设计模式
【Java并发编程实战 Day 11】并发设计模式 开篇 这是"Java并发编程实战"系列的第11天,今天我们聚焦于并发设计模式。并发设计模式是解决多线程环境下常见问题的经典解决方案,它们不仅提供了优雅的设计思路,还能显著提升系统的性能…...