百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

Linux基础知识(六) 细说linux基础知识

liebian365 2024-11-03 15:46 4 浏览 0 评论

本篇介绍一些Linux进程间通信IPC相关内容.

1. 进程间通信 IPC(inter-Process communication)

进程间通信就是在不同进程之间传播或交换信息,其主要包含以下目的

1)数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间。

2)共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。

3)通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

4)资源共享:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。

5)进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

2. 进程间交换数据的方法

1) 文件

父进程把运算结果写入文件中, 子进程去读取文件信息(需要加文件锁)

2) 信号

只能传递一个值 1~64 号信号中的一个

3) 管道

4) 共享内存

5) 消息队列

6) 信号量集, 与信号没有任何关系

3. 管道的使用

管道有以下特点:

1) 最古老的进程间通信方式, unix 最开始使用的进程交换数据的方式

2) 以文件作为交换数据的媒介, 管道分为匿名管道和命名管道

3) 历史上的管道指的是半双工管道

3.1 匿名管道

主要用于父子进程之间通信

int pipe ( int pipefd[2] ); 
pipe()函数会建立管道,并将文件描述词由参数pipefd数组返回。
pipefd : 用于存放文件描述符
 pipefd [0]为管道里的读取端
 pipefd [1]则为管道的写入端。
返回值 : 若成功则返回0,否则返回-1,错误原因存于errno中。

使用步骤:

int main()
{
 int _pipe[2];
 int ret = pipe(_pipe);
 if(ret == -1)
 {
 printf("creat pipe error!errno code is:%d\n",errno);
 return -1;
 }
 pid_t id = fork();
 if(id < 0)
 {
 printf("fork error!");
 return 2;
 }
 else if(id==0) /* 子进程 */
 {
 close(_pipe[0]); /* 关掉读*/
 int i = 0;
 char* buf = NULL;
 while(i<20)
 {
 if(i<10)
 { 
 buf = " i am child!";
 write(_pipe[1], buf,strlen(buf)+1); /* 写入数据*/
 }
 sleep(1);
 i++;
 }
 }
 else /* 父进程 */
 {
 char buf[100];
 int j = 0;
 while(j<3)
 {
 memset(buf,'\0',sizeof(buf));
 int ret = read(_pipe[0],buf,sizeof(buf)); /* 读出数据 */
 printf("%s:code is %d\n",buf,ret);
 j++;
 }
 close(_pipe[0]);
 }
 return 0;
}

3.2 命名管道

两个无关进程之间通信

编程模型:

int mkfifo(const char *pathname, mode_t mode);
pathname,要创建管道文件的路径和名称
mode,要创建的管道文件的操作权限
返回值 : 若成功则返回0,否则返回-1,错误原因存于errno中

当 A B 两个进程打开操作管道的过程中, 如果一段关闭管道(管道断裂), 另一端会收到 SIGPIPE , 该信号默认处理方式是结束当前进程

4. 共享内存

共享内存允许两个不相关的进程访问同一段物理内存, 由于数据不需要在不同的进程间复制,所以它是在两个正在运行的进程之间传递数据的一种非常有效的方式,一个进程向共享内存区域写入数据,共享该区域的所有进程就可以立刻看到其中的数据内容

使用步骤:

(1) 键 key的选取

如果是父子关系的进程间通信的话,这个标识符用IPC_PRIVATE来替代。
如果两个进程没有任何关系,就用ftok()函数算出来一个标识符(或者自己定义一个)使用

key_t key=12345

(2) 服务器进程使用 key 来创建(申请) 共享内存

int shmget ( key_t key, int size, int flag ); 
shmget函数 : 用于开辟或指向一块共享内存,返回获得共享内存区域的ID,如果不存在指定的共享区域就创建相应的区域
keyt key : 共享内存的标识符
int size : 以字节为单位指定需要共享的内存容量
int flag : 权限标志,它是这块内存的模式(mode)以及权限标识,模式可取如下值: 
 IPC_CREAT 新建(如果已创建则返回目前共享内存的id) 
 IPC_EXCL 与 IPC_CREAT结合使用,如果已创建则返回错误 
 将“模式” 和“权限标识”进行或运算,做为第三个参数
 如 IPC_CREAT | IPC_EXCL | 0640 其中0640为权限标识,4/2/1 分别表示读/写/执行3种权限,第一个0是UID,第一个6(4+2)表示拥有者的权限,第二个4表示同组权限,第3个0表示他人的权限
返回值 : 函数调用成功时返回共享内存的ID,失败时返回-1。

注:创建共享内存时,shmflg参数至少需要 IPC_CREAT | 权限标识,如果只有IPC_CREAT 则申请的地址都是k=0xffffffff,不能使用;

(3) 映射共享内存, 得到该物理内存的虚拟地址

void *shmat(int shmid, const void *shmaddr, int shmflg);
shmat函数 : 用来允许本进程访问一块共享内存的函数,第一次创建共享内存时,任何进程不能访问,要想启用对该共享内存的访问,必须将其连接到一个进程的地址空间中
int shmid : 共享内存的ID,即共享内存的标识
char *shmaddr : 共享内存连接到进程中的起始地址,如果shmaddr为NULL,内核会把共享内存映射到系统选定的地址空间中;如果shmaddr不为NULL,内核会把共享内存映射到shmaddr指定的位置。
int shmflag : 本进程对该内存的操作模式,可以有两个取值
 SHM_RND为读写模式,SHM_RDONLY是只读模式
返回值 : 函数调用成功时,返回共享内存的起始地址,失败时返回-1。

注:一般情况下很少需要控制共享内存连接的地址,通常都是让系统来选择一个地址,否则就会使应用程序对硬件的依赖性过高,所以一般把shmaddr设为NULL。

(4) 使用共享内存

类似于 p=malloc(1000);

(5) 解除映射

int shmdt(const void *shmaddr);
shmdt函数 : 用于函数删除本进程对这块内存的使用,shmdt()与shmat()相反,是用来禁止本进程访问一块共享内存的函数
char *shmaddr : 是那块共享内存的起始地址
返回值 : 函数调用成功时返回0,失败时返回-1

6) 释放/销毁共享内存

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmctl函数 : 控制对这块共享内存的使用
int shmid : 共享内存的ID,即共享内存标识
int cmd : 控制命令,表示要采取的动作,可取值如下:
 IPC_STAT 得到共享内存的状态:把shmid_ds结构中的数据设置为共享内存的当前关联值
 IPC_SET 改变共享内存的状态:把共享内存的当前关联值设置为shmid_ds结构中给出的值
 IPC_RMID 删除共享内存段
struct shmid_ds *buf : 结构体指针,IPC_STAT的时候,取得的状态放在这个结构体中,如果要改变共享内存的状态,用这个结构体指定,shmid_ds结构至少包含以下成员:
 struct shmid_ds {
 uid_t shm_perm.uid;
 uid_t shm_perm.gid;
 uid_t shm_perm.mode;
 }
返回值 : 函数调用成功时返回0,失败时返回-1

优点: 最快的进程间通信手段

缺点: 需要自行变成解决共享内存的同步访问,一般是通过信号量来解决的

5. 消息队列

消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法

每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型

编程步骤

(1) 键 key 的选取

如果是父子关系的进程间通信的话,这个标识符用IPC_PRIVATE来替代。
如果两个进程没有任何关系,就用ftok()函数算出来一个标识符(或者自己定义一个)使用

key_t key=12345

(2) 创建/获取消息队列

int msgget ( key_t key, int msgflg );
msgget函数 : 得到消息队列标识符或创建一个消息队列对象
key : 标识队列的唯一编号
msgflag : 创建消息队列时, flag 的取值为, IPC_CREAT|IPC_EXCL|权限
 获得消息队列时, flag 的取值为, 0
返回值 : 函数调用成功时返回0,失败时返回-1,错误原因存于error中

(3) 向消息队列这放入消息

int msgsnd (int msgid, const void *addr, size_t size, int msgflg );
msgsend函数 : 将消息写入到消息队列
msgid : 消息放入那个消息队列
addr : 消息数据地址
size : 消息的长度
msgflag : 发送消息通常情况下给 0, 阻塞执行, 参数IPC_NOWAIT为不阻塞执行
 当消息队列满了的情况下, 如果给 0, 就等到可以放进去位为止( 接受到其他 信号也可能停止, 如 ctrl +c ), IPC_NOWAIT 则会直接报错
返回值 : 函数调用成功时返回0,失败时返回-1,错误原因存于error中

从消息队列这取出消息

ssize_t msgrcv(int msgid, void *addr, size_t size, long msgtype, int msgflg);
msgrcv函数 : 从消息队列读取消息
msgid : 知道从哪个消息队列这取出消息
addr : 取出的消息存储什么位置
size : 期望取到的消息的长度
msgtype : 指定取出那个类型的消息
 =0, 接受任意类型的消息
 >0, 接受特定类型的消息
 <0, 接受消息类型小于等于 |msgtpye|的消息( msgtyoe 的绝对值), 按照消息类型从小到大返回接受的消息
msgflag : 发送消息通常情况下给 0, 阻塞执行, 参数IPC_NOWAIT为不阻塞执行
 IPC_NOERROR, 使用这个参数时如果接收的字节长度比发送的长, 则会把多余的丢弃, 如果不加这个参数则会报错返回-1
返回值 : 函数调用成功时返回0,失败时返回-1,错误原因存于error中

(4) 销毁/删除消息队列

int msgctl(int msgid, int cmd, struct msgid_ds *buf);
msgctl函数 : 获取和设置消息队列的属性, 销毁消息队列
msgid : 需要进行操作的消息队列的 id
cmd : IPC_RMID,销毁消息队列, 知道没有进程使用才真正销毁, 类似与 unlink
 IPC_STAT, 通过 buf 参数返回消息队列的属性信息
 IPC_SET,通过buf参数设置消息队列属性信息
buf : 与 IPC_STAT 一起使用的时候返回消息队列存的属性信息
返回值 : 函数调用成功时返回0,失败时返回-1,错误原因存于error中

(5) 发送消息格式

消息分为有类型消息和无类型消息

1) 无类型消息简单, 但是接受消息时无细分, 只能先进先出

2) 有类型的消息编程比较规范, 接受可以按照消息类型先进先出

格式如下所示

struct s_msg{ /*msgp定义的参照格式*/
 long type; /* 必须大于0,消息类型 */
 char mtext[256]; /*消息正文,可以是其他任何类型*/
} msgp;

6. 信号量(集)

信号量是一个计数器( 数字), 控制访问共享资源的最大并行进程总数, 使用时一般是给一个初始值( int 类型的变量)

假如该共享资源允许两个同时两个进程使用, 初始值设置为2, 如果有一个进程使用该资源则该计数-1, 如果有另一个进程放弃该资源计数+1, 如果计数为 0, 则不允许新的进程来访问共享资源, 新的进程阻塞等待, 直到计数重新大于 0 可以完成-1 操作, 解除阻塞

如果由多个共享资源需要控制访问, 继续要多个信号量, 把多个信号量存入一个, 数组中, 这个数组就叫信号量集

信号量集的编程步骤:

(1) 获得/确定 key

key_t key=nnnn; /*自己定义*/
key_t ftok = (pathname, proj_id); /*使用ftok函数生成*/
pathname : 存在的文件的名称
proj_id : 1~255 之间的任意值
返回值 : 通过 inode 值和 proj_id 编码生成的 key_t 

2) 创建/获取信号量集

int semget(key_t key, int nsems, int semflg);
semget函数 : 创建一个新的信号量或获取一个已经存在的信号量的键值
key : 创建/获取的信号量集对应的 key 键
nsems : 创建的信号量集中包含的信号量的个数
semflg : 信号量的创建方式或权限,有IPC_CREAT,IPC_EXCL
 IPC_CREAT如果信号量不存在,则创建一个信号量,否则获取
 IPC_EXCL只有信号量不存在的时候,新的信号量才建立,否则就产生错误
返回值 : 成功返回对应的信号量集的 id , 失败返回-1,错误原因存于error中

3) 设置信号量的初始值

int semctl(int semid, int semnum, int cmd, ...);
semctl函数 : 对信号量进行设置
semid : 要操作哪个信号量集
semnum : 指定要操作该集合中的哪个信号量
cmd : 对该信号量执行怎样的操作
 IPC_STAT, 从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中
 IPC_SET, 设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值
 IPC_RMID, 从内核中删除信号量集合
 SETVAL, 用联合体中val成员的值设置信号量集合中单个信号量的值
args : 第四个参数取决于cmd参数的取值
 union semun {
 short val; /*SETVAL用的值*/
 struct semid_ds* buf; /*IPC_STAT、IPC_SET用的semid_ds结构*/
 unsigned short* array; /*SETALL、GETALL用的数组值*/
 struct seminfo *buf; /*为控制IPC_INFO提供的缓存*/
 } arg;
返回值 : 成功返回大于或等于0, 失败返回-1,错误原因存于error中

4) 控制队关系资源的并发访问

在使用共享资源之前给信号量-1,-1 成功, 就操作共享资源, 使用完毕之后信号量+1,-1 不成功, 则阻塞

int semop(int semid, struct sembuf *sops, unsigned nsops);
semop函数 : 完成对信号量的操作
semid : 要操作哪个信号量集
nsops : 指明要操作的信号量的个数
sops : 对信号量集进进行哪种操作
该结构提包含的数据类型
{
 sem_num; // 要操作集合中的第几个
 sem_op; // 一般有两个值,-1 带表减 1 操作,1 代表加 1 操作
 sem_flg; // 取0,操作不成功阻塞,取IPC_NOWAIT不阻塞直接返回
}
返回值 : 成功返回0, 失败返回-1,错误原因存于error中

5) 信号量集不再使用时, 销毁信号量集

semctl函数配合IPC_RMID参数使用

总结:

(1) 共享内存是最快的 IPC 访问机制, 适用于进程间传递大批量的数据, 但自行编程控制多进程队共享内存的竞争访问, 一般使用信号量解决

(2) 如果进行间传递的数据具有很强的数据性, 可以考虑消息队列实现

(3) 进程间的同步更多的是考虑信号量机制

(4)操作共享内存、 消息队列、 信号量集的命令

ipcs -c 列出系统中共享内存、 消息队列、 信号量集

ipcrm -M key 删除指定的共享内存

ipcrm -Q key 删除指定的消息队列

相关推荐

快递查询教程,批量查询物流,一键管理快递

作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...

一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递

对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...

快递查询单号查询,怎么查物流到哪了

输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...

3分钟查询物流,教你一键批量查询全部物流信息

很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...

快递单号查询,一次性查询全部物流信息

现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...

快递查询工具,批量查询多个快递快递单号的物流状态、签收时间

最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...

快递查询软件,自动识别查询快递单号查询方法

当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...

教你怎样查询快递查询单号并保存物流信息

商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...

简单几步骤查询所有快递物流信息

在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...

物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号

最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...

连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息

快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...

快递查询教程,快递单号查询,筛选更新量为1的单号

最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...

掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析

在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...

从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息

在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...

物流单号查询,在哪里查询快递

如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...

取消回复欢迎 发表评论: