进程间通信

进程间通信

因为不同的进程用户地址空间都是相互隔离的,但是内核空间是每一个进程共享的,所以进程间的通信必然通过内核(空间)。

linux下查看IPC使用情况:

ipcs

一、管道

1)匿名管道

匿名管道存在于内存之中,不能在文件系统中找到匿名管道

相关指令 |

比如: ps -ef|grep docker

相关函数

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

创建一个匿名管道,返回两个描述符,一个用于读(fd[0]),一个用于写(fd[1])。创建的管道其实就是内核中的一段缓存。

匿名管道一般用于父子进程之间的通信,因为一个管道有读写端,所以一般在父子进程中分别关掉一端,一个管道只用来单方向通信,如果需要互相读写,则使用两个管道。

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char **argv)
{
    int pipefd[2];
    char buf;
    int pid;

    if (pipe(pipefd) == -1){
        perror("pipe");
        exit(EXIT_FAILURE);
    }
    
    pid = fork();
    if (pid == -1){
        perror("fork");
        exit(EXIT_FAILURE);
    }
    
    if (pid == 0){
        close(pipefd[1]);
        while(read(pipefd[0], &buf, 1)>0){
            write(STDOUT_FILENO, &buf, 1);
        }
        write(STDOUT_FILENO, "\n", 1);
        close(pipefd[0]);
        printf("Child exit\n");
        exit(EXIT_SUCCESS);
    }else{
        close(pipefd[0]);
        write(pipefd[1], "HAHAHAH", 7);
        close(pipefd[1]);
        wait(NULL);
        printf("Parent exit\n");
        exit(EXIT_SUCCESS);
    }

}

2)有名管道

相关指令

创建管道:

mkfifo test

写管道

echo "tests" > test

读管道

echo < test

相关函数

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

使用mkfifo创建管道文件,之后把它作为文件来进行读写就可以了。

匿名管道和有名管道的数据量是有一定限制的,linux下缓冲区大小为1页也就是4k,所以管道适合数据量很小的通信。而且当读写速度不一致时,读端会阻塞,写端当缓存满了之后也会阻塞,影响通信的效率。

二、消息队列

消息队列是保存在内核中的消息链表 ,进程间规定好消息体(自定义的结构体)。发送端发送消息之后,数据存在于消息队列链表中,读取消息队列按照发送的顺序读取,当读取完所有的消息之后,内核把这个消息体删除掉。

消息队列有大小限制,所有的消息体的总大小也有限制。所以不适用于大量数据的传递。而且消息队列存在用户态到内核态的数据拷贝开销。

相较于管道,消息队列是以消息为最小单位传输的,而管道是连续字节流数据。

相关函数

1、创建和获取一个消息队列

int msgget(key_t, key, int msgflg);

key用来标识一个消息队列

msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。

返回值返回一个消息队列标识,失败返回-1

2、发送消息到消息队列

int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);

msgid使用msgget的返回值

msg_ptr是一个指向准备发送消息的指针,消息的结构体定义要求:

struct message{
	long int message_type;
	...
}

msgflg用于控制当前消息队列满或队列消息到达系统范围的限制时的操作

3、从消息队列获取消息

int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);

4、消息队列操作

int msgctl(int msgid, int command, struct msgid_ds *buf);

command取值:

IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。
IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
IPC_RMID:删除消息队列

buf是指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构。

示例

1、发送端

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/msg.h>

struct msg_st{
    long int msg_type;
    char msg[BUFSIZ];
};

int main()
{
    int msgid;
    struct msg_st snd_data;
    

    msgid = msgget((key_t)123, 0666|IPC_CREAT);
    if (msgid==-1){
        perror("msgget");
        exit(EXIT_FAILURE);
    }
    
    snd_data.msg_type = 11;
    strcpy(snd_data.msg, "testdata");
    
    if (msgsnd(msgid, (void*)&snd_data, BUFSIZ,0) == -1){
        perror("msgsnd");
        exit(EXIT_FAILURE);
    }
    
    exit(EXIT_SUCCESS);

}

2、接受端

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/msg.h>

struct msg_st{
    long int msg_type;
    char msg[BUFSIZ];
};

int main(){
    struct msg_st rcv_data;
    int msgid;
    int msg_type=0;

    msgid = msgget((key_t)123, 0666|IPC_CREAT);
    if (msgid==-1){
        perror("msgget");
        exit(EXIT_FAILURE);
    }
    
    if (msgrcv(msgid, (void*)&rcv_data, BUFSIZ,msg_type, 0) == -1){
        perror("msgrcv");
        exit(EXIT_FAILURE);
    }else{
        printf("%s\n",rcv_data.msg);
    }
    
    if (msgctl(msgid, IPC_RMID, 0) == -1){
        perror("remove msg");
        exit(EXIT_FAILURE);
    }
    
    exit(EXIT_SUCCESS);

}

三、共享内存

共享内存就是把两个进程的物理地址映射到同一块物理地址上,这样两个进程在同一块物理内存上操作,避免了用户态到内核态的拷贝开销。

但是共享内存也有一个问题就是,进程之间的读取需要手动完成同步的操作,一般使用信号量来进行同步访问控制。

相关函数

1、分配共享内存

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

创建或打开一块共享内存区

key:进程间通信键值,ftok() 的返回值。

size:该共享存储段的长度(字节)。

shmflg:标识函数的行为及共享内存的权限,其取值如下:

IPC_CREAT:如果不存在就创建

IPC_EXCL: 如果已经存在则返回失败

位或权限位:共享内存位或权限位后可以设置共享内存的访问权限,格式和 open() 函数的 mode_t 一样(open() 的使用请点此链接),但可执行权限未使用。

返回值

成功:共享内存标识符。

失败:-1。

2、映射共享内存

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

把一段已经分配好的共享内存映射到进程的数据段中。

shmid:共享内存标识符,shmget() 的返回值。

shmaddr:共享内存映射地址(若为 NULL 则由系统自动指定),推荐使用 NULL。

shmflg:共享内存段的访问权限和映射条件( 通常为 0 ),具体取值如下:

0:共享内存具有可读可写权限。

SHM_RDONLY:只读。

SHM_RND:(shmaddr 非空时才有效)

返回值

成功:共享内存段映射地址( 相当于这个指针就指向此共享内存 )

失败:-1

3、解除共享内存的映射

int shmdt(const void *shmaddr);

把映射到共享内存的地址解除映射。

shmaddr:共享内存映射地址。

返回值

成功:0

失败:-1

4、控制共享内存

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

shmid:共享内存标识符。

cmd:函数功能的控制,其取值如下:

IPC_RMID:删除。(常用 )

IPC_SET:设置 shmid_ds 参数,相当于把共享内存原来的属性值替换为 buf 里的属性值。

IPC_STAT:保存 shmid_ds 参数,把共享内存原来的属性值备份到 buf 里。

SHM_LOCK:锁定共享内存段( 超级用户 )。

SHM_UNLOCK:解锁共享内存段。

buf:shmid_ds 数据类型的地址(具体类型请点此链接 ),用来存放或修改共享内存的属性。

返回值

成功:0

失败:-1

示例

1、写内存端

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#include <sys/types.h>

struct shared_st
{
    int writeable;
    char data[BUFSIZ];
};

int main(){
    int shmid;
    void *shm=NULL;
    struct shared_st *data=NULL;

    shmid = shmget((key_t)123, sizeof(struct shared_st), 0666|IPC_CREAT);
    if (shmid == -1){
        perror("shmget");
        exit(EXIT_FAILURE);
    }
    shm=shmat(shmid, 0,0);
    if (shm == (void*)-1){
        perror("shmat");
        exit(EXIT_FAILURE);
    }
    
    printf("shared address %X\n", (int)shm);
    
    data = (struct shared_st*)shm;
    
    while(data->writeable!=1){
        sleep(1);
    }
    
    strcpy(data->data, "A message from writer\n");
    data->writeable = 0;
    
    if(shmdt(shm) == -1)   
    {      
        perror("shmdt");  
        exit(EXIT_FAILURE);
    }
    
    exit(EXIT_SUCCESS); 

}

2、读内存端

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#include <sys/types.h>

struct shared_st
{
    int writeable;
    char data[BUFSIZ];
};

int main(){
    int shmid;
    void *shm=NULL;
    struct shared_st *data=NULL;

    shmid = shmget((key_t)123, sizeof(struct shared_st), 0666|IPC_CREAT);
    if (shmid == -1){
        perror("shmget");
        exit(EXIT_FAILURE);
    }
    
    shm=shmat(shmid, 0,0);
    if (shm == (void*)-1){
        perror("shmat");
        exit(EXIT_FAILURE);
    }
    
    printf("shared address %X\n", (int)shm);
    
    data = (struct shared_st*)shm;
    data->writeable = 1;
    
    while(data->writeable!=0){
        sleep(1);
    }
    
    if (strlen(data->data)>0){
        printf("read data %s\n",data->data);
    }
    
    if (shmdt(shm)==-1){
        perror("shmdt");
        exit(EXIT_FAILURE);
    }
    
    if (shmctl(shmid, IPC_RMID, 0)==-1){
        perror("remove shm");
        exit(EXIT_FAILURE);
    }
    
    exit(EXIT_SUCCESS);

}   

四、信号量

在使用共享内存的时候遇到数据同步的问题,可以使用信号量来解决。信号量主要用于进程间的互斥和同步,而不能用来传输数据。

信号量的两种操作

P:如果sv的值大于0则减一,如果sv值为0,则进程挂起

V:如果有其他的进程因为等待sv而挂起,则使它恢复运行,如果没有,则sv值加一

sv初始值为1

相关函数(System V)

(还有sem_init的一套函数(POSIX信号量),一般用于线程同步)

1、创建或者获取一个信号量

int semget(key_t key, int num_sems, int sem_flags);

key:信号量键值,可以理解为信号量的唯一性标记,可通过ftok获取。
num_sems:信号量的数目,一般为1,如果只是访问不创建可以为0.
sem_flags:有两个值,IPC_CREATE和IPC_EXCL,
IPC_CREATE表示若信号量已存在,返回该信号量标识符。
IPC_EXCL表示若信号量已存在,返回错误。

返回值:相应的信号量标识符,失败返回-1

2、改变信号量的值

int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);

**sem_id:**信号量标识符
sem_opa:结构如下

struct sembuf{  
    short sem_num;//除非使用一组信号量,否则它为0  
    short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,  
                    //一个是+1,即V(发送信号)操作。  
    short sem_flg;//SEM_UNDO:当操作的进程退出后,该进程对sem进行的操作将被取消;
    	          //IPC_NOWAIT:在对信号量的操作不能执行的情况下,该操作立即返回;
    			  //0:在对信号量的操作不能执行的情况下,该操作阻塞到可以执行为止
}; 

3、信号量初始化和删除

int semctl(int sem_id,int sem_num,int command,[union semun sem_union]);

command:有两个值SETVAL,IPC_RMID,分别表示初始化和删除信号量。
sem_union:可选参数,结构如下:

union semun{  
    int val; 
    struct semid_ds *buf;  
    unsigned short *arry;  
}; 

五、信号

查看linux下所有的信号:

kill -l

当进程接收到信号的时候,处理方式如下:

1)执行默认操作。对大多数的信号系统的默认处理方式就是终止进程。

2)捕获信号。当定义了信号处理函数的时候,会捕获信号并执行对应的处理函数。

比如:

void handler(int sig) 
{  
	printf("%d\n",sig); 
} 

int main() 
{   
	signal(SIGINT,fun);  
    ...
}

3)忽略信号。

比如:

signal(SIGINT, SIG_IGN);

其中,SIGKILLSIGSTOP是不可以被捕获或者忽略的。

六、Socket

当互相通信的进程有可能不部署在同一台机器的时候,最好使用socket来通信。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值