linux基础——linux进程间通信(IPC)机制总结
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.youkuaiyun.com/a987073381/article/details/52006729 </div>
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-f57960eb32.css">
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-f57960eb32.css">
<div class="htmledit_views" id="content_views">
管道的实现机制:
管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。
pipe函数原型:
-
#include <unistd.h>
-
int pipe(int file_descriptor[2]);
//建立管道,该函数在数组上填上两个新的文件描述符后返回0,失败返回-1。
-
eg.
int fd[
2]
-
int result = pipe(fd);
通过使用底层的read和write调用来访问数据。 向
file_descriptor[1]写
数据,从
file_descriptor[0]中
读数据。写入与读取的顺序原则是
先进先出。
命名管道是一种特殊类型的文件,它在系统中以文件形式存在。这样克服了管道的弊端,他可以 允许没有亲缘关系的进程间通信。
创建管道的两个系统调用原型:
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
int mkfifo(const char *filename,mode_t mode);
//建立一个名字为filename的命名管道,参数mode为该文件的权限(mode%~umask),若成功则返回0,否则返回-1,错误原因存于errno中。
-
eg.mkfifo(
"/tmp/cmd_pipe", S_IFIFO |
0666 );
具体操作方法只要创建了一个命名管道然后就可以使用open、read、write等系统调用来操作。创建可以手工创建或者程序中创建。
-
int mknod(const char *path, mode_t mode, dev_t dev);
//第一个参数表示你要创建的文件的名称,第二个参数表示文件类型,第三个参数表示该文件对应的设备文件的设备号。只有当文件类型为 S_IFCHR 或 S_IFBLK 的时候该文件才有设备号,创建普通文件时传入0即可。
-
eg.mknod(FIFO_FILE,S_IFIFO|
0666,
0);
信号机制是unix系统中最为古老的进程之间的通信机制,用于一个或几个进程之间传递异步信号。信号可以有各种异步事件产生,比如键盘中断等。shell也可以使用信号将作业控制命令传递给它的子进程。
-
#include <sys/types.h>
-
#include <signal.h>
-
void (*signal(
int sig,
void (*func)(
int)))(
int);
//用于截取系统信号,第一个参数为信号,第二个参数为对此信号挂接用户自己的处理函数指针。返回值为以前信号处理程序的指针。
-
eg.
int ret = signal(SIGSTOP, sig_handle);
由于signal不够健壮,推荐使用sigaction函数。
-
int kill(pid_t pid,int sig);
//kill函数向进程号为pid的进程发送信号,信号值为sig。当pid为0时,向当前系统的所有进程发送信号sig。
-
int raise(int sig);
//向当前进程中自举一个信号sig, 即向当前进程发送信号。
-
#include <unistd.h>
-
unsigned int alarm(unsigned int seconds);
//alarm()用来设置信号SIGALRM在经过参数seconds指定的秒数后传送给目前的进程。如果参数seconds为0,则之前设置的闹钟会被取消,并将剩下的时间返回。使用alarm函数的时候要注意alarm函数的覆盖性,即在一个进程中采用一次alarm函数则该进程之前的alarm函数将失效。
-
int pause(void);
//使调用进程(或线程)睡眠状态,直到接收到信号,要么终止,或导致它调用一个信号捕获函数。
消息队列是内核地址空间中的内部链表,通过linux内核在各个进程直接传递内容,消息顺序地发送到消息队列中,并以几种不同的方式从队列中获得,每个消息队列可以用 IPC标识符 唯一地进行识别。内核中的消息队列是通过IPC的标识符来区别,不同的消息队列直接是相互独立的。每个消息队列中的消息,又构成一个 独立的链表。
消息队列头文件:
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
#include <sys/msg.h>
-
struct msgbuf{
-
long mtype;
-
char mtext[
1];
//柔性数组
-
}
-
struct msgbuf{
-
long mtype;
-
char mtext[
1];
//柔性数组
-
}
<div style="font-family: 微软雅黑; font-size: 14px; line-height: 21px;">struct msgid_ds{</div><div style="font-family: 微软雅黑; font-size: 14px; line-height: 21px;"> struct ipc_perm msg_perm{</div><div style="font-family: 微软雅黑; font-size: 14px; line-height: 21px;"> time_t msg_stime;</div><div style="font-family: 微软雅黑; font-size: 14px; line-height: 21px;"> time_t msg_rtime;</div><div style="font-family: 微软雅黑; font-size: 14px; line-height: 21px;"> time_t msg_ctime;</div><div style="font-family: 微软雅黑; font-size: 14px; line-height: 21px;"> unsigned long _msg_cbuyes;</div><div style="font-family: 微软雅黑; font-size: 14px; line-height: 21px;"> ..........</div><div style="font-family: 微软雅黑; font-size: 14px; line-height: 21px;"> };</div>
-
struct ipc_perm{
-
key_t key;
-
uid_t uid;
-
gid_t gid;
-
.......
-
};
-
key_t ftok(
const
char * fname,
int id );
//参数一为目录名称, 参数二为id。如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。
-
eg.
key_t key = key =ftok(
".",
1);
-
int msgget(key_t key,int msgflag);
//msgget用来创建和访问一个消息队列。程序必须提供一个键值来命名特定的消息队列。
-
eg.
int msg_id = msgget(key, IPC_CREATE | IPC_EXCL |
0x0666);
//根据关键字创建一个新的队列(IPC_CREATE),如果队列存在则出错(IPC_EXCL),拥有对文件的读写执行权限(0666)。
-
int msgsnd(int msgid,const void *msgptr,size_t msg_sz,int msgflg);
//msgsnd函数允许我们把一条消息添加到消息队列中。msgptr只想准备发送消息的指针,指针结构体必须以一个长整型变量开始。
-
eg.struct msgmbuf{
-
int mtype;
-
char mtext[
10];
-
};
-
struct msgmbuf msg_mbuf;
-
msg_mbuf.mtype =
10;
//消息大小10字节
-
memcpy(msg_mbuf.mtext,
"测试消息",
sizeof(
"测试消息"));
-
int ret = msgsnd(msg_id, &msg_mbuf,
sizeof(
"测试消息"), IPC_NOWAIT);
-
int msgrcv(int msgid, void *msgptr, size_t msg_sz, long int msgtype, int msgflg);
//msgrcv可以通过msqid对指定消息队列进行接收操作。第二个参数为消息缓冲区变量地址,第三个参数为消息缓冲区结构大小,但是不包括mtype成员长度,第四个参数为mtype指定从队列中获取的消息类型。
-
eg.
int ret = msgrcv(msg_id, &msg_mbuf,
10,
10, IPC_NOWAIT | MSG_NOERROR);
-
int msgctl(int msqid,int cmd,struct msqid_ds *buf);
//msgctl函数主要是一些控制如删除消息队列等操作。 cmd值如下:
-
IPC_STAT:获取队列的msgid_ds结构,并把它存到buf指向的地址。
-
IPC_SET:将队列的msgid_ds设置为buf指向的msgid_ds。
-
IPC_RMID:内核删除消息队列,最后一项填
NULL, 执行操作后,内核会把消息队列从系统中删除。
信号量是一种计数器,用于控制对多个进程共享的资源进行的访问。它们常常被用作一个锁机制,在某个进程正在对特定的资源进行操作时,信号量可以防止另一个进程去访问它。
信号量是特殊的变量,它只取正整数值并且只允许对这个值进行两种操作:等待(wait)和信号(signal)。(P、V操作,P用于等待,V用于信号)
p(sv):如果sv的值大于0,就给它减1;如果它的值等于0,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行;如果没有其他进程因等待sv而挂起,则给它加1
简单理解就是P相当于申请资源,V相当于释放资源
信号量头文件:
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
#include <sys/sem.h>
-
struct semid_ds{
-
struct ipc_perm sem_perm;
-
unsigned
short sem_nsems;
-
time_t sem_otime;
-
time_t sem_ctime;
-
...
-
}
-
union semun{
-
int val;
-
struct semid_ds *buf;
-
unsigned
short *
array;
-
struct seminfo *__buf;
-
}
信号量操作sembuf结构:
-
struct sembuf{
-
ushort sem_num;
//信号量的编号
-
short sem_op;
//信号量的操作。如果为正,则从信号量中加上一个值,如果为负,则从信号量中减掉一个值,如果为0,则将进程设置为睡眠状态,直到信号量的值为0为止。
-
short sem_flg;
//信号的操作标志,一般为IPC_NOWAIT。
-
}
常用函数:
-
int semget(key_t key, int num_sems, int sem_flags);
//semget函数用于创建一个新的信号量集合 , 或者访问一个现有的集合(不同进程只要key值相同即可访问同一信号量集合)。第一个参数key是ftok生成的键值,第二个参数num_sems可以指定在新的集合应该创建的信号量的数目,第三个参数sem_flags是打开信号量的方式。
-
eg.
int semid = semget(key,
0, IPC_CREATE | IPC_EXCL |
0666);
//第三个参数参考消息队列int msgget(key_t key,int msgflag);第二个参数。
-
int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);
//semop函数用于改变信号量的值。第二个参数是要在信号集合上执行操作的一个数组,第三个参数是该数组操作的个数 。
-
eg.struct sembuf sops = {
0, +
1, IPC_NOWAIT};
//对索引值为0的信号量加一。
-
semop(semid, &sops,
1);
//以上功能执行的次数为一次。
-
int semctl(int sem_id, int sem_num, int command,...);
//semctl函数用于信号量集合执行控制操作,初始化信号量的值,删除一个信号量等。 类似于调用msgctl(), msgctl()是用于消息队列上的操作。第一个参数是指定的信号量集合(semget的返回值),第二个参数是要执行操作的信号量在集合中的索引值(例如集合中第一个信号量下标为0),第三个command参数代表要在集合上执行的命令。
-
IPC_STAT:获取某个集合的semid_ds结构,并把它存储到semun联合体的buf参数指向的地址。
-
IPC_SET:将某个集合的semid_ds结构的ipc_perm成员的值。该命令所取的值是从semun联合体的buf参数中取到。
-
IPC_RMID:内核删除该信号量集合。
-
GETVAL:返回集合中某个信号量的值。
-
SETVAL:把集合中单个信号量的值设置成为联合体val成员的值。
共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,由IPC为进程创建的一个特殊地址范围,它将出现在该进程的地址空间(这里的地址空间具体是哪个地方?)中。其他进程可以将 同一段共享内存连接到自己的地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是malloc分配的一样。如果一个进程向共享内存中写入了数据,所做的改动将立刻被其他进程看到。
注意:共享内存本身并没有同步机制,需要程序员自己控制。
共享内存头文件:
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
#include <sys/shm.h>
-
strcut shmid_ds{
-
struct ipc_perm shm_perm;
-
size_t shm_segsz;
-
time_t shm_atime;
-
time_t shm_dtime;
-
......
-
}
-
int shmget(key_t key,size_t size,int shmflg);
//shmget函数用来创建一个新的共享内存段, 或者访问一个现有的共享内存段(不同进程只要key值相同即可访问同一共享内存段)。第一个参数key是ftok生成的键值,第二个参数size为共享内存的大小,第三个参数sem_flags是打开共享内存的方式。
-
eg.
int shmid = shmget(key,
1024, IPC_CREATE | IPC_EXCL |
0666);
//第三个参数参考消息队列int msgget(key_t key,int msgflag);
-
void *shmat(int shm_id,const void *shm_addr,int shmflg);
//shmat函数通过shm_id将共享内存连接到进程的地址空间中。第二个参数可以由用户指定共享内存映射到进程空间的地址,shm_addr如果为0,则由内核试着查找一个未映射的区域。返回值为共享内存映射的地址。
-
eg.
char *shms = (
char *)shmat(shmid,
0,
0);
//shmid由shmget获得
-
int shmdt(const void *shm_addr);
//shmdt函数将共享内存从当前进程中分离。 参数为共享内存映射的地址。
-
eg.shmdt(shms);
-
int shmctl(int shm_id,int cmd,struct shmid_ds *buf);
//shmctl函数是控制函数,使用方法和消息队列msgctl()函数调用完全类似。参数一shm_id是共享内存的句柄,cmd是向共享内存发送的命令,最后一个参数buf是向共享内存发送命令的参数。
内存映射文件,是由一个文件到一块内存的映射。内存映射文件与 虚拟内存有些类似,通过内存映射文件可以保留一个地址的区域,
-
#include <sys.mman.h>
-
void *mmap(void*start,size_t length,int prot,int flags,int fd,off_t offset);
//mmap函数将一个文件或者其它对象映射进内存。 第一个参数为映射区的开始地址,设置为0表示由系统决定映射区的起始地址,第二个参数为映射的长度,第三个参数为期望的内存保护标志,第四个参数是指定映射对象的类型,第五个参数为文件描述符(指明要映射的文件),第六个参数是被映射对象内容的起点。成功返回被映射区的指针,失败返回MAP_FAILED[其值为(void *)-1]。
-
int munmap(void* start,size_t length);
//munmap函数用来取消参数start所指的映射内存起始地址,参数length则是欲取消的内存大小。如果解除映射成功则返回0,否则返回-1,错误原因存于errno中错误代码EINVAL。
-
int msync(void *addr,size_t len,int flags);
//msync函数实现磁盘文件内容和共享内存取内容一致,即同步。第一个参数为文件映射到进程空间的地址,第二个参数为映射空间的大小,第三个参数为刷新的参数设置。
共享内存和内存映射文件的区别:
共享内存是内存映射文件的一种特殊情况,内存映射的是一块内存,而非磁盘上的文件。共享内存的主语是进程(Process),操作系统默认会给每一个进程分配一个内存空间,每一个进程只允许访问操作系统分配给它的哪一段内存,而不能访问其他进程的。而有时候需要在不同进程之间访问同一段内存,怎么办呢?操作系统给出了 创建访问共享内存的API,需要共享内存的进程可以通过这一组定义好的API来访问多个进程之间共有的内存,各个进程访问这一段内存就像访问一个硬盘上的文件一样。
套接字机制不但可以单机的不同进程通信,而且使得跨网机器间进程可以通信。
套接字的创建和使用与管道是有区别的,套接字 明确地将客户端与服务器 区分开来,可以实现多个客户端连到同一服务器。
服务器套接字连接过程描述:
首先,服务器应用程序用socket创建一个套接字,它是系统分配服务器进程的类似文件描述符的资源。 接着,服务器调用bind给套接字命名。这个名字是一个标示符,它允许linux将进入的针对特定端口的连接转到正确的服务器进程。 然后,系统调用listen函数开始接听,等待客户端连接。listen创建一个队列并将其用于存放来自客户端的进入连接。 当客户端调用connect请求连接时,服务器调用accept接受客户端连接,accept此时会创建一个新套接字,用于与这个客户端进行通信。
客户端套接字连接过程描述:
客户端首先调用socket创建一个未命名套接字,让后将服务器的命名套接字作为地址来调用connect与服务器建立连接。
只要双方连接建立成功,我们就可以像操作底层文件一样来操作socket套接字实现通信。
几个基础函数定义:
-
#include <sys/types.h>
-
#include <sys/socket.h>
-
int socket(it domain,int type,int protocal);
-
int bind(int socket,const struct sockaddr *address,size_t address_len);
-
int listen(int socket,int backlog);
-
int accept(int socket,struct sockaddr *address,size_t *address_len);
-
int connect(int socket,const struct sockaddr *addrsss,size_t address_len);
详细请看:
http://blog.youkuaiyun.com/a987073381/article/details/51869000
511



被折叠的 条评论
为什么被折叠?



