爆款云主机2核4G限时秒杀,88元/年起!
查看详情

活动

天翼云最新优惠活动,涵盖免费试用,产品折扣等,助您降本增效!
热门活动
  • 618智算钜惠季 爆款云主机2核4G限时秒杀,88元/年起!
  • 免费体验DeepSeek,上天翼云息壤 NEW 新老用户均可免费体验2500万Tokens,限时两周
  • 云上钜惠 HOT 爆款云主机全场特惠,更有万元锦鲤券等你来领!
  • 算力套餐 HOT 让算力触手可及
  • 天翼云脑AOne NEW 连接、保护、办公,All-in-One!
  • 中小企业应用上云专场 产品组合下单即享折上9折起,助力企业快速上云
  • 息壤高校钜惠活动 NEW 天翼云息壤杯高校AI大赛,数款产品享受线上订购超值特惠
  • 天翼云电脑专场 HOT 移动办公新选择,爆款4核8G畅享1年3.5折起,快来抢购!
  • 天翼云奖励推广计划 加入成为云推官,推荐新用户注册下单得现金奖励
免费活动
  • 免费试用中心 HOT 多款云产品免费试用,快来开启云上之旅
  • 天翼云用户体验官 NEW 您的洞察,重塑科技边界

智算服务

打造统一的产品能力,实现算网调度、训练推理、技术架构、资源管理一体化智算服务
智算云(DeepSeek专区)
科研助手
  • 算力商城
  • 应用商城
  • 开发机
  • 并行计算
算力互联调度平台
  • 应用市场
  • 算力市场
  • 算力调度推荐
一站式智算服务平台
  • 模型广场
  • 体验中心
  • 服务接入
智算一体机
  • 智算一体机
大模型
  • DeepSeek-R1-昇腾版(671B)
  • DeepSeek-R1-英伟达版(671B)
  • DeepSeek-V3-昇腾版(671B)
  • DeepSeek-R1-Distill-Llama-70B
  • DeepSeek-R1-Distill-Qwen-32B
  • Qwen2-72B-Instruct
  • StableDiffusion-V2.1
  • TeleChat-12B

应用商城

天翼云精选行业优秀合作伙伴及千余款商品,提供一站式云上应用服务
进入甄选商城进入云市场创新解决方案
办公协同
  • WPS云文档
  • 安全邮箱
  • EMM手机管家
  • 智能商业平台
财务管理
  • 工资条
  • 税务风控云
企业应用
  • 翼信息化运维服务
  • 翼视频云归档解决方案
工业能源
  • 智慧工厂_生产流程管理解决方案
  • 智慧工地
建站工具
  • SSL证书
  • 新域名服务
网络工具
  • 翼云加速
灾备迁移
  • 云管家2.0
  • 翼备份
资源管理
  • 全栈混合云敏捷版(软件)
  • 全栈混合云敏捷版(一体机)
行业应用
  • 翼电子教室
  • 翼智慧显示一体化解决方案

合作伙伴

天翼云携手合作伙伴,共创云上生态,合作共赢
天翼云生态合作中心
  • 天翼云生态合作中心
天翼云渠道合作伙伴
  • 天翼云代理渠道合作伙伴
天翼云服务合作伙伴
  • 天翼云集成商交付能力认证
天翼云应用合作伙伴
  • 天翼云云市场合作伙伴
  • 天翼云甄选商城合作伙伴
天翼云技术合作伙伴
  • 天翼云OpenAPI中心
  • 天翼云EasyCoding平台
天翼云培训认证
  • 天翼云学堂
  • 天翼云市场商学院
天翼云合作计划
  • 云汇计划
天翼云东升计划
  • 适配中心
  • 东升计划
  • 适配互认证

开发者

开发者相关功能入口汇聚
技术社区
  • 专栏文章
  • 互动问答
  • 技术视频
资源与工具
  • OpenAPI中心
开放能力
  • EasyCoding敏捷开发平台
培训与认证
  • 天翼云学堂
  • 天翼云认证
魔乐社区
  • 魔乐社区

支持与服务

为您提供全方位支持与服务,全流程技术保障,助您轻松上云,安全无忧
文档与工具
  • 文档中心
  • 新手上云
  • 自助服务
  • OpenAPI中心
定价
  • 价格计算器
  • 定价策略
基础服务
  • 售前咨询
  • 在线支持
  • 在线支持
  • 工单服务
  • 建议与反馈
  • 用户体验官
  • 服务保障
  • 客户公告
  • 会员中心
增值服务
  • 红心服务
  • 首保服务
  • 客户支持计划
  • 专家技术服务
  • 备案管家

了解天翼云

天翼云秉承央企使命,致力于成为数字经济主力军,投身科技强国伟大事业,为用户提供安全、普惠云服务
品牌介绍
  • 关于天翼云
  • 智算云
  • 天翼云4.0
  • 新闻资讯
  • 天翼云APP
基础设施
  • 全球基础设施
  • 信任中心
最佳实践
  • 精选案例
  • 超级探访
  • 云杂志
  • 分析师和白皮书
  • 天翼云·创新直播间
市场活动
  • 2025智能云生态大会
  • 2024智算云生态大会
  • 2023云生态大会
  • 2022云生态大会
  • 天翼云中国行
天翼云
  • 活动
  • 智算服务
  • 产品
  • 解决方案
  • 应用商城
  • 合作伙伴
  • 开发者
  • 支持与服务
  • 了解天翼云
      • 文档
      • 控制中心
      • 备案
      • 管理中心

      Linux进程间通信

      首页 知识中心 其他 文章详情页

      Linux进程间通信

      2023-05-23 09:46:32 阅读次数:512

      linux,进程,通信

       

      进程间通信介绍

      进程间通信的概念

      进程间通信简称IPC(Interprocess communication),进程间通信就是在不同进程之间传播或交换信息。

      进程间通信的目的

      • 数据传输: 一个进程需要将它的数据发送给另一个进程。
      • 资源共享: 多个进程之间共享同样的资源。
      • 通知事件: 一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件,比如进程终止时需要通知其父进程。
      • 进程控制: 有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

      进程间通信的本质

      进程间通信的本质就是,让不同的进程看到同一份资源。

      由于各个运行进程之间具有独立性,这个独立性主要体现在数据层面,而代码逻辑层面可以私有也可以公有(例如父子进程),因此各个进程之间要实现通信是非常困难的。

      各个进程之间若想实现通信,一定要借助第三方资源,这些进程就可以通过向这个第三方资源写入或是读取数据,进而实现进程之间的通信,这个第三方资源实际上就是操作系统提供的一段内存区域。
      Linux进程间通信
      因此,进程间通信的本质就是,让不同的进程看到同一份资源(内存,文件内核缓冲等)。 由于这份资源可以由操作系统中的不同模块提供,因此出现了不同的进程间通信方式。

      进程间通信的分类

      管道

      • 匿名管道
      • 命名管道

      System V IPC

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

      POSIX IPC

      • 消息队列
      • 共享内存
      • 信号量
      • 互斥量
      • 条件变量
      • 读写锁

      管道

      什么是管道

      管道是Unix中最古老的进程间通信的形式,我们把从一个进程连接到另一个进程的数据流称为一个“管道”。

      例如,统计我们当前使用云服务器上的登录用户个数。
      Linux进程间通信
      其中,who命令和wc命令都是两个程序,当它们运行起来后就变成了两个进程,who进程通过标准输出将数据打到“管道”当中,wc进程再通过标准输入从“管道”当中读取数据,至此便完成了数据的传输,进而完成数据的进一步加工处理。
      Linux进程间通信
      注明: who命令用于查看当前云服务器的登录用户(一行显示一个用户),wc -l用于统计当前的行数。

      匿名管道

      匿名管道的原理

      匿名管道用于进程间通信,且仅限于本地父子进程之间的通信。

      进程间通信的本质就是,让不同的进程看到同一份资源,使用匿名管道实现父子进程间通信的原理就是,让两个父子进程先看到同一份被打开的文件资源,然后父子进程就可以对该文件进行写入或是读取操作,进而实现父子进程间通信。
      Linux进程间通信
      注意:

      • 这里父子进程看到的同一份文件资源是由操作系统来维护的,所以当父子进程对该文件进行写入操作时,该文件缓冲区当中的数据并不会进行写时拷贝。
      • 管道虽然用的是文件的方案,但操作系统一定不会把进程进行通信的数据刷新到磁盘当中,因为这样做有IO参与会降低效率,而且也没有必要。也就是说,这种文件是一批不会把数据写到磁盘当中的文件,换句话说,磁盘文件和内存文件不一定是一一对应的,有些文件只会在内存当中存在,而不会在磁盘当中存在。

      pipe函数

      pipe函数用于创建匿名管道,pip函数的函数原型如下:

      int pipe(int pipefd[2]);
      

      pipe函数的参数是一个输出型参数,数组pipefd用于返回两个指向管道读端和写端的文件描述符:

      数组元素 含义
      pipefd[0] 管道读端的文件描述符
      pipefd[1] 管道写端的文件描述符

      pipe函数调用成功时返回0,调用失败时返回-1。

      匿名管道使用步骤

      在创建匿名管道实现父子进程间通信的过程中,需要pipe函数和fork函数搭配使用,具体步骤如下:

      1、父进程调用pipe函数创建管道。
      Linux进程间通信
      2、父进程创建子进程。
      Linux进程间通信
      3、父进程关闭写端,子进程关闭读端。
      Linux进程间通信
      注意:

      1. 管道只能够进行单向通信,因此当父进程创建完子进程后,需要确认父子进程谁读谁写,然后关闭相应的读写端。
      2. 从管道写端写入的数据会被内核缓冲,直到从管道的读端被读取。

      我们可以站在文件描述符的角度再来看看这三个步骤:

      1、父进程调用pipe函数创建管道。
      Linux进程间通信
      2、父进程创建子进程。
      Linux进程间通信
      3、父进程关闭写端,子进程关闭读端。
      Linux进程间通信
      例如,在以下代码当中,子进程向匿名管道当中写入10行数据,父进程从匿名管道当中将数据读出。

      //child->write, father->read                                                                                                                                                                                                                                                                                                                                                                                                                                                        
      #include <stdio.h>
      #include <unistd.h>
      #include <string.h>
      #include <stdlib.h>
      #include <sys/types.h>
      #include <sys/wait.h>
      int main()
      {
      	int fd[2] = { 0 };
      	if (pipe(fd) < 0){ //使用pipe创建匿名管道
      		perror("pipe");
      		return 1;
      	}
      	pid_t id = fork(); //使用fork创建子进程
      	if (id == 0){
      		//child
      		close(fd[0]); //子进程关闭读端
      		//子进程向管道写入数据
      		const char* msg = "hello father, I am child...";
      		int count = 10;
      		while (count--){
      			write(fd[1], msg, strlen(msg));
      			sleep(1);
      		}
      		close(fd[1]); //子进程写入完毕,关闭文件
      		exit(0);
      	}
      	//father
      	close(fd[1]); //父进程关闭写端
      	//父进程从管道读取数据
      	char buff[64];
      	while (1){
      		ssize_t s = read(fd[0], buff, sizeof(buff));
      		if (s > 0){
      			buff[s] = '\0';
      			printf("child send to father:%s\n", buff);
      		}
      		else if (s == 0){
      			printf("read file end\n");
      			break;
      		}
      		else{
      			printf("read error\n");
      			break;
      		}
      	}
      	close(fd[0]); //父进程读取完毕,关闭文件
      	waitpid(id, NULL, 0);
      	return 0;
      }
      

      运行结果如下:
      Linux进程间通信

      管道读写规则

      pipe2函数与pipe函数类似,也是用于创建匿名管道,其函数原型如下:

      int pipe2(int pipefd[2], int flags);
      

      pipe2函数的第二个参数用于设置选项。

      1、当没有数据可读时:

      • O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来为止。
      • O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

      2、当管道满的时候:

      • O_NONBLOCK disable:write调用阻塞,直到有进程读走数据。
      • O_NONBLOCK enable:write调用返回-1,errno值为EAGAIN。

      3、如果所有管道写端对应的文件描述符被关闭,则read返回0。
      4、如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出。
      5、当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性。
      6、当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性。

      管道的特点

      1、管道内部自带同步与互斥机制。

      我们将一次只允许一个进程使用的资源,称为临界资源。管道在同一时刻只允许一个进程对其进行写入或是读取操作,因此管道也就是一种临界资源。

      临界资源是需要被保护的,若是我们不对管道这种临界资源进行任何保护机制,那么就可能出现同一时刻有多个进程对同一管道进行操作的情况,进而导致同时读写、交叉读写以及读取到的数据不一致等问题。

      为了避免这些问题,内核会对管道操作进行同步与互斥:

      • 同步: 两个或两个以上的进程在运行过程中协同步调,按预定的先后次序运行。比如,A任务的运行依赖于B任务产生的数据。
      • 互斥: 一个公共资源同一时刻只能被一个进程使用,多个进程不能同时使用公共资源。

      实际上,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。对于管道的场景来说,互斥就是两个进程不可以同时对管道进行操作,它们会相互排斥,必须等一个进程操作完毕,另一个才能操作,而同步也是指这两个不能同时对管道进行操作,但这两个进程必须要按照某种次序来对管道进行操作。

      也就是说,互斥具有唯一性和排它性,但互斥并不限制任务的运行顺序,而同步的任务之间则有明确的顺序关系。

      2、管道的生命周期随进程。

      管道本质上是通过文件进行通信的,也就是说管道依赖于文件系统,那么当所有打开该文件的进程都退出后,该文件也就会被释放掉,所以说管道的生命周期随进程。

      3、管道提供的是流式服务。

      对于进程A写入管道当中的数据,进程B每次从管道读取的数据的多少是任意的,这种被称为流式服务,与之相对应的是数据报服务:

      • 流式服务: 数据没有明确的分割,不分一定的报文段。
      • 数据报服务: 数据有明确的分割,拿数据按报文段拿。

      4、管道是半双工通信的。

      在数据通信中,数据在线路上的传送方式可以分为以下三种:

      1. 单工通信(Simplex Communication):单工模式的数据传输是单向的。通信双方中,一方固定为发送端,另一方固定为接收端。
      2. 半双工通信(Half Duplex):半双工数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。
      3. 全双工通信(Full Duplex):全双工通信允许数据在两个方向上同时传输,它的能力相当于两个单工通信方式的结合。全双工可以同时(瞬时)进行信号的双向传输。

      管道是半双工的,数据只能向一个方向流动,需要双方通信时,需要建立起两个管道。
      Linux进程间通信

      管道的四种特殊情况

      在使用管道时,可能出现以下四种特殊情况:

      1. 写端进程不写,读端进程一直读,那么此时会因为管道里面没有数据可读,对应的读端进程会被挂起,直到管道里面有数据后,读端进程才会被唤醒。
      2. 读端进程不读,写端进程一直写,那么当管道被写满后,对应的写端进程会被挂起,直到管道当中的数据被读端进程读取后,写端进程才会被唤醒。
      3. 写端进程将数据写完后将写端关闭,那么读端进程将管道当中的数据读完后,就会继续执行该进程之后的代码逻辑,而不会被挂起。
      4. 读端进程将读端关闭,而写端进程还在一直向管道写入数据,那么操作系统会将写端进程杀掉。

      其中前面两种情况就能够很好的说明,管道是自带同步与互斥机制的,读端进程和写端进程是有一个步调协调的过程的,不会说当管道没有数据了读端还在读取,而当管道已经满了写端还在写入。读端进程读取数据的条件是管道里面有数据,写端进程写入数据的条件是管道当中还有空间,若是条件不满足,则相应的进程就会被挂起,直到条件满足后才会被再次唤醒。

      第三种情况也很好理解,读端进程已经将管道当中的所有数据都读取出来了,而且此后也不会有写端再进行写入了,那么此时读端进程也就可以执行该进程的其他逻辑了,而不会被挂起。

      第四种情况也不难理解,既然管道当中的数据已经没有进程会读取了,那么写端进程的写入将没有意义,因此操作系统直接将写端进程杀掉。而此时子进程代码都还没跑完就被终止了,属于异常退出,那么子进程必然收到了某种信号。

      我们可以通过以下代码看看情况四中,子进程退出时究竟是收到了什么信号。

      #include <stdio.h>
      #include <unistd.h>
      #include <string.h>
      #include <stdlib.h>
      #include <sys/types.h>
      #include <sys/wait.h>
      int main()
      {
      	int fd[2] = { 0 };
      	if (pipe(fd) < 0){ //使用pipe创建匿名管道
      		perror("pipe");
      		return 1;
      	}
      	pid_t id = fork(); //使用fork创建子进程
      	if (id == 0){
      		//child
      		close(fd[0]); //子进程关闭读端
      		//子进程向管道写入数据
      		const char* msg = "hello father, I am child...";
      		int count = 10;
      		while (count--){
      			write(fd[1], msg, strlen(msg));
      			sleep(1);
      		}
      		close(fd[1]); //子进程写入完毕,关闭文件
      		exit(0);
      	}
      	//father
      	close(fd[1]); //父进程关闭写端
      	close(fd[0]); //父进程直接关闭读端(导致子进程被操作系统杀掉)
      	int status = 0;
      	waitpid(id, &status, 0);
      	printf("child get signal:%d\n", status & 0x7F); //打印子进程收到的信号
      	return 0;
      }
      

      运行结果显示,子进程退出时收到的是13号信号。
      Linux进程间通信
      通过kill -l命令可以查看13对应的具体信号。

      [cl@VM-0-15-centos nonamepipe]$ kill -l
      

      Linux进程间通信
      由此可知,当发生情况四时,操作系统向子进程发送的是SIGPIPE信号将子进程终止的。

      管道的大小

      管道的容量是有限的,如果管道已满,那么写端将阻塞或失败,那么管道的最大容量是多少呢?

      方法一:使用man手册

      根据man手册,在2.6.11之前的Linux版本中,管道的最大容量与系统页面大小相同,从Linux 2.6.11往后,管道的最大容量是65536字节。
      Linux进程间通信
      然后我们可以使用uname -r命令,查看自己使用的Linux版本。
      Linux进程间通信
      根据man手册,我使用的是Linux 2.6.11之后的版本,因此管道的最大容量是65536字节。

      方法二:使用ulimit命令

      其次,我们还可以使用ulimit -a命令,查看当前资源限制的设定。
      Linux进程间通信
      根据显示,管道的最大容量是 512 × 8 = 4096 512\times8=4096 512×8=4096 字节。

      方法三:自行测试

      这里发现,根据man手册得到的管道容量与使用ulimit命令得到的管道容量不同,那么此时我们可以自行进行测试。

      前面说到,若是读端进程一直不读取管道当中的数据,写端进程一直向管道写入数据,当管道被写满后,写端进程就会被挂起。据此,我们可以写出以下代码来测试管道的最大容量。

      #include <unistd.h>
      #include <stdio.h>
      #include <stdlib.h>
      #include <sys/wait.h>
      int main()
      {
      	int fd[2] = { 0 };
      	if (pipe(fd) < 0){ //使用pipe创建匿名管道
      		perror("pipe");
      		return 1;
      	}
      	pid_t id = fork(); //使用fork创建子进程
      	if (id == 0){
      		//child 
      		close(fd[0]); //子进程关闭读端
      		char c = 'a';
      		int count = 0;
      		//子进程一直进行写入,一次写入一个字节
      		while (1){
      			write(fd[1], &c, 1);
      			count++;
      			printf("%d\n", count); //打印当前写入的字节数
      		}
      		close(fd[1]);
      		exit(0);
      	}
      	//father
      	close(fd[1]); //父进程关闭写端
      
      	//父进程不进行读取
      
      	waitpid(id, NULL, 0);
      	close(fd[0]);
      	return 0;
      }
      

      可以看到,在读端进程不进行读取的情况下,写端进程最多写65536字节的数据就被操作系统挂起了,也就是说,我当前Linux版本中管道的最大容量是65536字节。
      Linux进程间通信

      命名管道

      命名管道的原理

      匿名管道只能用于具有共同祖先的进程(具有亲缘关系的进程)之间的通信,通常,一个管道由一个进程创建,然后该进程调用fork,此后父子进程之间就可应用该管道。
      如果要实现两个毫不相关进程之间的通信,可以使用命名管道来做到。命名管道就是一种特殊类型的文件,两个进程通过命名管道的文件名打开同一个管道文件,此时这两个进程也就看到了同一份资源,进而就可以进行通信了。

      注意:

      1. 普通文件是很难做到通信的,即便做到通信也无法解决一些安全问题。
      2. 命名管道和匿名管道一样,都是内存文件,只不过命名管道在磁盘有一个简单的映像,但这个映像的大小永远为0,因为命名管道和匿名管道都不会将通信数据刷新到磁盘当中。

      使用命令创建命名管道

      我们可以使用mkfifo命令创建一个命名管道。

      [cl@VM-0-15-centos fifo]$ mkfifo fifo
      

      Linux进程间通信
      可以看到,创建出来的文件的类型是p,代表该文件是命名管道文件。
      Linux进程间通信
      使用这个命名管道文件,就能实现两个进程之间的通信了。我们在一个进程(进程A)中用shell脚本每秒向命名管道写入一个字符串,在另一个进程(进程B)当中用cat命令从命名管道当中进行读取。
      现象就是当进程A启动后,进程B会每秒从命名管道中读取一个字符串打印到显示器上。这就证明了这两个毫不相关的进程可以通过命名管道进行数据传输,即通信。
      Linux进程间通信
      之前我们说过,当管道的读端进程退出后,写端进程再向管道写入数据就没有意义了,此时写端进程会被操作系统杀掉,在这里就可以很好的得到验证:当我们终止掉读端进程后,因为写端执行的循环脚本是由命令行解释器bash执行的,所以此时bash就会被操作系统杀掉,我们的云服务器也就退出了。
      Linux进程间通信

      创建一个命名管道

      在程序中创建命名管道使用mkfifo函数,mkfifo函数的函数原型如下:

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

      mkfifo函数的第一个参数是pathname,表示要创建的命名管道文件。

      • 若pathname以路径的方式给出,则将命名管道文件创建在pathname路径下。
      • 若pathname以文件名的方式给出,则将命名管道文件默认创建在当前路径下。(注意当前路径的含义)

      mkfifo函数的第二个参数是mode,表示创建命名管道文件的默认权限。

      例如,将mode设置为0666,则命名管道文件创建出来的权限如下:
      Linux进程间通信
      但实际上创建出来文件的权限值还会受到umask(文件默认掩码)的影响,实际创建出来文件的权限为:mode&(~umask)。umask的默认值一般为0002,当我们设置mode值为0666时实际创建出来文件的权限为0664。
      Linux进程间通信
      若想创建出来命名管道文件的权限值不受umask的影响,则需要在创建文件前使用umask函数将文件默认掩码设置为0。

      umask(0); //将文件默认掩码设置为0
      

      mkfifo函数的返回值。

      • 命名管道创建成功,返回0。
      • 命名管道创建失败,返回-1。

      创建命名管道示例:

      使用以下代码即可在当前路径下,创建出一个名为myfifo的命名管道。

      #include <stdio.h>
      #include <sys/types.h>
      #include <sys/stat.h>
      
      #define FILE_NAME "myfifo"
      
      int main()
      {
      	umask(0); //将文件默认掩码设置为0
      	if (mkfifo(FILE_NAME, 0666) < 0){ //使用mkfifo创建命名管道文件
      		perror("mkfifo");
      		return 1;
      	}
      
      	//create success...
      
      	return 0;
      }
      

      运行代码后,命名管道myfifo就在当前路径下被创建了。Linux进程间通信

      命名管道的打开规则

      1、如果当前打开操作是为读而打开FIFO时。

      • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO。
      • O_NONBLOCK enable:立刻返回成功。

      2、如果当前打开操作是为写而打开FIFO时。

      • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO。
      • O_NONBLOCK enable:立刻返回失败,错误码为ENXIO。

      用命名管道实现serve&client通信

      实现服务端(server)和客户端(client)之间的通信之前,我们需要先让服务端运行起来,我们需要让服务端运行后创建一个命名管道文件,然后再以读的方式打开该命名管道文件,之后服务端就可以从该命名管道当中读取客户端发来的通信信息了。

      服务端的代码如下:

      //server.c
      #include "comm.h"
      
      int main()
      {
      	umask(0); //将文件默认掩码设置为0
      	if (mkfifo(FILE_NAME, 0666) < 0){ //使用mkfifo创建命名管道文件
      		perror("mkfifo");
      		return 1;
      	}
      	int fd = open(FILE_NAME, O_RDONLY); //以读的方式打开命名管道文件
      	if (fd < 0){
      		perror("open");
      		return 2;
      	}
      	char msg[128];
      	while (1){
      		msg[0] = '\0'; //每次读之前将msg清空
      		//从命名管道当中读取信息
      		ssize_t s = read(fd, msg, sizeof(msg)-1);
      		if (s > 0){
      			msg[s] = '\0'; //手动设置'\0',便于输出
      			printf("client# %s\n", msg); //输出客户端发来的信息
      		}
      		else if (s == 0){
      			printf("client quit!\n");
      			break;
      		}
      		else{
      			printf("read error!\n");
      			break;
      		}
      	}
      	close(fd); //通信完毕,关闭命名管道文件
      	return 0;
      }
      

      而对于客户端来说,因为服务端运行起来后命名管道文件就已经被创建了,所以客户端只需以写的方式打开该命名管道文件,之后客户端就可以将通信信息写入到命名管道文件当中,进而实现和服务端的通信。

      客户端的代码如下:

      //client.c
      #include "comm.h"
      
      int main()
      {
      	int fd = open(FILE_NAME, O_WRONLY); //以写的方式打开命名管道文件
      	if (fd < 0){
      		perror("open");
      		return 1;
      	}
      	char msg[128];
      	while (1){
      		msg[0] = '\0'; //每次读之前将msg清空
      		printf("Please Enter# "); //提示客户端输入
      		fflush(stdout);
      		//从客户端的标准输入流读取信息
      		ssize_t s = read(0, msg, sizeof(msg)-1);
      		if (s > 0){
      			msg[s - 1] = '\0';
      			//将信息写入命名管道
      			write(fd, msg, strlen(msg));
      		}
      	}
      	close(fd); //通信完毕,关闭命名管道文件
      	return 0;
      }
      

      对于如何让客户端和服务端使用同一个命名管道文件,这里我们可以让客户端和服务端包含同一个头文件,该头文件当中提供这个共用的命名管道文件的文件名,这样客户端和服务端就可以通过这个文件名,打开同一个命名管道文件,进而进行通信了。

      共用头文件的代码如下:

      //comm.h
      #pragma once
      
      #include <stdio.h>
      #include <unistd.h>
      #include <sys/types.h>
      #include <sys/stat.h>
      #include <string.h>
      #include <fcntl.h>
      
      #define FILE_NAME "myfifo" //让客户端和服务端使用同一个命名管道
      

      代码编写完毕后,先将服务端进程运行起来,之后我们就能在客户端看到这个已经被创建的命名管道文件。
      Linux进程间通信
      接着再将客户端也运行起来,此时我们从客户端写入的信息被客户端写入到命名管道当中,服务端再从命名管道当中将信息读取出来打印在服务端的显示器上,该现象说明服务端是能够通过命名管道获取到客户端发来的信息的,换句话说,此时这两个进程之间是能够通信的。
      Linux进程间通信
      当客户端和服务端运行起来时,我们还可以通过ps命令查看这两个进程的信息,可以发现这两个进程确实是两个毫不相关的进程,因为它们的PID和PPID都不相同。也就证明了,命名管道是可以实现两个毫不相关进程之间的通信的。
      Linux进程间通信

      服务端和客户端之间的退出关系

      当客户端退出后,服务端将管道当中的数据读完后就再也读不到数据了,那么此时服务端也就会去执行它的其他代码了(在当前代码中是直接退出了)。
      Linux进程间通信
      当服务端退出后,客户端写入管道的数据就不会被读取了,也就没有意义了,那么当客户端下一次再向管道写入数据时,就会收到操作系统发来的13号信号(SIGPIPE),此时客户端就被操作系统强制杀掉了。
      Linux进程间通信

      通信是在内存当中进行的

      若是我们只让客户端向管道写入数据,而服务端不从管道读取数据,那么这个管道文件的大小会不会发生变化呢?

      //server.c
      #include "comm.h"
      
      int main()
      {
      	umask(0); //将文件默认掩码设置为0
      	if (mkfifo(FILE_NAME, 0666) < 0){ //使用mkfifo创建命名管道文件
      		perror("mkfifo");
      		return 1;
      	}
      	int fd = open(FILE_NAME, O_RDONLY); //以读的方式打开命名管道文件
      	if (fd < 0){
      		perror("open");
      		return 2;
      	}
      	while (1){
      		//服务端不读取管道信息
      	}
      	close(fd); //通信完毕,关闭命名管道文件
      	return 0;
      }
      

      可以看到,尽管服务端不读取管道当中的数据,但是管道当中的数据并没有被刷新到磁盘,使用ll命令看到命名管道文件的大小依旧为0,也就说明了双方进程之间的通信依旧是在内存当中进行的,和匿名管道通信是一样的。
      Linux进程间通信

      用命名管道实现派发计算任务

      需要注意的是两个进程之间的通信,并不是简单的发送字符串而已,服务端是会对客户端发送过来的信息进行某些处理的。

      这里我们以客户端向服务端派发计算任务为例,客户端通过管道向服务端发送双操作数的计算请求,服务端接收到客户端的信息后需要计算出相应的结果。

      这里我们无需更改客户端的代码,只需改变服务端处理通信信息的逻辑即可。

      //server.c
      #include "comm.h"
      
      int main()
      {
      	umask(0); //将文件默认掩码设置为0
      	if (mkfifo(FILE_NAME, 0666) < 0){ //使用mkfifo创建命名管道文件
      		perror("mkfifo");
      		return 1;
      	}
      	int fd = open(FILE_NAME, O_RDONLY); //打开命名管道文件
      	if (fd < 0){
      		perror("open");
      		return 2;
      	}
      	char msg[128];
      	while (1){
      		msg[0] = '\0'; //每次读之前将msg清空
      		//从命名管道当中读取信息
      		ssize_t s = read(fd, msg, sizeof(msg)-1);
      		if (s > 0){
      			msg[s] = '\0'; //手动设置'\0',便于输出
      			printf("client# %s\n", msg);
      			//服务端进行计算任务
      		    char* lable = "+-*/%";
      			char* p = msg;
      			int flag = 0;
      			while (*p){
      				switch (*p){
      				case '+':
      					flag = 0;
      					break;
      				case '-':
      					flag = 1;
      					break;
      				case '*':
      					flag = 2;
      					break;
      				case '/':
      					flag = 3;
      					break;
      				case '%':
      					flag = 4;
      					break;
      				}
      				p++;
      			}
      			char* data1 = strtok(msg, "+-*/%");
      			char* data2 = strtok(NULL, "+-*/%");
      			int num1 = atoi(data1);
      			int num2 = atoi(data2);
      			int ret = 0;
      			switch (flag){
      			case 0:
      				ret = num1 + num2;
      				break;
      			case 1:
      				ret = num1 - num2;
      				break;
      			case 2:
      				ret = num1 * num2;
      				break;
      			case 3:
      				ret = num1 / num2;
      				break;
      			case 4:
      				ret = num1 % num2;
      				break;
      			}
      			printf("%d %c %d = %d\n", num1, lable[flag], num2, ret); //打印计算结果
      		}
      		else if (s == 0){
      			printf("client quit!\n");
      			break;
      		}
      		else{
      			printf("read error!\n");
      			break;
      		}
      	}
      	close(fd); //通信完毕,关闭命名管道文件
      	return 0;
      }
      

      此时服务端接收到客户端的信息后,需要进行的处理动作就不是将其打印到显示器了,而是需要将信息经过进一步的处理,从而得到相应的结果。
      Linux进程间通信

      用命名管道实现进程遥控

      比较有意思的是,我们可以通过一个进程来控制另一个进程的行为,比如我们从客户端输入命令到管道当中,再让服务端将管道当中的命令读取出来并执行。

      下面我们只实现了让服务端执行不带选项的命令,若是想让服务端执行带选项的命令,可以对管道当中获取的命令进行解析处理。这里的实现非常简单,只需让服务端从管道当中读取命令后创建子进程,然后再进行进程程序替换即可。

      这里也无需更改客户端的代码,只需改变服务端处理通信信息的逻辑即可。

      #include "comm.h"
      
      int main()
      {
      	umask(0); //将文件默认掩码设置为0
      	if (mkfifo(FILE_NAME, 0666) < 0){ //使用mkfifo创建命名管道文件
      		perror("mkfifo");
      		return 1;
      	}
      	int fd = open(FILE_NAME, O_RDONLY); //以读的方式打开命名管道文件
      	if (fd < 0){
      		perror("open");
      		return 2;
      	}
      	char msg[128];
      	while (1){
      		msg[0] = '\0'; //每次读之前将msg清空
      		//从命名管道当中读取信息
      		ssize_t s = read(fd, msg, sizeof(msg)-1);
      		if (s > 0){
      			msg[s] = '\0'; //手动设置'\0',便于输出
      			printf("client# %s\n", msg);
      			if (fork() == 0){
      				//child
      				execlp(msg, msg, NULL); //进程程序替换
      				exit(1);
      			}
      			waitpid(-1, NULL, 0); //等待子进程
      		}
      		else if (s == 0){
      			printf("client quit!\n");
      			break;
      		}
      		else{
      			printf("read error!\n");
      			break;
      		}
      	}
      	close(fd); //通信完毕,关闭命名管道文件
      	return 0;
      }
      

      此时服务端接收到客户端的信息后,便进行进程程序替换,进而执行客户端发送过来的命令。
      Linux进程间通信

      用命名管道实现文件拷贝

      这里我们再用命名管道实现一下文件的拷贝。

      需要拷贝的文件是file.txt,该文件当中的内容如下:
      Linux进程间通信
      我们要做的就是,让客户端将file.txt文件通过管道发送给服务端,在服务端创建一个file-bat.txt文件,并将从管道获取到的数据写入file-bat.txt文件当中,至此便实现了file.txt文件的拷贝。
      Linux进程间通信
      其中服务端需要做的就是,创建命名管道并以读的方式打开该命名管道,再创建一个名为file-bat.txt的文件,之后需要做的就是将从管道当中读取到的数据写入到file-bat.txt文件当中即可。

      服务端的代码如下:

      //server.c
      #include "comm.h"
      
      int main()
      {
      	umask(0); //将文件默认掩码设置为0
      	if (mkfifo(FILE_NAME, 0666) < 0){ //使用mkfifo创建命名管道文件
      		perror("mkfifo");
      		return 1;
      	}
      	int fd = open(FILE_NAME, O_RDONLY); //以读的方式打开命名管道文件
      	if (fd < 0){
      		perror("open");
      		return 2;
      	}
      	//创建文件file-bat.txt,并以写的方式打开该文件
      	int fdout = open("file-bat.txt", O_CREAT | O_WRONLY, 0666);
      	if (fdout < 0){
      		perror("open");
      		return 3;
      	}
      	char msg[128];
      	while (1){
      		msg[0] = '\0'; //每次读之前将msg清空
      		//从命名管道当中读取信息
      		ssize_t s = read(fd, msg, sizeof(msg)-1);
      		if (s > 0){
      			write(fdout, msg, s); //将读取到的信息写入到file-bat.txt文件当中
      		}
      		else if (s == 0){
      			printf("client quit!\n");
      			break;
      		}
      		else{
      			printf("read error!\n");
      			break;
      		}
      	}
      	close(fd); //通信完毕,关闭命名管道文件
      	close(fdout); //数据写入完毕,关闭file-bat.txt文件
      	return 0;
      }
      

      而客户端需要做的就是,以写的方式打开这个已经存在的命名管道文件,再以读的方式打开file.txt文件,之后需要做的就是将file.txt文件当中的数据读取出来并写入管道当中即可。

      客户端的代码如下:

      //client.c
      #include "comm.h"
      
      int main()
      {
      	int fd = open(FILE_NAME, O_WRONLY); //以写的方式打开命名管道文件
      	if (fd < 0){
      		perror("open");
      		return 1;
      	}
      	int fdin = open("file.txt", O_RDONLY); //以读的方式打开file.txt文件
      	if (fdin < 0){
      		perror("open");
      		return 2;
      	}
      	char msg[128];
      	while (1){
      		//从file.txt文件当中读取数据
      		ssize_t s = read(fdin, msg, sizeof(msg));
      		if (s > 0){
      			write(fd, msg, s); //将读取到的数据写入到命名管道当中
      		}
      		else if (s == 0){
      			printf("read end of file!\n");
      			 break;
      		}
      		else{
      			printf("read error!\n");
      			break;
      		}
      	}
      	close(fd); //通信完毕,关闭命名管道文件
      	close(fdin); //数据读取完毕,关闭file.txt文件
      	return 0;
      }
      

      共用头文件的代码和之前的一样,如下:

      //comm.h
      #pragma once
      
      #include <stdio.h>
      #include <unistd.h>
      #include <sys/types.h>
      #include <sys/stat.h>
      #include <string.h>
      #include <fcntl.h>
      
      #define FILE_NAME "myfifo" //让客户端和服务端使用同一个命名管道
      

      编写完代码后,先运行服务端,再运行客户端,一瞬间这两个进程就相继运行结束了。
      Linux进程间通信
      此时使用ll命令就可以看到,已经完成了file.txt文件的拷贝。
      Linux进程间通信
      使用cat命令打印file-bat.txt文件当中的内容,发现和file.txt文件当中的内容相同,拷贝文件成功。
      Linux进程间通信

      使用管道实现文件的拷贝有什么意义?

      因为这里是使用管道在本地进行的文件拷贝,所以看似没什么意义,但我们若是将这里的管道想象成“网络”,将客户端想象成“Windows Xshell”,再将服务端想象成“centos服务器”。那我们此时实现的就是文件上传的功能,若是将方向反过来,那么实现的就是文件下载的功能。
      Linux进程间通信

      命名管道和匿名管道的区别

      • 匿名管道由pipe函数创建并打开。
      • 命名管道由mkfifo函数创建,由open函数打开。
      • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在于它们创建与打开的方式不同,一旦这些工作完成之后,它们具有相同的语义。

      命令行当中的管道

      现有data.txt文件,文件当中的内容如下:
      Linux进程间通信
      我们可以利用管道(“|”)同时使用cat命令和grep命令,进而实现文本过滤。

      [cl@VM-0-15-centos pipe]$ cat data.txt | grep dragon
      

      Linux进程间通信

      那么在命令行当中的管道(“|”)到底是匿名管道还是命名管道呢?

      由于匿名管道只能用于有亲缘关系的进程之间的通信,而命名管道可以用于两个毫不相关的进程之间的通信,因此我们可以先看看命令行当中用管道(“|”)连接起来的各个进程之间是否具有亲缘关系。

      下面通过管道(“|”)连接了三个进程,通过ps命令查看这三个进程可以发现,这三个进程的PPID是相同的,也就是说它们是由同一个父进程创建的子进程。
      Linux进程间通信
      而它们的父进程实际上就是命令行解释器,这里为bash。
      Linux进程间通信
      也就是说,由管道(“|”)连接起来的各个进程是有亲缘关系的,它们之间互为兄弟进程。
      Linux进程间通信
      现在我们已经知道了,若是两个进程之间采用的是命名管道,那么在磁盘上必须有一个对应的命名管道文件名,而实际上我们在使用命令的时候并不存在类似的命名管道文件名,因此命令行上的管道实际上是匿名管道。

      system V进程间通信

      管道通信本质是基于文件的,也就是说操作系统并没有为此做过多的设计工作,而system V IPC是操作系统特地设计的一种通信方式。但是不管怎么样,它们的本质都是一样的,都是在想尽办法让不同的进程看到同一份由操作系统提供的资源。

      system V IPC提供的通信方式有以下三种:

      1. system V共享内存
      2. system V消息队列
      3. system V信号量

      其中,system V共享内存和system V消息队列是以传送数据为目的的,而system V信号量是为了保证进程间的同步与互斥而设计的,虽然system V信号量和通信好像没有直接关系,但属于通信范畴。

      说明一下:
      system V共享内存和system V消息队列就类似于手机,用于沟通信息;system V信号量就类似于下棋比赛时用的棋钟,用于保证两个棋手之间的同步与互斥。

      system V共享内存

      共享内存的基本原理

      共享内存让不同进程看到同一份资源的方式就是,在物理内存当中申请一块内存空间,然后将这块内存空间分别与各个进程各自的页表之间建立映射,再在虚拟地址空间当中开辟空间并将虚拟地址填充到各自页表的对应位置,使得虚拟地址和物理地址之间建立起对应关系,至此这些进程便看到了同一份物理内存,这块物理内存就叫做共享内存。
      Linux进程间通信
      注意:
      这里所说的开辟物理空间、建立映射等操作都是调用系统接口完成的,也就是说这些动作都由操作系统来完成。

      共享内存数据结构

      在系统当中可能会有大量的进程在进行通信,因此系统当中就可能存在大量的共享内存,那么操作系统必然要对其进行管理,所以共享内存除了在内存当中真正开辟空间之外,系统一定还要为共享内存维护相关的内核数据结构。

      共享内存的数据结构如下:

      struct shmid_ds {
      	struct ipc_perm     shm_perm;   /* operation perms */
      	int         shm_segsz;  /* size of segment (bytes) */
      	__kernel_time_t     shm_atime;  /* last attach time */
      	__kernel_time_t     shm_dtime;  /* last detach time */
      	__kernel_time_t     shm_ctime;  /* last change time */
      	__kernel_ipc_pid_t  shm_cpid;   /* pid of creator */
      	__kernel_ipc_pid_t  shm_lpid;   /* pid of last operator */
      	unsigned short      shm_nattch; /* no. of current attaches */
      	unsigned short      shm_unused; /* compatibility */
      	void            *shm_unused2;   /* ditto - used by DIPC */
      	void            *shm_unused3;   /* unused */
      };
      

      当我们申请了一块共享内存后,为了让要实现通信的进程能够看到同一个共享内存,因此每一个共享内存被申请时都有一个key值,这个key值用于标识系统中共享内存的唯一性。
      可以看到上面共享内存数据结构的第一个成员是shm_perm,shm_perm是一个ipc_perm类型的结构体变量,每个共享内存的key值存储在shm_perm这个结构体变量当中,其中ipc_perm结构体的定义如下:

      struct ipc_perm{
      	__kernel_key_t  key;
      	__kernel_uid_t  uid;
      	__kernel_gid_t  gid;
      	__kernel_uid_t  cuid;
      	__kernel_gid_t  cgid;
      	__kernel_mode_t mode;
      	unsigned short  seq;
      };
      

      记录一下:
      共享内存的数据结构shmid_ds和ipc_perm结构体分别在/usr/include/linux/shm.h和/usr/include/linux/ipc.h中定义。

      共享内存的建立与释放

      共享内存的建立大致包括以下两个过程:

      1. 在物理内存当中申请共享内存空间。
      2. 将申请到的共享内存挂接到地址空间,即建立映射关系。

      共享内存的释放大致包括以下两个过程:

      1. 将共享内存与地址空间去关联,即取消映射关系。
      2. 释放共享内存空间,即将物理内存归还给系统。

      共享内存的创建

      创建共享内存我们需要用shmget函数,shmget函数的函数原型如下:

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

      shmget函数的参数说明:

      • 第一个参数key,表示待创建共享内存在系统当中的唯一标识。
      • 第二个参数size,表示待创建共享内存的大小。
      • 第三个参数shmflg,表示创建共享内存的方式。

      shmget函数的返回值说明:

      • shmget调用成功,返回一个有效的共享内存标识符(用户层标识符)。
      • shmget调用失败,返回-1。

      注意: 我们把具有标定某种资源能力的东西叫做句柄,而这里shmget函数的返回值实际上就是共享内存的句柄,这个句柄可以在用户层标识共享内存,当共享内存被创建后,我们在后续使用共享内存的相关接口时,都是需要通过这个句柄对指定共享内存进行各种操作。

      传入shmget函数的第一个参数key,需要我们使用ftok函数进行获取

      ftok函数的函数原型如下:

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

      ftok函数的作用就是,将一个已存在的路径名pathname和一个整数标识符proj_id转换成一个key值,称为IPC键值,在使用shmget函数获取共享内存时,这个key值会被填充进维护共享内存的数据结构当中。需要注意的是,pathname所指定的文件必须存在且可存取。

      注意:

      1. 使用ftok函数生成key值可能会产生冲突,此时可以对传入ftok函数的参数进行修改。
      2. 需要进行通信的各个进程,在使用ftok函数获取key值时,都需要采用同样的路径名和和整数标识符,进而生成同一种key值,然后才能找到同一个共享资源。

      传入shmget函数的第三个参数shmflg,常用的组合方式有以下两种:

      组合方式 作用
      IPC_CREAT 如果内核中不存在键值与key相等的共享内存,则新建一个共享内存并返回该共享内存的句柄;如果存在这样的共享内存,则直接返回该共享内存的句柄
      IPC_CREAT | IPC_EXCL 如果内核中不存在键值与key相等的共享内存,则新建一个共享内存并返回该共享内存的句柄;如果存在这样的共享内存,则出错返回

      换句话说:

      • 使用组合IPC_CREAT,一定会获得一个共享内存的句柄,但无法确认该共享内存是否是新建的共享内存。
      • 使用组合IPC_CREAT | IPC_EXCL,只有shmget函数调用成功时才会获得共享内存的句柄,并且该共享内存一定是新建的共享内存。

      至此我们就可以使用ftok和shmget函数创建一块共享内存了,创建后我们可以将共享内存的key值和句柄进行打印,以便观察,代码如下:

      #include <stdio.h>
      #include <sys/types.h> 
      #include <sys/ipc.h> 
      #include <sys/shm.h> 
      #include <unistd.h>
      		
      #define PATHNAME "/home/cl/Linuxcode/IPC/shm/server.c" //路径名
      
      #define PROJ_ID 0x6666 //整数标识符
      #define SIZE 4096 //共享内存的大小
      
      int main()
      {
      	key_t key = ftok(PATHNAME, PROJ_ID); //获取key值
      	if (key < 0){
      		perror("ftok");
      		return 1;
      	}
      	int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL); //创建新的共享内存
      	if (shm < 0){
      		perror("shmget");
      		return 2;
      	}
      	printf("key: %x\n", key); //打印key值
      	printf("shm: %d\n", shm); //打印句柄
      	return 0;
      }
      

      该代码编写完毕运行后,我们可以看到输出的key值和句柄值:
      Linux进程间通信 在Linux当中,我们可以使用ipcs命令查看有关进程间通信设施的信息。
      Linux进程间通信
      单独使用ipcs命令时,会默认列出消息队列、共享内存以及信号量相关的信息,若只想查看它们之间某一个的相关信息,可以选择携带以下选项:

      • -q:列出消息队列相关信息。
      • -m:列出共享内存相关信息。
      • -s:列出信号量相关信息。

      例如,携带-m选项查看共享内存相关信息:
      Linux进程间通信
      此时,根据ipcs命令的查看结果和我们的输出结果可以确认,共享内存已经创建成功了。

      ipcs命令输出的每列信息的含义如下:

      标题 含义
      key 系统区别各个共享内存的唯一标识
      shmid 共享内存的用户层id(句柄)
      owner 共享内存的拥有者
      perms 共享内存的权限
      bytes 共享内存的大小
      nattch 关联共享内存的进程数
      status 共享内存的状态

      注意: key是在内核层面上保证共享内存唯一性的方式,而shmid是在用户层面上保证共享内存的唯一性,key和shmid之间的关系类似于fd和FILE*之间的的关系。

      共享内存的释放

      通过上面创建共享内存的实验可以发现,当我们的进程运行完毕后,申请的共享内存依旧存在,并没有被操作系统释放。实际上,管道是生命周期是随进程的,而共享内存的生命周期是随内核的,也就是说进程虽然已经退出,但是曾经创建的共享内存不会随着进程的退出而释放。

      这说明,如果进程不主动删除创建的共享内存,那么共享内存就会一直存在,直到关机重启(system V IPC都是如此),同时也说明了IPC资源是由内核提供并维护的。

      此时我们若是要将创建的共享内存释放,有两个方法,一就是使用命令释放共享内存,二就是在进程通信完毕后调用释放共享内存的函数进行释放。

      使用命令释放共享内存资源

      我们可以使用ipcrm -m shmid命令释放指定id的共享内存资源。

      [cl@VM-0-15-centos shm]$ ipcrm -m 8
      

      Linux进程间通信
      注意: 指定删除时使用的是共享内存的用户层id,即列表当中的shmid。

      使用程序释放共享内存资源

      控制共享内存我们需要用shmctl函数,shmctl函数的函数原型如下:

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

      shmctl函数的参数说明:

      • 第一个参数shmid,表示所控制共享内存的用户级标识符。
      • 第二个参数cmd,表示具体的控制动作。
      • 第三个参数buf,用于获取或设置所控制共享内存的数据结构。

      shmctl函数的返回值说明:

      • shmctl调用成功,返回0。
      • shmctl调用失败,返回-1。

      其中,作为shmctl函数的第二个参数传入的常用的选项有以下三个:

      选项 作用
      IPC_STAT 获取共享内存的当前关联值,此时参数buf作为输出型参数
      IPC_SET 在进程有足够权限的前提下,将共享内存的当前关联值设置为buf所指的数据结构中的值
      IPC_RMID 删除共享内存段

      例如,在以下代码当中,共享内存被创建,两秒后程序自动移除共享内存,再过两秒程序就会自动退出。

      #include <stdio.h>
      #include <sys/types.h>
      #include <sys/ipc.h>
      #include <sys/shm.h>
      #include <unistd.h>
      
      #define PATHNAME "/home/cl/Linuxcode/IPC/shm/server.c" //路径名
      
      #define PROJ_ID 0x6666 //整数标识符
      #define SIZE 4096 //共享内存的大小
      
      int main()
      {
      	key_t key = ftok(PATHNAME, PROJ_ID); //获取key值
      	if (key < 0){
      		perror("ftok");
      		return 1;
      	}
      	int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL); //创建新的共享内存
      	if (shm < 0){
      		perror("shmget");
      		return 2;
      	}
      	printf("key: %x\n", key); //打印key值
      	printf("shm: %d\n", shm); //打印句柄
      
      	sleep(2);
      	shmctl(shm, IPC_RMID, NULL); //释放共享内存
      	sleep(2);
      	return 0;
      }
      

      我们可以在程序运行时,使用以下监控脚本时刻关注共享内存的资源分配情况:

      [cl@VM-0-15-centos shm]$ while :; do ipcs -m;echo "###################################";sleep 1;done
      

      通过监控脚本可以确定共享内存确实创建并且成功释放了。
      Linux进程间通信

      共享内存的关联

      将共享内存连接到进程地址空间我们需要用shmat函数,shmat函数的函数原型如下:

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

      shmat函数的参数说明:

      • 第一个参数shmid,表示待关联共享内存的用户级标识符。
      • 第二个参数shmaddr,指定共享内存映射到进程地址空间的某一地址,通常设置为NULL,表示让内核自己决定一个合适的地址位置。
      • 第三个参数shmflg,表示关联共享内存时设置的某些属性。

      shmat函数的返回值说明:

      • shmat调用成功,返回共享内存映射到进程地址空间中的起始地址。
      • shmat调用失败,返回(void*)-1。

      其中,作为shmat函数的第三个参数传入的常用的选项有以下三个:

      选项 作用
      SHM_RDONLY 关联共享内存后只进行读取操作
      SHM_RND 若shmaddr不为NULL,则关联地址自动向下调整为SHMLBA的整数倍。公式:shmaddr-(shmaddr%SHMLBA)
      0 默认为读写权限

      这时我们可以尝试使用shmat函数对共享内存进行关联。

      #include <stdio.h>
      #include <sys/types.h>
      #include <sys/ipc.h>
      #include <sys/shm.h>
      #include <unistd.h>
      
      #define PATHNAME "/home/cl/Linuxcode/IPC/shm/server.c" //路径名
      
      #define PROJ_ID 0x6666 //整数标识符
      #define SIZE 4096 //共享内存的大小
      
      int main()
      {
      	key_t key = ftok(PATHNAME, PROJ_ID); //获取key值
      	if (key < 0){
      		perror("ftok");
      		return 1;
      	}
      	int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL); //创建新的共享内存
      	if (shm < 0){
      		perror("shmget");
      		return 2;
      	}
      	printf("key: %x\n", key); //打印key值
      	printf("shm: %d\n", shm); //打印句柄
      
      	printf("attach begin!\n");
      	sleep(2);
      	char* mem = shmat(shm, NULL, 0); //关联共享内存
      	if (mem == (void*)-1){
      		perror("shmat");
      		return 1;
      	}
      	printf("attach end!\n");
      	sleep(2);
      	
      	shmctl(shm, IPC_RMID, NULL); //释放共享内存
      	return 0;
      }
      

      代码运行后发现关联失败,主要原因是我们使用shmget函数创建共享内存时,并没有对创建的共享内存设置权限,所以创建出来的共享内存的默认权限为0,即什么权限都没有,因此server进程没有权限关联该共享内存。
      Linux进程间通信
      我们应该在使用shmget函数创建共享内存时,在其第三个参数处设置共享内存创建后的权限,权限的设置规则与设置文件权限的规则相同。

      int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666); //创建权限为0666的共享内存
      

      此时再运行程序,即可发现关联该共享内存的进程数由0变成了1,而共享内存的权限显示也不再是0,而是我们设置的666权限。
      Linux进程间通信

      共享内存的去关联

      取消共享内存与进程地址空间之间的关联我们需要用shmdt函数,shmdt函数的函数原型如下:

      int shmdt(const void *shmaddr);
      

      shmdt函数的参数说明:

      • 待去关联共享内存的起始地址,即调用shmat函数时得到的起始地址。

      shmdt函数的返回值说明:

      • shmdt调用成功,返回0。
      • shmdt调用失败,返回-1。

      现在我们就能够取消共享内存与进程之间的关联了。

      #include <stdio.h>
      #include <sys/types.h>
      #include <sys/ipc.h>
      #include <sys/shm.h>
      #include <unistd.h>
      
      #define PATHNAME "/home/cl/Linuxcode/IPC/shm/server.c" //路径名
      
      #define PROJ_ID 0x6666 //整数标识符
      #define SIZE 4096 //共享内存的大小
      
      int main()
      {
      	key_t key = ftok(PATHNAME, PROJ_ID); //获取key值
      	if (key < 0){
      		perror("ftok");
      		return 1;
      	}
      	int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666); //创建新的共享内存
      	if (shm < 0){
      		perror("shmget");
      		return 2;
      	}
      	printf("key: %x\n", key); //打印key值
      	printf("shm: %d\n", shm); //打印句柄
      
      	printf("attach begin!\n");
      	sleep(2);
      	char* mem = shmat(shm, NULL, 0); //关联共享内存
      	if (mem == (void*)-1){
      		perror("shmat");
      		return 1;
      	}
      	printf("attach end!\n");
      	sleep(2);
      	
      	printf("detach begin!\n");
      	sleep(2);
      	shmdt(mem); //共享内存去关联
      	printf("detach end!\n");
      	sleep(2);
      
      	shmctl(shm, IPC_RMID, NULL); //释放共享内存
      	return 0;
      }
      

      运行程序,通过监控即可发现该共享内存的关联数由1变为0的过程,即取消了共享内存与该进程之间的关联。
      Linux进程间通信
      注意: 将共享内存段与当前进程脱离不等于删除共享内存,只是取消了当前进程与该共享内存之间的联系。

      用共享内存实现serve&client通信

      在知道了共享内存的创建、关联、去关联以及释放后,现在可以尝试让两个进程通过共享内存进行通信了。在让两个进程进行通信之前,我们可以先测试一下这两个进程能否成功挂接到同一个共享内存上。

      服务端负责创建共享内存,创建好后将共享内存和服务端进行关联,之后进入死循环,便于观察服务端是否挂接成功。

      服务端代码如下:

      //server.c
      #include "comm.h"
      
      int main()
      {
      	key_t key = ftok(PATHNAME, PROJ_ID); //获取key值
      	if (key < 0){
      		perror("ftok");
      		return 1;
      	}
      
      	int shm = shmget(key, SIZE, IPC_CREAT | IPC_EXCL | 0666); //创建新的共享内存
      	if (shm < 0){
      		perror("shmget");
      		return 2;
      	}
      	
      	printf("key: %x\n", key); //打印key值
      	printf("shm: %d\n", shm); //打印共享内存用户层id
      
      	char* mem = shmat(shm, NULL, 0); //关联共享内存
      
      	while (1){
      		//不进行操作
      	}
      
      	shmdt(mem); //共享内存去关联
      
      	shmctl(shm, IPC_RMID, NULL); //释放共享内存
      	return 0;
      }
      

      客户端只需要直接和服务端创建的共享内存进行关联即可,之后也进入死循环,便于观察客户端是否挂接成功。

      客户端代码如下:

      //client.c
      #include "comm.h"
      
      int main()
      {
      	key_t key = ftok(PATHNAME, PROJ_ID); //获取与server进程相同的key值
      	if (key < 0){
      		perror("ftok");
      		return 1;
      	}
      	int shm = shmget(key, SIZE, IPC_CREAT); //获取server进程创建的共享内存的用户层id
      	if (shm < 0){
      		perror("shmget");
      		return 2;
      	}
      
      	printf("key: %x\n", key); //打印key值
      	printf("shm: %d\n", shm); //打印共享内存用户层id
      
      	char* mem = shmat(shm, NULL, 0); //关联共享内存
      
      	int i = 0;
      	while (1){
      		//不进行操作
      	}
      
      	shmdt(mem); //共享内存去关联
      	return 0;
      }
      

      为了让服务端和客户端在使用ftok函数获取key值时,能够得到同一种key值,那么服务端和客户端传入ftok函数的路径名和和整数标识符必须相同,这样才能生成同一种key值,进而找到同一个共享资源进行挂接。这里我们可以将这些需要共用的信息放入一个头文件当中,服务端和客户端共用这个头文件即可。

      共用头文件的代码如下:

      //comm.h
      #include <stdio.h>
      #include <stdio.h>
      #include <sys/types.h>
      #include <sys/ipc.h>
      #include <sys/shm.h>
      #include <unistd.h>
      
      #define PATHNAME "/home/cl/Linuxcode/IPC/shm/server.c" //路径名
      
      #define PROJ_ID 0x6666 //整数标识符
      #define SIZE 4096 //共享内存的大小
      

      先后运行服务端和客户端后,通过监控脚本可以看到服务端和客户端所关联的是同一个共享内存,共享内存关联的进程数也是2,表示服务端和客户端挂接共享内存成功。
      Linux进程间通信
      此时我们就可以让服务端和客户端进行通信了,这里以简单的发送字符串为例。

      客户端不断向共享内存写入数据:

      //客户端不断向共享内存写入数据
      int i = 0;
      while (1){
      	mem[i] = 'A' + i;
      	i++;
      	mem[i] = '\0';
      	sleep(1);
      }
      

      服务端不断读取共享内存当中的数据并输出:

      //服务端不断读取共享内存当中的数据并输出
      while (1){
      	printf("client# %s\n", mem);
      	sleep(1);
      }
      

      此时先运行服务端创建共享内存,当我们运行客户端时服务端就开始不断输出数据,说明服务端和客户端是能够正常通信的。
      Linux进程间通信

      共享内存与管道进行对比

      当共享内存创建好后就不再需要调用系统接口进行通信了,而管道创建好后仍需要read、write等系统接口进行通信。实际上,共享内存是所有进程间通信方式中最快的一种通信方式。

      我们先来看看管道通信:
      Linux进程间通信
      从这张图可以看出,使用管道通信的方式,将一个文件从一个进程传输到另一个进程需要进行四次拷贝操作:

      1. 服务端将信息从输入文件复制到服务端的临时缓冲区中。
      2. 将服务端临时缓冲区的信息复制到管道中。
      3. 客户端将信息从管道复制到客户端的缓冲区中。
      4. 将客户端临时缓冲区的信息复制到输出文件中。

      我们再来看看共享内存通信:
      Linux进程间通信
      从这张图可以看出,使用共享内存进行通信,将一个文件从一个进程传输到另一个进程只需要进行两次拷贝操作:

      1. 从输入文件到共享内存。
      2. 从共享内存到输出文件。

      所以共享内存是所有进程间通信方式中最快的一种通信方式,因为该通信方式需要进行的拷贝次数最少。

      但是共享内存也是有缺点的,我们知道管道是自带同步与互斥机制的,但是共享内存并没有提供任何的保护机制,包括同步与互斥。

      System V消息队列

      消息队列的基本原理

      消息队列实际上就是在系统当中创建了一个队列,队列当中的每个成员都是一个数据块,这些数据块都由类型和信息两部分构成,两个互相通信的进程通过某种方式看到同一个消息队列,这两个进程向对方发数据时,都在消息队列的队尾添加数据块,这两个进程获取数据块时,都在消息队列的队头取数据块。
      Linux进程间通信
      其中消息队列当中的某一个数据块是由谁发送给谁的,取决于数据块的类型。

      总结一下:

      1. 消息队列提供了一个从一个进程向另一个进程发送数据块的方法。
      2. 每个数据块都被认为是有一个类型的,接收者进程接收的数据块可以有不同的类型值。
      3. 和共享内存一样,消息队列的资源也必须自行删除,否则不会自动清除,因为system V IPC资源的生命周期是随内核的。

      消息队列数据结构

      当然,系统当中也可能会存在大量的消息队列,系统一定也要为消息队列维护相关的内核数据结构。

      消息队列的数据结构如下:

      struct msqid_ds {
      	struct ipc_perm msg_perm;
      	struct msg *msg_first;      /* first message on queue,unused  */
      	struct msg *msg_last;       /* last message in queue,unused */
      	__kernel_time_t msg_stime;  /* last msgsnd time */
      	__kernel_time_t msg_rtime;  /* last msgrcv time */
      	__kernel_time_t msg_ctime;  /* last change time */
      	unsigned long  msg_lcbytes; /* Reuse junk fields for 32 bit */
      	unsigned long  msg_lqbytes; /* ditto */
      	unsigned short msg_cbytes;  /* current number of bytes on queue */
      	unsigned short msg_qnum;    /* number of messages in queue */
      	unsigned short msg_qbytes;  /* max number of bytes on queue */
      	__kernel_ipc_pid_t msg_lspid;   /* pid of last msgsnd */
      	__kernel_ipc_pid_t msg_lrpid;   /* last receive pid */
      };
      

      可以看到消息队列数据结构的第一个成员是msg_perm,它和shm_perm是同一个类型的结构体变量,ipc_perm结构体的定义如下:

      struct ipc_perm{
      	__kernel_key_t  key;
      	__kernel_uid_t  uid;
      	__kernel_gid_t  gid;
      	__kernel_uid_t  cuid;
      	__kernel_gid_t  cgid;
      	__kernel_mode_t mode;
      	unsigned short  seq;
      };
      

      记录一下:
      共享内存的数据结构msqid_ds和ipc_perm结构体分别在/usr/include/linux/msg.h和/usr/include/linux/ipc.h中定义。

      消息队列的创建

      创建消息队列我们需要用msgget函数,msgget函数的函数原型如下:

      int msgget(key_t key, int msgflg);
      

      说明一下:

      1. 创建消息队列也需要使用ftok函数生成一个key值,这个key值作为msgget函数的第一个参数。
      2. msgget函数的第二个参数,与创建共享内存时使用的shmget函数的第三个参数相同。
      3. 消息队列创建成功时,msgget函数返回的一个有效的消息队列标识符(用户层标识符)。

      消息队列的释放

      释放消息队列我们需要用msgctl函数,msgctl函数的函数原型如下:

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

      说明一下:
      msgctl函数的参数与释放共享内存时使用的shmctl函数的三个参数相同,只不过msgctl函数的第三个参数传入的是消息队列的相关数据结构。

      向消息队列发送数据

      向消息队列发送数据我们需要用msgsnd函数,msgsnd函数的函数原型如下:

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

      msgsnd函数的参数说明:

      • 第一个参数msqid,表示消息队列的用户级标识符。
      • 第二个参数msgp,表示待发送的数据块。
      • 第三个参数msgsz,表示所发送数据块的大小
      • 第四个参数msgflg,表示发送数据块的方式,一般默认为0即可。

      msgsnd函数的返回值说明:

      • msgsnd调用成功,返回0。
      • msgsnd调用失败,返回-1。

      其中msgsnd函数的第二个参数必须为以下结构:

      struct msgbuf{
      	long mtype;       /* message type, must be > 0 */
      	char mtext[1];    /* message data */
      };
      

      注意: 该结构当中的第二个成员mtext即为待发送的信息,当我们定义该结构时,mtext的大小可以自己指定。

      从消息队列获取数据

      从消息队列获取数据我们需要用msgrcv函数,msgrcv函数的函数原型如下:

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

      msgrcv函数的参数说明:

      • 第一个参数msqid,表示消息队列的用户级标识符。
      • 第二个参数msgp,表示获取到的数据块,是一个输出型参数。
      • 第三个参数msgsz,表示要获取数据块的大小
      • 第四个参数msgtyp,表示要接收数据块的类型。

      msgrcv函数的返回值说明:

      • msgsnd调用成功,返回实际获取到mtext数组中的字节数。
      • msgsnd调用失败,返回-1。

      System V信号量

      信号量相关概念

      • 由于进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系叫做进程互斥。
      • 系统中某些资源一次只允许一个进程使用,称这样的资源为临界资源或互斥资源。
      • 在进程中涉及到临界资源的程序段叫临界区。
      • IPC资源必须删除,否则不会自动删除,因为system V IPC的生命周期随内核。

      信号量数据结构

      在系统当中也为信号量维护了相关的内核数据结构。

      信号量的数据结构如下:

      struct semid_ds {
      	struct ipc_perm sem_perm;       /* permissions .. see ipc.h */
      	__kernel_time_t sem_otime;      /* last semop time */
      	__kernel_time_t sem_ctime;      /* last change time */
      	struct sem  *sem_base;      /* ptr to first semaphore in array */
      	struct sem_queue *sem_pending;      /* pending operations to be processed */
      	struct sem_queue **sem_pending_last;    /* last pending operation */
      	struct sem_undo *undo;          /* undo requests on this array */
      	unsigned short  sem_nsems;      /* no. of semaphores in array */
      };
      

      信号量数据结构的第一个成员也是ipc_perm类型的结构体变量,ipc_perm结构体的定义如下:

      struct ipc_perm{
      	__kernel_key_t  key;
      	__kernel_uid_t  uid;
      	__kernel_gid_t  gid;
      	__kernel_uid_t  cuid;
      	__kernel_gid_t  cgid;
      	__kernel_mode_t mode;
      	unsigned short  seq;
      };
      

      记录一下:
      共享内存的数据结构msqid_ds和ipc_perm结构体分别在/usr/include/linux/sem.h和/usr/include/linux/ipc.h中定义。

      信号量相关函数

      信号量集的创建

      创建信号量集我们需要用semget函数,semget函数的函数原型如下:

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

      说明一下:

      1. 创建信号量集也需要使用ftok函数生成一个key值,这个key值作为semget函数的第一个参数。
      2. semget函数的第二个参数nsems,表示创建信号量的个数。
      3. semget函数的第三个参数,与创建共享内存时使用的shmget函数的第三个参数相同。
      4. 信号量集创建成功时,semget函数返回的一个有效的信号量集标识符(用户层标识符)。

      信号量集的删除

      删除信号量集我们需要用semctl函数,semctl函数的函数原型如下:

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

      信号量集的操作

      对信号量集进行操作我们需要用semop函数,semop函数的函数原型如下:

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

      进程互斥

      进程间通信通过共享资源来实现,这虽然解决了通信的问题,但是也引入了新的问题,那就是通信进程间共用的临界资源,若是不对临界资源进行保护,就可能产生各个进程从临界资源获取的数据不一致等问题。

      保护临界资源的本质是保护临界区,我们把进程代码中访问临界资源的代码称之为临界区,信号量就是用来保护临界区的,信号量分为二元信号量和多元信号量。

      比如当前有一块大小为100字节的资源,我们若是一25字节为一份,那么该资源可以被分为4份,那么此时这块资源可以由4个信号量进行标识。
      Linux进程间通信
      信号量本质是一个计数器,在二元信号量中,信号量的个数为1(相当于将临界资源看成一整块),二元信号量本质解决了临界资源的互斥问题,以下面的伪代码进行解释:
      Linux进程间通信
      根据以上代码,当进程A申请访问共享内存资源时,如果此时sem为1(sem代表当前信号量个数),则进程A申请资源成功,此时需要将sem减减,然后进程A就可以对共享内存进行一系列操作,但是在进程A在访问共享内存时,若是进程B申请访问该共享内存资源,此时sem就为0了,那么这时进程B会被挂起,直到进程A访问共享内存结束后将sem加加,此时才会将进程B唤起,然后进程B再对该共享内存进行访问操作。

      在这种情况下,无论什么时候都只会有一个进程在对同一份共享内存进行访问操作,也就解决了临界资源的互斥问题。

      实际上,代码中计数器sem减减的操作就叫做P操作,而计数器加加的操作就叫做V操作,P操作就是申请信号量,而V操作就是释放信号量。
      Linux进程间通信

      system V IPC联系

      通过对system V系列进程间通信的学习,可以发现共享内存、消息队列以及信号量,虽然它们内部的属性差别很大,但是维护它们的数据结构的第一个成员确实一样的,都是ipc_perm类型的成员变量。

      这样设计的好处就是,在操作系统内可以定义一个struct ipc_perm类型的数组,此时每当我们申请一个IPC资源,就在该数组当中开辟一个这样的结构。
      Linux进程间通信
      也就是说,在内核当中只需要将所有的IPC资源的ipc_perm成员组织成数组的样子,然后用切片的方式获取到该IPC资源的起始地址,然后就可以访问该IPC资源的每一个成员了。

      版权声明:本文内容来自第三方投稿或授权转载,原文地址:https://blog.csdn.net/chenlong_cxy/article/details/121184624,作者:2021dragon,版权归原作者所有。本网站转在其作品的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如因作品内容、版权等问题需要同本网站联系,请发邮件至ctyunbbs@chinatelecom.cn沟通。

      上一篇:Linux基础IO

      下一篇:Linux动态库和静态库

      相关文章

      2025-05-19 09:04:53

      查看RISC-V版本的gcc中默认定义的宏

      查看RISC-V版本的gcc中默认定义的宏

      2025-05-19 09:04:53
      c++ , linux
      2025-05-16 09:15:10

      Linux系统基础-进程信号超详细讲解

      Linux系统基础-进程信号超详细讲解

      2025-05-16 09:15:10
      kill , 信号 , 命令 , 进程
      2025-05-14 10:02:58

      Linux top 命令使用教程

      Linux top 是一个在Linux和其他类Unix 系统上常用的实时系统监控工具。它提供了一个动态的、交互式的实时视图,显示系统的整体性能信息以及正在运行的进程的相关信息。

      2025-05-14 10:02:58
      CPU , 信息 , 内存 , 占用 , 备注 , 进程
      2025-05-13 09:51:29

      ogg在启动应用进程时报错OGG-00412

      ogg在启动应用进程时报错OGG-00412

      2025-05-13 09:51:29
      ogg , OGG , 时报 , 进程
      2025-05-13 09:51:17

      无法启动ogg配置好的抓取以及投递进程

      无法启动ogg配置好的抓取以及投递进程

      2025-05-13 09:51:17
      ogg , 启动 , 抓取 , 进程
      2025-05-13 09:49:19

      内置变量_其他相关

      内置变量_其他相关

      2025-05-13 09:49:19
      参数 , 基础知识 , 实践 , 小结 , 脚本 , 进程
      2025-05-09 08:51:09

      【Linux 从基础到进阶】进程管理与性能调优

      在 Linux 系统中,进程是执行中的程序实例。有效的进程管理和性能调优可以提升系统的响应速度和资源利用率。本文将介绍进程管理的基本概念、常用命令,以及性能调优的最佳实践,适用于 CentOS 和 Ubuntu 系统。

      2025-05-09 08:51:09
      CPU , 性能 , 管理 , 调优 , 进程
      2025-05-07 09:08:16

      MFC如何打开和关闭进程、动态获取进程号、关闭自身进程

      进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,每一个进程都是一个实体有属于自己的地址控件,进程也是一个执行的程序

      2025-05-07 09:08:16
      函数 , 句柄 , 返回值 , 进程
      2025-04-22 09:40:08

      【Linux】Java进程CPU 使用率过高问题排查

      【Linux】Java进程CPU 使用率过高问题排查

      2025-04-22 09:40:08
      CPU , 打印 , 进程
      2025-04-22 09:27:28

      Linux进程调度与等待:背后的机制与实现

      Linux进程调度与等待:背后的机制与实现

      2025-04-22 09:27:28
      wait , 状态 , 进程
      查看更多
      推荐标签

      作者介绍

      天翼云小翼
      天翼云用户

      文章

      33561

      阅读量

      5232958

      查看更多

      最新文章

      查看RISC-V版本的gcc中默认定义的宏

      2025-05-19 09:04:53

      ogg在启动应用进程时报错OGG-00412

      2025-05-13 09:51:29

      【Linux 从基础到进阶】进程管理与性能调优

      2025-05-09 08:51:09

      MFC如何打开和关闭进程、动态获取进程号、关闭自身进程

      2025-05-07 09:08:16

      Linux进程调度与等待:背后的机制与实现

      2025-04-22 09:27:28

      Linux中ps命令使用指南

      2025-04-16 09:26:45

      查看更多

      热门文章

      Linux crontab 任务误删恢复及备份步骤

      2023-03-20 08:19:07

      Linux 趣味小知识--软硬连接以及应用

      2023-04-23 09:32:49

      Linux常用命令总结

      2023-05-12 07:20:42

      linux-压缩与解压缩

      2023-05-15 10:03:24

      linux基本命令(47)——iostat命令

      2023-05-12 07:21:43

      Linux中文本搜索命令grep用法详解

      2023-06-07 07:36:41

      查看更多

      热门标签

      linux java python javascript 数组 前端 docker Linux vue 函数 shell git 节点 容器 示例
      查看更多

      相关产品

      弹性云主机

      随时自助获取、弹性伸缩的云服务器资源

      天翼云电脑(公众版)

      便捷、安全、高效的云电脑服务

      对象存储

      高品质、低成本的云上存储服务

      云硬盘

      为云上计算资源提供持久性块存储

      查看更多

      随机文章

      Linux Shell:local关键字

      进程的状态与转换

      解决nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)以及nginx 502 Bad Gateway解决方法

      docker到底能在哪些平台安装?

      linux基本命令(24)——linux文件类型与扩展名

      linux工具-sar命令

      • 7*24小时售后
      • 无忧退款
      • 免费备案
      • 专家服务
      售前咨询热线
      400-810-9889转1
      关注天翼云
      • 旗舰店
      • 天翼云APP
      • 天翼云微信公众号
      服务与支持
      • 备案中心
      • 售前咨询
      • 智能客服
      • 自助服务
      • 工单管理
      • 客户公告
      • 涉诈举报
      账户管理
      • 管理中心
      • 订单管理
      • 余额管理
      • 发票管理
      • 充值汇款
      • 续费管理
      快速入口
      • 天翼云旗舰店
      • 文档中心
      • 最新活动
      • 免费试用
      • 信任中心
      • 天翼云学堂
      云网生态
      • 甄选商城
      • 渠道合作
      • 云市场合作
      了解天翼云
      • 关于天翼云
      • 天翼云APP
      • 服务案例
      • 新闻资讯
      • 联系我们
      热门产品
      • 云电脑
      • 弹性云主机
      • 云电脑政企版
      • 天翼云手机
      • 云数据库
      • 对象存储
      • 云硬盘
      • Web应用防火墙
      • 服务器安全卫士
      • CDN加速
      热门推荐
      • 云服务备份
      • 边缘安全加速平台
      • 全站加速
      • 安全加速
      • 云服务器
      • 云主机
      • 智能边缘云
      • 应用编排服务
      • 微服务引擎
      • 共享流量包
      更多推荐
      • web应用防火墙
      • 密钥管理
      • 等保咨询
      • 安全专区
      • 应用运维管理
      • 云日志服务
      • 文档数据库服务
      • 云搜索服务
      • 数据湖探索
      • 数据仓库服务
      友情链接
      • 中国电信集团
      • 189邮箱
      • 天翼企业云盘
      • 天翼云盘
      ©2025 天翼云科技有限公司版权所有 增值电信业务经营许可证A2.B1.B2-20090001
      公司地址:北京市东城区青龙胡同甲1号、3号2幢2层205-32室
      • 用户协议
      • 隐私政策
      • 个人信息保护
      • 法律声明
      备案 京公网安备11010802043424号 京ICP备 2021034386号