700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 【Linux】Linux进程间通信——共享内存/消息队列/守护进程

【Linux】Linux进程间通信——共享内存/消息队列/守护进程

时间:2019-04-19 07:19:17

相关推荐

【Linux】Linux进程间通信——共享内存/消息队列/守护进程

文章目录

进程间通信——共享内存/守护进程一, 共享内存1. 共享内存概念2. 共享内存使用1. 共享内存使用步骤2. 共享内存操作函数3. 共享内存常用操作命令4. 共享内存使用示例:5. 共享内存使用总结 二, 守护进程1. 终端2. 进程组3. 会话4. 进程组、会话等相关操作函数5. 守护进程5.1 创建守护进程5.2 创建守护进程示例 三, 消息队列1. 什么是消息队列2. 消息队列相关操作函数3. 消息队列使用示例

进程间通信——共享内存/守护进程

共享内存是效率最高的进程间通信方式。

一, 共享内存

1. 共享内存概念

共享内存允许两个或多个进程共享物理内存的同一块区域(通常被称为段)。由于一个共享内存段会成为一个进程用户空间的一部分,因此这种IPC机制无需内核介入。

我们需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其它所有共享同一个段的进程可用。

与管道等要求 发送进程将数据从用户空间的缓冲区复制进内核内存 和 接收进程将数据从内核内存复制进用户空间的缓冲区的做法相比,这种IPC技术速度更快。

但共享内存也会带来一些问题,比如,几个进程共享内存,那么可能进程A在写数据,进程B也在写数据,进程C读数据,那么就会出现冲突,进程C最终读出来的数据到底是什么呢?涉及到进程同步的问题。

共享内存存储在用户区的共享库所在空间,如下:

2. 共享内存使用

1. 共享内存使用步骤

创建shmget:调用shmget()创建一个新的共享内存段或取得一个既有共享内存段的标识符(由其它进程创建的共享内存段)。这个调用将返回后续调用中需要用到的共享内存标识符。链接shmat:使用shmat()附加共享内存段到当前的调用进程,使该段成为调用进程的虚拟内存的一部分。操作:此刻在程序中可以像对待其它可用内存那样对待这个共享内存段。为引用这块共享内存,程序需要使用由shmat()调用返回的addr值,它是一个指向进程的虚拟地址空间中该共享内存段的起点的指针。分离shmdt:调用shmdt()来分离共享内存段,在这个调用之后,进程就无法再引用这块共享内存了。这一步是可选的,并且在进程终止时会自动完成这一步。删除shmctl():调用shmctl()来删除共享内存段。只有当当前所有附件内存段的进程都与之分离之后内存段才会销毁,只有一个进程需要执行这一步。

2. 共享内存操作函数

共享内存操作函数有如下:

int shmget(key_t key, size_t size, int shmflg); // 创建或获取共享内存void *shmat(int shmid, const void *shmaddr, int shmflg); // 共享内存链接到当前调用进程int shmdt(const void *shmaddr); // 将当前进程与共享内存分离int shmctl(int shmid, int cmd, struct shmid_ds *buf); // 对共享内存操作,设置/获取/删除共享内存key_t ftok(const char *pathname, int proj_id); // 生成共享key,创建或获取共享内存时会使用该函数生成的key

shmget()函数说明:

shmat()函数说明:

shmdt()函数说明:

shmctl()函数说明:

当参数cmd设置为IPC_STAT或IPC_SET时,用到第三个参数buf,是结构体指针struct shmid_ds*,该结构体声明如下:

struct shmid_ds {struct ipc_perm shm_perm; /* Ownership and permissions */size_tshm_segsz; /* Size of segment (bytes) */time_tshm_atime; /* Last attach time */time_tshm_dtime; /* Last detach time */time_tshm_ctime; /* Last change time */pid_t shm_cpid; /* PID of creator */pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */shmatt_t shm_nattch; /* No. of current attaches */...};

ftok()函数说明:

3. 共享内存常用操作命令

ipc命令

ipcs -a:打印当前系统中所有的进程键通信方式的信息ipcs -m:打印使用共享内存进行进程间通信的信息ipc -q:打印使用消息队列进行进程间通信的信息ipc -s:打印使用信号量进行进程间通信的信息

ipcrm命令

ipcrm -M shmkey:移除用shmkey创建的共享内存段ipcrm -m shmid:移除用shmid标识的共享内存段ipcrm -Q msgkey:移除用msqkey创建的消息队列ipcrm -q msgid:移除用msqid标识的消息队列ipcrm -S semkey:移除semkey创建的信号量ipcrm -s semid:移除用semid标识的信号量

4. 共享内存使用示例:

示例1:创建两个进程,一个向共享内存中写数据write_shm.c,一个从共享内存中读数据read_shm.c。

read_shm.c

/*** @file read_shm.c* @author zoya (2314902703@)* @brief 从共享内存中读取数据* @version 0.1* @@date: -10-03** @copyright Copyright (c) **/#include <stdio.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>#include <stdlib.h>#include <unistd.h>#include <string.h>int main(){// 获取keykey_t key = ftok("/home/zoya/Linux/chap2/lesson14/key", 'a');if (key == -1){perror("ftok");exit(-1);}// 获取共享内存int shmid = shmget(key, 0, IPC_CREAT);if (shmid == -1){perror("shmget");exit(-1);}// 共享内存与当前进程关联void *ptr = shmat(shmid, NULL, SHM_RDONLY);if (ptr == (void *)-1){perror("shmat");exit(-1);}// 操作共享内存中的数据,即读取共享内存数据while (1){if(ptr == NULL)continue;printf("read : %s\n", (char *)ptr);sleep(2);}// 分离当前进程与共享内存的关联int ret = shmdt(ptr);if (ret == -1){perror("shmdt");exit(-1);}// 删除共享内存ret = shmctl(shmid, IPC_RMID, NULL);if (ret == -1){perror("shmctl");exit(-1);}return 0;}

write_shm.c

/*** @file read_shm.c* @author zoya (2314902703@)* @brief 向共享内存中写数据* @version 0.1* @@date: -10-03** @copyright Copyright (c) **/#include <stdio.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>#include <stdlib.h>#include <unistd.h>#include <string.h>int main(){// 获取keykey_t key = ftok("/home/zoya/Linux/chap2/lesson14/key", 'a');if (key == -1){perror("ftok");exit(-1);}// 获取共享内存int shmid = shmget(key, 4096, IPC_CREAT | 0664);if (shmid == -1){perror("shmget");exit(-1);}// 共享内存与当前进程关联void *ptr = shmat(shmid, NULL, 0); // 读写模式与当前进程关联if (ptr == (void *)-1){perror("shmat");exit(-1);}// 操作共享内存中的数据,即读取共享内存数据char str[1024] = {0};long int num = 0;while (1){memset(str, 0, sizeof(str));sprintf(str, "hello, %ld", num++);memcpy((char *)ptr, str, strlen(str));printf("按任意键继续\n");getchar();}// 分离当前进程与共享内存的关联int ret = shmdt(ptr);if (ret == -1){perror("shmdt");exit(-1);}// 删除共享内存ret = shmctl(shmid, IPC_RMID, NULL);if (ret == -1){perror("shmctl");exit(-1);}return 0;}

程序运行:

示例2:命令ipcs -q查看进程间使用共享内存通信的个数,发现有很多,并且键为0。要删除这些用不到的共享内存,思路:

首先获取是哪些进程用到了共享内存,根据shmid使用shmctl()函数可以获取进程pid;然后使用kill -9 pid杀死进程即可;

参考代码:

/*** @file rmshmid.c* @author zoya (2314902703@)* @brief 根据指定的shmid杀死对应的进程* @version 0.1* @@date: -10-03* * @copyright Copyright (c) * */#include <stdio.h>#include <sys/ipc.h>#include <sys/shm.h>int main(int argc, char *argv[]){// 根据shmid获取进程idint shmid = atoi(argv[1]);struct shmid_ds buf;shmctl(shmid,IPC_STAT,&buf);printf("shmid=%d 对应的 pid=%d, last oppid=%d , uid=%d\n",shmid,buf.shm_cpid,buf.shm_lpid,buf.shm_perm.cuid);return 0;}

获取到进程pid后使用kill命令杀死对应的进程即可。

5. 共享内存使用总结

1. 操作系统如何知道共享内存被多少个进程关联?

从内核来看,共享内存维护结构体struct shmid_ds,该结构体中有一个成员shm_nattach,shm_attach记录了关联的进程个数。

2. 是否可以对共享内存进行多次删除 shmctl

可以,shmctl只是标记共享内存,不是直接删除;

当和共享内存关联的进程数为0时,共享内存被真正删除;

当共享内存的key变为0时,共享内存被标记删除了;

如果一个进程和共享内存取消关联,该进程就不能继续操作共享内存,也不能进行再次关联;

3. 共享内存和内存映射的区别

共享内存可以直接创建,内存映射需要磁盘文件(匿名映射除外);共享内存效率更高;内存:

共享内存:所有进程操作的是同一块共享内存;

内存映射:每个进程在自己的虚拟地址空间有一个独立的空间;数据安全 进程突然退出:共享内存还存在;内存映射区小时运行进程的电脑宕机:数据存在共享内存中,就没有了;内存映射:由于磁盘文件中的数据还在,所以内存映射区中的数据还存在 生命周期 内存映射:进程退出,内存映射区销毁共享内存:进程退出,共享内存还在,需要手动删除(所有关联的进程数为0也可以删除)或者关机;如果进程退出,会自动和共享内存取消关联。

二, 守护进程

先了解下终端、进程组、会话的概念。

1. 终端

UNIX/Linux中,用户通过终端登录系统后得到一个shell进程,这个终端称为shell进程的控制终端。进程中,控制终端是保存在PCB中的信息,而fork()会复制PCB中的信息,因此由shell()进程启动的其它进程的控制终端也是这个终端。

默认情况下,每个进程的标准输入、标准输出和标准错误都指向控制终端,进程从标准输入读出就是读用户的键盘输入,进程标准输出或标准错误输出是输出到显示器上。

在控制终端输入一些特殊的控制键可以给前台进程发信号,例如 CTRL+C 会产生SIGINT信号,Ctrl+\会产生SIGQUIT信号。

echo $$可以查看当前终端的进程id

.\a.out &以后台进程运行

2. 进程组

进程组:很多进程的集合。

进程组和会话在进程之间形成了一种两级层次关系;进程组是一组相关进程的集合,会话是一组相关进程组的集合。

进程组和会话是为支持shell作业控制而定义的抽象概念,用户通过shell能够交互式地在前台或后台运行命令。

进程组由一个或多个共享同一进程组标识符PGID的进程组成。一个进程组拥有一个进程组首进程,该进程是创建该组的进程,其进程ID为该进程组的ID,新进程会继承其父进程所属的进程组ID。

进程组拥有一个生命周期,其开始时间为首进程创建组的时刻,结束时间为最后一个成员进程推出组的时刻。一个进程可能会因为终止而退出进程组,也可能会因为加入了另外一个进程组而退出进程组。进程组首进程无需是最后一个离开进程组的成员。

3. 会话

会话是一组进程组的集合。会话首进程是创建该新会话的进程,其进程ID称为会话ID,新进程会继承其父进程的会话ID(SID)。

一个会话中的所有进程共享单个控制终端。控制终端在会话首进程首先打开一个终端设备时被建立,一个终端最多可能会称为一个会话的控制终端。

在任一时刻,会话中的其中一个进程组会成为终端的前台进程组,其它进程组会成为后台进程组。只有前台进程组中的进程才能从控制终端中读取输入。当用户在控制终端中输入终端字符生成信号后,该信号会被发送到前台进程组中的所有成员。

当控制终端的连接建立起来之后,会话首进程会成为该终端的控制进程。

find / 2 > /dev/null | wc -l &>是重定向,&表示是后台运行

sort < longlist | uniq -c

4. 进程组、会话等相关操作函数

pid_t getpgrp(void); // 获取当前调用进程的进程组idint setpgid(pid_t pid,pid_t pgid); // 设置pid的进程组id为pgid;如果pid=0,使用当前调用进程的id为pid;如果pgid=0,则pid指定的pgid与pid一致// 建议使用上面两个函数设置/获取进程组idpid_t getpgid(pid_t pid); // 获取指定的进程组IDpid_t getsid(pid_t pid); // 获取指定pid的会话idpid_t setsid(void); // 如果当前调用进程不是进程组首进程,则创建会话;默认创建的会话没有控制终端

5. 守护进程

守护进程,也就是Daemon进程/精灵进程,是Linux中的后台服务进程,是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件,一般采用以d结尾的名字。

守护进程具备下列特征:

生命周期很长,守护进程会在系统启动的时候被创建并一直运行直至系统被关闭。它在后台运行并且不拥有控制终端。没有控制终端确保了内核永远不会为守护进程自动生成任何控制信号以及终端相关的信号。

Linux的大多数服务器就是用守护进程实现的。比如,internet服务器inetd,web服务器httpd等。

5.1 创建守护进程

创建守护进程步骤(其中后面打√表示该步骤是必须操作的):

执行一个fork(),然后父进程退出,子进程继续执行; √

目的是为了 防止父进程结束后显示~指示符 ;使用fork()可以确保子进程ID不是所在进程组的进程组ID; 子进程调用setsid()开启一个新会话; √ 避免创建有冲突的会话; 清除进程的umask以确保当守护进程创建文件和目录时拥有所需的权限;修改进程的当前工作目录;关闭守护进程从其父进程继承来的所有打开的文件描述符;在关闭了文件描述符0、1、2之后,守护进程通常会打开/dev/null,并使用dup2()使所有这些文件描述符指向这个设备;核心业务逻辑 √

5.2 创建守护进程示例

/*** @file daemon.c* @author zoya (2314902703@)* @brief 创建一个守护进程,每隔2s打印当前时间* @version 0.1* @@date: -10-04** @copyright Copyright (c) **/#define _XOPEN_SOURCE#include <stdio.h>#include <sys/stat.h>#include <sys/types.h>#include <sys/mman.h>#include <sys/wait.h>#include <sys/time.h>#include <fcntl.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <signal.h>#include <time.h>void my_alarm(int signum){// 打印时间time_t t1 = time(NULL);struct tm *t = localtime(&t1);char *str = asctime(t);int fd = open("time.txt", O_CREAT | O_RDWR | O_APPEND, 0664);write(fd, str, strlen(str));close(fd);}int main(){// 创建子进程,父进程退出,子进程继续执行pid_t pid = fork();if (pid > 0){printf("父进程,退出!\n");exit(0);}// 子进程开启一个新会话pid_t sid = setsid();if (sid == (pid_t)-1){perror("setsid");exit(-1);}// 清除进程的umaskumask(022);// 修改进程的当前工作目录chdir("/home/zoya");// 关闭守护进程从其父进程继承来的所有打开的文件描述符// 父进程没有打开文件描述符// 重定向到/dev/nullint fd = open("/dev/null", O_RDWR);dup2(fd, STDIN_FILENO);dup2(fd, STDOUT_FILENO);dup2(fd, STDERR_FILENO);// 核心业务逻辑// 捕捉定时器信号struct sigaction act;act.sa_flags = 0;act.sa_handler = my_alarm;sigemptyset(&act.sa_mask);sigaction(SIGALRM, &act, NULL);// 设置定时器,1s后每隔2s定时一次struct itimerval new;new.it_interval.tv_sec = 2;new.it_interval.tv_usec = 0;new.it_value.tv_sec = 1;new.it_value.tv_usec = 0;setitimer(ITIMER_REAL, &new, NULL);// 进程持续运行while (1){sleep(5);}return 0;}

三, 消息队列

1. 什么是消息队列

消息队列也称为报文队列,也是Linux的一种通信机制。消息队列传递的数据具有某种结构,不是简单的字节流;每个数据结构被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构Linux使用宏MSGMAX和MSGMNB限制一条消息的最大长度和一个队列的最大长度。消息队列的本质是一个内核提供的链表,内核基于这个链表,实现了一个数据结构;向消息队列中写数据,实际是向这个数据结构中插入了一个新结点;从消息队列中读数据,实际是从这个数据结构中删除一个结点;消息队列提供了从一个进程向另外一个进程发送一块数据的方法;消息队列每个数据块的最大长度是有上限的,系统上的全体队列的最大总长度也有一个上限;

进程间使用消息队列进行通信时通过key队列标识码建立连接。

无论是发送数据的进程还是接收数据的进程,都需要在进程空间中用消息缓冲区来暂存消息。该消息缓冲区的结构定义如下:

struct msgbuf{long mtype; // 消息的类型char mtext[1]; // 消息正文};

mtype用来区分数据类型,同时判断mtext是否为需要接收的数据;

mtext为存放消息正文的数组,可以根据消息的大小定义该数组的长度;

2. 消息队列相关操作函数

#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>int msgget(key_t key,int msgflg);int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg);ssize_t msgrcv(int msgid, void *msgp, size_t msgsz, long msgtyp, int msgflg);int msgctl(int msgid,int command,struct msgid_ds *buf);

msgget()函数说明

msgsnd()函数说明:

msgrcv()函数说明:

msgctl()函数说明:

struct msqid_ds {struct ipc_perm msg_perm;/* Ownership and permissions */time_tmsg_stime; /* Time of last msgsnd(2) */time_tmsg_rtime; /* Time of last msgrcv(2) */time_tmsg_ctime; /* Time of last change */unsigned long __msg_cbytes; /* Current number of bytes inqueue (nonstandard) */msgqnum_t msg_qnum;/* Current number of messagesin queue */msglen_t msg_qbytes; /* Maximum number of bytesallowed in queue */pid_t msg_lspid; /* PID of last msgsnd(2) */pid_t msg_lrpid; /* PID of last msgrcv(2) */};struct ipc_perm {key_t__key; /* Key supplied to msgget(2) */uid_tuid; /* Effective UID of owner */gid_tgid; /* Effective GID of owner */uid_tcuid; /* Effective UID of creator */gid_tcgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */};

查看消息队列:

使用命令ipcs -q查看已经创建的消息队列,包括key值信息,id信息,拥有者信息,文件权限信息,已使用的字节数和消息条数。

使用命令ipcrm -Q加消息队列的key值用来删除一个消息队列。

3. 消息队列使用示例

read_msgqueue.c

/*** @file read_msgqueue.c* @author zoya (2314902703@)* @brief 从消息队列中读取消息* @version 0.1* @@date: -10-04** @copyright Copyright (c) **/#include <stdio.h>#include <sys/types.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <sys/ipc.h>#include <sys/msg.h>#include <fcntl.h>struct MSG{long mtype;char mtext[2048];};int main(){// 获取keykey_t key = ftok("/home/zoya/Linux/chap2/lesson16/test", 'b');printf("key : 0x%x\n", key);// 创建消息队列int msgid = msgget(key, O_RDONLY);if (msgid == -1){perror("msgget");exit(-1);}// 从消息队列中读取消息struct MSG msgbuf;msgbuf.mtype = 1; // 消息类型while (1){msgrcv(msgid, &msgbuf, sizeof(msgbuf.mtext), msgbuf.mtype, 0);printf("receive msg : [%s]\n", msgbuf.mtext);printf("按enter键继续,按0退出\n");if ('0' == getchar())break;msgbuf.mtype++;msgbuf.mtype %= 4;if (msgbuf.mtype == 0)msgbuf.mtype++;}// 删除消息队列msgctl(msgid, IPC_RMID, NULL);return 0;}

write_msgqueue.c

/*** @file read_msgqueue.c* @author zoya (2314902703@)* @brief 向消息队列发送消息* @version 0.1* @@date: -10-04** @copyright Copyright (c) **/#include <stdio.h>#include <sys/types.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <sys/ipc.h>#include <sys/msg.h>#include <fcntl.h>struct MSG{long mtype;char mtext[2048];};int main(){// 获取keykey_t key = ftok("/home/zoya/Linux/chap2/lesson16/test", 'b');printf("key : 0x%x\n", key);// 创建消息队列int msgid = msgget(key, IPC_CREAT | O_WRONLY | 0777);if (msgid == -1){perror("msgget");exit(-1);}// 向消息队列发送消息struct MSG msgbuf;msgbuf.mtype = 1; // 消息类型char buf[2048];while (1){memset(buf,0,sizeof(buf));sprintf(buf, "key=0x%x, msgid=%d, type=%ld, msg : hello, i am writer!",key, msgid, msgbuf.mtype);printf("send msg: [%s]\n", buf);strcpy(msgbuf.mtext, buf);msgsnd(msgid, &msgbuf, sizeof(msgbuf.mtext), 0);printf("按enter键继续,按0退出\n");if ('0' == getchar())break;msgbuf.mtype++;msgbuf.mtype %= 4;if (msgbuf.mtype == 0)msgbuf.mtype++;}// 删除消息队列msgctl(msgid, IPC_RMID, NULL);return 0;}

程序运行:

文章参考:

/qq_42425882/article/details/105105209/study/live/504/2/28

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。