IPC进程间通信及示例代码

一. 什么是进程通信

        进程通信( InterProcess Communication,IPC)就是指进程之间的信息交换。实际上,进程的同步与互斥本质上也是一种进程通信(这也就是待会我们会在进程通信机制中看见信号量和 PV 操作的原因了),只不过它传输的仅仅是信号量,通过修改信号量,使得进程之间建立联系,相互协调和协同工作,但是它缺乏传递数据的能力

        虽然存在某些情况,进程之间交换的信息量很少,比如仅仅交换某个状态信息,这样进程的同步与互斥机制完全可以胜任这项工作。但是大多数情况下,进程之间需要交换大批数据,比如传送一批信息或整个文件,这就需要通过一种新的通信机制来完成,也就是所谓的进程通信。

        再来从操作系统层面直观的看一些进程通信:我们知道,为了保证安全,每个进程的用户地址空间都是独立的,一般而言一个进程不能直接访问另一个进程的地址空间,不过内核空间是每个进程都共享的,所以进程之间想要进行信息交换就必须通过内核

下面就来我们来列举一下 Linux 内核提供的常见的进程通信机制:

  • 管道(也称作共享文件)
  • 消息队列(也称作消息传递)
  • 共享内存(也称作共享存储)
  • 信号量和 PV 操作
  • 信号
  • 套接字(Socket)

二. 管道

1.匿名管道

        管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。如果想实现相互通信(全双工通信),我们需要创建两个管道才行。另外,通过管道符 | 创建的管道是匿名管道,用完了就会被自动销毁。并且,匿名管道只能在具有亲缘关系(父子进程)的进程间使用。也就是说,匿名管道只能用于父子进程之间的通信

在 Linux 的实际编码中,是通过 pipe 函数来创建匿名管道的,若创建成功则返回 0,创建失败就返回 -1:

#include<sys/types.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
	pid_t pid1;
	int fields[2];
	char buffer[80];
	char s[100];
	char ss[100];
	if(pipe(fields)!=0){
		fprintf(stderr,"Createpipe error:%s\n\a",strerror(errno));
		exit(1);
	}
	if((pid1=fork())<0)printf("fork child error!\n");
/* 子进程写入数据 */
	if(pid1==0){
	printf("fork child,child is sending a message !\n");
	char s[]="hello!\n";
	write(fields[1],s,sizeof(s));
    exit(0)
		}
/* 父进程读取数据 */
	else 
	{
	printf("parent read start !\n");
	read(fields[0],buffer,80);
printf("parent receive the message:%s",buffer);
	}
	exit (0);
}

编译运行 

cc -o unamepipe unamepipe.c -g
./unamepipe

运行结果 

2.命名管道

        有名管道fifo解决了pipe只能有关系的进程才能通信的问题,实现一个有名管道实际上就是实现一个FIFO文件,有名管道一旦建立,之后它的读,以及关闭操作都与普通管道完全相同。虽然FIFO文件的inode节点在磁盘上,但仅是一个节点而已,文件的数据还是存在内核缓冲页面上,和普通管道相同。

使用 Linux 命令 mkfifo 来创建有名管道:

$ mkfifo myPipe

myPipe 就是这个管道的名称,接下来,我们往 myPipe 这个有名管道中写入数据: 

$ echo "hello" > myPipe

        执行这行命令后,你会发现它就停在这了,这是因为管道里的内容没有被读取,只有当管道里的数据被读完后,命令才可以正常退出。于是,我们执行另外一个命令来读取这个有名管道里的数据: 

$ cat < myPipe
hello

程序源码示例如下:

//读进程
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define FIFO_PATH "myfifofile"
int main()
{
	int fd;
	char cont_r[255];
#创建命名管道
	if(mkfifo(FIFO_PATH,0666)<0 && errno!=EEXIST)
	{
	
		perror("create fifo failed");
		return -1;

	}else
	{
	  	printf("create fifo success\n");
    #打开文件进行读操作
		fd =open(FIFO_PATH,O_CREAT|O_RDONLY,0666);
		if(fd>0)
		{
			while(1){
				read(fd,cont_r,255);
				printf("read:%s\n",cont_r);
			}
			close(fd);
		}else
			perror("open failed");

	}
	return 0;
}
//写进程
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define FIFO_PATH "myfifofile"
int main()
{
        int fd;
        char cont_w[] = "hello sundy";

        if(mkfifo(FIFO_PATH,0666)<0 && errno!=EEXIST)
        {

                perror("create fifo failed");
                return -1;

        }else
        {
                printf("create fifo success\n");

                fd =open(FIFO_PATH,O_CREAT|O_WRONLY,0666);
                if(fd>0)
                {
                        while(1){
                                write(fd,cont_w,strlen(cont_w));
                                printf("write success\n");
				                   sleep(2);
                        }
                        close(fd);
                }else
                        perror("open failed");

        }
        return 0;
}

编译运行
打开两个终端执行

三. 消息队列

        可以看出,管道这种进程通信方式虽然使用简单,但是效率比较低,不适合进程间频繁地交换数据,并且管道只能传输无格式的字节流。为此,消息传递机制(Linux 中称消息队列)应用而生。比如,A 进程要给 B 进程发送消息,A 进程把数据放在对应的消息队列后就可以正常返回了,B 进程在需要的时候自行去消息队列中读取数据就可以了。同样的,B 进程要给 A 进程发送消息也是如此。

消息队列的本质就是存放在内存中的消息的链表,而消息本质上是用户自定义的数据结构。如果进程从消息队列中读取了某个消息,这个消息就会被从消息队列中删除。对比一下管道机制:

  • 消息队列允许一个或多个进程向它写入或读取消息。
  • 消息队列可以实现消息的随机查询,不一定非要以先进先出的次序读取消息,也可以按消息的类型读取。比有名管道的先进先出原则更有优势。
  • 对于消息队列来说,在某个进程往一个队列写入消息之前,并不需要另一个进程在该消息队列上等待消息的到达。而对于管道来说,除非读进程已存在,否则先有写进程进行写入操作是没有意义的。
  • 消息队列的生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列就会一直存在。而匿名管道随进程的创建而建立,随进程的结束而销毁。

需要注意的是,消息队列对于交换较少数量的数据很有用,因为无需避免冲突。但是,由于用户进程写入数据到内存中的消息队列时,会发生从用户态拷贝数据到内核态的过程;同样的,另一个用户进程读取内存中的消息数据时,会发生从内核态拷贝数据到用户态的过程。因此,如果数据量较大,使用消息队列就会造成频繁的系统调用,也就是需要消耗更多的时间以便内核介入

相关函数

消息数据格式如下

源程序示例如下:

// 写进程 
#include <stdio.h> 
#include <sys/ipc.h> 
#include <sys/msg.h> 
  
// 消息队列数据结构
struct mesg_buffer { 
    long mesg_type; 
    char mesg_text[100]; 
} message; 
  
int main() 
{ 
    key_t key; 
    int msgid; 
  
    // ftok to generate unique key 
    key = ftok("progfile", 65); 
  
    // msgget creates a message queue 
    // and returns identifier 
    msgid = msgget(key, 0666 | IPC_CREAT); 
    message.mesg_type = 1; 
  
    printf("Write Data : "); 
    gets(message.mesg_text); 
  
    // msgsnd to send message 
    msgsnd(msgid, &message, sizeof(message), 0); 
  
    // display the message 
    printf("Data send is : %s \n", message.mesg_text); 
  
    return 0; 
}

 

// 读进程
#include <stdio.h> 
#include <sys/ipc.h> 
#include <sys/msg.h> 
  
// structure for message queue 
struct mesg_buffer { 
    long mesg_type; 
    char mesg_text[100]; 
} message; 
  
int main() 
{ 
    key_t key; 
    int msgid; 
  
    // ftok to generate unique key 
    key = ftok("progfile", 65); 
  
    // msgget creates a message queue 
    // and returns identifier 
    msgid = msgget(key, 0666 | IPC_CREAT); 
  
    // msgrcv to receive message 
    msgrcv(msgid, &message, sizeof(message), 1, 0); 
  
    // display the message 
    printf("Data Received is : %s \n",  
                    message.mesg_text); 
  
    // to destroy the message queue 
    msgctl(msgid, IPC_RMID, NULL); 
  
    return 0; 
}

运行结果

 

四. 共享内存

        为了避免像消息队列那样频繁的拷贝消息、进行系统调用,共享内存机制出现了。顾名思义,共享内存就是允许不相干的进程将同一段物理内存连接到它们各自的地址空间中,使得这些进程可以访问同一个物理内存,这个物理内存就成为共享内存。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

        集合内存管理的内容,我们来深入理解下共享内存的原理。首先,每个进程都有属于自己的进程控制块(PCB)和逻辑地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的逻辑地址(虚拟地址)与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同进程的逻辑地址通过页表映射到物理空间的同一区域,它们所共同指向的这块区域就是共享内存

         不同于消息队列频繁的系统调用,对于共享内存机制来说,仅在建立共享内存区域时需要系统调用,一旦建立共享内存,所有的访问都可作为常规内存访问,无需借助内核。这样,数据就不需要在进程之间来回拷贝,所以这是最快的一种进程通信方式。

相关函数

shmget:申请共享内存
shmat:建立用户进程空间到共享内存的映射
shmdt:解除映射关系
shmctl:回收共享内存空间

程序源码示例

//写进程
#include <sys/ipc.h> 
#include <sys/shm.h> 
#include <stdio.h> 
  
int main() 
{ 
    // ftok to generate unique key 
    key_t key = ftok("shmfile",65); 
  
    // shmget returns an identifier in shmid 
    int shmid = shmget(key,1024,0666|IPC_CREAT); 
  
    // shmat to attach to shared memory 
    char *str = (char*) shmat(shmid,(void*)0,0); 
  
    gets(str); 
  
    printf("Data written in memory: %s\n",str); 
      
    //detach from shared memory  
    shmdt(str); 
  
    return 0; 
}
//读进程
#include <sys/ipc.h> 
#include <sys/shm.h> 
#include <stdio.h> 

  
int main() 
{ 
    // ftok to generate unique key 
    key_t key = ftok("shmfile",65); 
  
    // shmget returns an identifier in shmid 
    int shmid = shmget(key,1024,0666|IPC_CREAT); 
  
    // shmat to attach to shared memory 
    char *str = (char*) shmat(shmid,(void*)0,0); 
  
    printf("Data read from memory: %s\n",str); 
      
    //detach from shared memory  
    shmdt(str); 
    
    // destroy the shared memory 
    shmctl(shmid,IPC_RMID,NULL); 
     
    return 0; 
}

编译运行

五. 信号量和 PV 操作

        实际上,对具有多 CPU 系统的最新研究表明,在这类系统上,消息传递的性能其实是要优于共享内存的,因为消息队列无需避免冲突,而共享内存机制可能会发生冲突。也就是说如果多个进程同时修改同一个共享内存,先来的那个进程写的内容就会被后来的覆盖。并且,在多道批处理系统中,多个进程是可以并发执行的,但由于系统的资源有限,进程的执行不是一贯到底的, 而是走走停停,以不可预知的速度向前推进(异步性)。但有时候我们又希望多个进程能密切合作,按照某个特定的顺序依次执行,以实现一个共同的任务。

        举个例子,如果有 A、B 两个进程分别负责读和写数据的操作,这两个线程是相互合作、相互依赖的。那么写数据应该发生在读数据之前。而实际上,由于异步性的存在,可能会发生先读后写的情况,而此时由于缓冲区还没有被写入数据,读进程 A 没有数据可读,因此读进程 A 被阻塞。

        因此,为了解决上述这两个问题,保证共享内存在任何时刻只有一个进程在访问(互斥),并且使得进程们能够按照某个特定顺序访问共享内存(同步),我们就可以使用进程的同步与互斥机制,常见的比如信号量与 PV 操作。

        信号量就是具有原子性的计数器,就相当于一把锁,在每个进程要访问临界资源时,必须要向信号量拿个锁”,它才能进去临界资源这个“房间”,并锁上门,不让其他进程进来,此时信号量执行P()操作,锁的数目减少了一个,所以计数器减1,;当它访问完成时,它出来,将锁还给信号量,执行V()操作,计数器加1;

程序源码示例:

让两个进程分别向显示器(linux下一切皆文件,临界资源)打印AA和BB,当没有信号量进行保护时,会出现数据混乱,例如:“AABBABAAAB…”,为了解决这一问题,我们创建信号量进行保护。打印“AA”或“BB”

#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<sys/sem.h>

union semun{
	int val;
	struct semid_ds *buf;
	unsigned short *array;
	//struct seminfo *buff;	
};

static int set_semvalue(void);
static void del_semvalue(void);
static int semaphore_p(void);
static int semaphore_v(void);
static int sem_id;

int main(int argc, char *argv[])
{
	int i;
	int pause_time;
	char op_char = 'O';
	srand((unsigned int)getpid());

	sem_id =semget((key_t)1234, 1, 0666 | IPC_CREAT);

/* 如果程序第一个被调用,也就是调用时含有一个参数,使得argc>1,此时就调用set_semvalue初始化信号量,并将op_char设置为x*/
	if (argc > 1){
		if(!set_semvalue()){
			fprintf(stderr, "Failed to initialize semaphore\n");
			exit(EXIT_FAILURE);
		}
		op_char = 'X';
		sleep(2);
	}

/*进入和离开临界区10次,每次循环开始的时候首先调用semaphore_p函数,它在程序将进入临界区域时设置信号量以等待进入*/
	for(i=0; i<10; i++){
		if(!semaphore_p()) exit(EXIT_FAILURE);
		printf("%c", op_char);fflush(stdout);
		pause_time = rand() % 3;
		sleep(pause_time);
		printf("%c", op_char);fflush(stdout);

/*进入临界区域后,调用semaphore_v将信号量设置为可用,然后等待一段随机的时间,再进入下一次循环*/
		if(!semaphore_v()) exit(EXIT_FAILURE);
		pause_time = rand() % 2;
		sleep(pause_time);
	}
	printf("\n%d - finished\n", getpid());
	if (argc > 1)
	{
		sleep(10);
		del_semvalue();
	}
	exit(EXIT_SUCCESS);
}

/*该函数用来将semctl调用的command参数设置为SETVAL来初始化信号量*/
static int set_semvalue(void)
{
	union semun sem_union;
	
	sem_union.val = 1;
	if (semctl(sem_id, 0, SETVAL, sem_union)==-1) return 0;
	return (1);
}
/*通过调用semctl调用的command设置为IPC_RMID来删除信号量ID*/
static void del_semvalue(void)
{
	union semun sem_union;

	if (semctl(sem_id, 0, IPC_RMID, sem_union)==-1)
	fprintf(stderr, "Failed to delete semaphore");
}

/*对信号量执行减1操作*/
static int semaphore_p(void)
{
	struct sembuf sem_b;
	sem_b.sem_num = 0;
	sem_b.sem_op = -1;
	sem_b.sem_flg = SEM_UNDO;
	if (semop(sem_id, &sem_b, 1) == -1){
		fprintf(stderr,"semaphore_p failed\n");
		return (0);
	}
	return(1);
}

/*对信号量执行加1操作*/
static int semaphore_v(void)
{
	struct sembuf sem_b;
	sem_b.sem_num = 0;
	sem_b.sem_op = 1;
	sem_b.sem_flg = SEM_UNDO;
	if (semop(sem_id, &sem_b, 1) == -1){
		fprintf(stderr,"semaphore_v failed\n");
		return (0);
	}
	return(1);
}

编译运行

源代码示例2,与上述代码几乎相同,只不过变为了父子进程间的信号量机制。

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

static int set_semvalue(void);
static void del_semvalue(void);
static int semaphore_p(void);
static int semaphore_v(void);
static int sem_id;

union semun{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    //struct seminfo *buff; 
};
int main(int argc, char *argv[])
{
    int i;
    int pause_time;
    char op_char = 'O';
    srand((unsigned int)getpid());

    sem_id =semget((key_t)1234, 1, 0666 | IPC_CREAT);
    int id = fork();
    if(id<0)
    {
        perror("fork failed\n");
        return -1;
    }

    else if (id>0){
        if(!set_semvalue()){
            fprintf(stderr, "Failed to initialize semaphore\n");
            exit(EXIT_FAILURE);
        }
        op_char = 'X';
        sleep(2);
    }
    for(i=0; i<10; i++){
        if(!semaphore_p()) exit(EXIT_FAILURE);
        printf("%c", op_char);fflush(stdout);
        pause_time = rand() % 3;
        sleep(pause_time);
        printf("%c", op_char);fflush(stdout);
        if(!semaphore_v()) exit(EXIT_FAILURE);
        pause_time = rand() % 2;
        sleep(pause_time);
    }
    printf("\n%d - finished\n", getpid());
    if (id> 0)
    {
        sleep(10);
        del_semvalue();
    }
    exit(EXIT_SUCCESS);
}

static int set_semvalue(void)
{
    union semun sem_union;
    
    sem_union.val = 1;
    if (semctl(sem_id, 0, SETVAL, sem_union)==-1) return 0;
    return (1);
}

static void del_semvalue(void)
{
    union semun sem_union;

    if (semctl(sem_id, 0, IPC_RMID, sem_union)==-1)
    fprintf(stderr, "Failed to delete semaphore");
}
static int semaphore_p(void)
{
    struct sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = -1;
    sem_b.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sem_b, 1) == -1){
        fprintf(stderr,"semaphore_p failed\n");
        return (0);
    }
    return(1);
}
static int semaphore_v(void)
{
    struct sembuf sem_b;
    sem_b.sem_num = 0;
    sem_b.sem_op = 1;
    sem_b.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sem_b, 1) == -1){
        fprintf(stderr,"semaphore_v failed\n");
        return (0);
    }
    return(1);
}

编译运行

在这里我们可以看到子进程的pid为父进程的pid+1,注意的是fork创建子进程的返回值为0,和这里的pid是不同的。

六. 信号

注意!信号和信号量是完全不同的两个概念

        信号是进程通信机制中唯一的异步通信机制,它可以在任何时候发送信号给某个进程。通过发送指定信号来通知进程某个异步事件的发送,以迫使进程执行信号处理程序。信号处理完毕后,被中断进程将恢复执行。用户、内核和进程都能生成和发送信号。

        信号事件的来源主要有硬件来源和软件来源。所谓硬件来源就是说我们可以通过键盘输入某些组合键给进程发送信号,比如常见的组合键 Ctrl+C 产生 SIGINT 信号,表示终止该进程;而软件来源就是通过 kill 系列的命令给进程发送信号,比如 kill -9 1111 ,表示给 PID 为 1111 的进程发送 SIGKILL 信号,让其立即结束。

程序源码示例

以模拟闹钟的形式,通过一个进程向另一个进程发送SIGALRM信号来表现进程间的通信。

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

static int alarm_fired = 0;

/*该函数用来模拟闹钟*/
void ding(int sig)
{
    alarm_fired =1;
}

/*main函数中告诉子进程在等待5秒后发送SIGALRM信号给它的父进程*/
int main()
{
    pid_t pid;
    printf("alarm start\n");
    pid = fork();  /*创建子进程*/
    switch (pid)
    {
    case -1:
        perror("fork failed");
        exit(1);
    
    case 0:
        sleep(5);  /*子进程休眠5秒*/
        kill(getppid(), SIGALRM);  /*子进程在5秒后将SIGALRM信号传递给父进程*/
        exit(0);
}

/*父进程通过一个signal调用捕获SIGALRM信号的工作,等待该信号的到来*/
    printf("waitting for alarm to go on\n");
    (void) signal(SIGALRM, ding);
    pause();
    if (alarm_fired)
        printf("ding!\n");
    printf("done\n");
    exit(0);
}

编译运行

七. Socket

        至此,上面介绍的 5 种方法都是用于同一台主机上的进程之间进行通信的,如果想要跨网络与不同主机上的进程进行通信,那该怎么做呢?这就是 Socket 通信做的事情了(当然,Socket 也能完成同主机上的进程通信)。

        socket即套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。也因为这样,套接字明确地将客户端和服务器区分开来。

 程序源码示例

 //服务器
    #include <unistd.h>  
    #include <sys/types.h>  
    #include <sys/socket.h>  
    #include <netinet/in.h>  
    #include <signal.h>  
    #include <stdio.h>  
    #include <stdlib.h>  
     int main()  
    {  
        int server_sockfd = -1;  
        int client_sockfd = -1;  
        int client_len = 0;  
        struct sockaddr_in server_addr;  
        struct sockaddr_in client_addr;  
        //创建流套接字  
        server_sockfd = socket(AF_INET, SOCK_STREAM, 0);  
        //设置服务器接收的连接地址和监听的端口  
        server_addr.sin_family = AF_INET;//指定网络套接字  
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//接受所有IP地址的连接  
        server_addr.sin_port = htons(9736);//绑定到9736端口  
        //绑定(命名)套接字  
        bind(server_sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));  
        //创建套接字队列,监听套接字  
        listen(server_sockfd, 5);  
        //忽略子进程停止或退出信号  
        signal(SIGCHLD, SIG_IGN);  
          
        while(1)  
        {  
            char ch = '\0';  
            client_len = sizeof(client_addr);  
            printf("Server waiting\n");  
            //接受连接,创建新的套接字  
            client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_addr, &client_len);  
      
            if(fork() == 0)  
            {  
                //子进程中,读取客户端发过来的信息,处理信息,再发送给客户端  
                read(client_sockfd, &ch, 1);  
                sleep(5);  
                ch++;  
                write(client_sockfd, &ch, 1);  
                close(client_sockfd);  
                exit(0);  
            }  
            else  
            {  
                //父进程中,关闭套接字  
                close(client_sockfd);  
            }  
        }  
    }  

 

 //客户端
    #include <unistd.h>  
    #include <sys/types.h>  
    #include <sys/socket.h>  
    #include <netinet/in.h>  
    #include <arpa/inet.h>  
    #include <stdio.h>  
    #include <stdlib.h>  
      
    int main()  
    {  
        int sockfd = -1;  
        int len = 0;  
        struct sockaddr_in address;  
        int result;  
        char ch = 'A';  
        //创建流套接字  
        sockfd = socket(AF_INET, SOCK_STREAM, 0);  
        //设置要连接的服务器的信息  
        address.sin_family = AF_INET;//使用网络套接字  
        address.sin_addr.s_addr = inet_addr("127.0.0.1");//服务器地址  
        address.sin_port = htons(9736);//服务器所监听的端口  
        len = sizeof(address);  
        //连接到服务器  
        result = connect(sockfd, (struct sockaddr*)&address, len);  
      
        if(result == -1)  
        {  
            perror("ops:client\n");  
            exit(1);  
        }  
        //发送请求给服务器  
        write(sockfd, &ch, 1);  
        //从服务器获取数据  
        read(sockfd, &ch, 1);  
        printf("char form server = %c\n", ch);  
        close(sockfd);  
        exit(0);  
    }  

编译运行

八. 总结

简单总结一下上面六种 Linux 内核提供的进程通信机制:

1)首先,最简单的方式就是管道,管道的本质是存放在内存中的特殊的文件。也就是说,内核在内存中开辟了一个缓冲区,这个缓冲区与管道文件相关联,对管道文件的操作,被内核转换成对这块缓冲区的操作。管道分为匿名管道和有名管道,匿名管道只能在父子进程之间进行通信,而有名管道没有限制。

2)虽然管道使用简单,但是效率比较低,不适合进程间频繁地交换数据,并且管道只能传输无格式的字节流。为此消息队列应用而生。消息队列的本质就是存放在内存中的消息的链表,而消息本质上是用户自定义的数据结构。如果进程从消息队列中读取了某个消息,这个消息就会被从消息队列中删除。

3)消息队列的速度比较慢,因为每次数据的写入和读取都需要经过用户态与内核态之间数据的拷贝过程,共享内存可以解决这个问题。所谓共享内存就是:两个不同进程的逻辑地址通过页表映射到物理空间的同一区域,它们所共同指向的这块区域就是共享内存。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

对于共享内存机制来说,仅在建立共享内存区域时需要系统调用,一旦建立共享内存,所有的访问都可作为常规内存访问,无需借助内核。这样,数据就不需要在进程之间来回拷贝,所以这是最快的一种进程通信方式。

4)共享内存速度虽然非常快,但是存在冲突问题,为此,我们可以使用信号量和 PV 操作来实现对共享内存的互斥访问,并且还可以实现进程同步。

5)信号和信号量是完全不同的两个概念!信号是进程通信机制中唯一的异步通信机制,它可以在任何时候发送信号给某个进程。通过发送指定信号来通知进程某个异步事件的发送,以迫使进程执行信号处理程序。信号处理完毕后,被中断进程将恢复执行。用户、内核和进程都能生成和发送信号。

6)上面介绍的 5 种方法都是用于同一台主机上的进程之间进行通信的,如果想要跨网络与不同主机上的进程进行通信,就需要使用 Socket 通信。另外,Socket 也能完成同主机上的进程通信。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/101014.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

什么?内存爆了?详细解读虚拟内存机制

不知道大家在运行自己写的程序时&#xff0c;有没有发现一个问题&#xff1a;就是物理机器明明只有8G内存&#xff0c;但是我们运行的程序却可以申请到16G的内存&#xff1f;或者说机器上运行的多个进程&#xff0c;占用的总内存已经远超物理内存了&#xff0c;却还能正常工作。…

pdf转换成图片免费软件用哪个?pdf转换成图片就用它

随着技术的发展&#xff0c;现在企业办公运用到的电子文档各种各样&#xff0c;我们日常需要掌握的技能越来越高要求&#xff0c;其中pdf和图片是我们经常接触的文件格式之一&#xff0c;而且这两个文件格式我们会经常将它们进行转换&#xff0c;那么pdf转换成图片怎么操作呢?…

LDAP服务器如何重启

1、find / -name ldap 该命令只会从根路径下查看ldap文件夹 find / -name ldap2、该命令会从根路径/查看所有包含ldap路径的文件夹&#xff0c;会查询出所有&#xff0c;相当于全局查询 find / -name *ldap*2、启动OpenLADP 找到LDAP安装目录后&#xff0c;执行以下命令 #直…

数字孪生智慧仓储的关键特点和优势有哪些

数字孪生智慧仓储是一种基于数字孪生技术的智能仓储解决方案。数字孪生是指使用数字模型来模拟和仿真现实世界中的物理实体或系统的技术。在智慧仓储的上下文中&#xff0c;数字孪生被用来创建虚拟的仓储环境&#xff0c;以实时监测、优化和管理仓储操作。 数字孪生智慧…

vue3升级了些什么

Vue 3 升级了以下几个方面的内容&#xff1a; 响应式系统&#xff1a;Vue 3 使用了 Proxy 对象来替代 Vue 2 中的 Object.defineProperty&#xff0c;这使得响应式系统更加高效和灵活。Vue 3 的响应式系统可以追踪更细粒度的依赖关系&#xff0c;提供了更好的性能和更细致的响应…

Apache的简单介绍(LAMP架构+搭建Discuz论坛)

文章目录 1.Apache概述1.1什么是apache1.2 apache的功能及特性1.2.1功能1.2.2特性 1.3 MPM 工作模式1.3.1 prefork模式1.3.2 worker模式1.3.3 event模式 2.LAMP概述2.1 LAMP的组成2.2 LAMP各组件的主要作用2.3 LAMP的工作过程2.4CGI和FastCGI 3.搭建Discuz论坛所需4.编译安装Ap…

BEVFusion复现 (Ubuntu RTX3090)

https://github.com/ADLab-AutoDrive/BEVFusion 1.环境安装 我的机器是RTX3090&#xff0c;CUDA11.1 1.创建虚拟环境 conda create -n bevfusion python3.8.3 2.安装PyTorch 和 torchvision pip install torch1.8.0cu111 torchvision0.9.0cu111 torchaudio0.8.0 -f https://…

C# 如何将使用的Dll嵌入到.exe应用程序中?

文章目录 前言详细实操简要步骤 前言 有没有想自己开发的exe保留一点神秘&#xff0c;不想让他人知道软件使用了哪些dll; 又或许是客户觉得一个软件里面的dll文件太多了&#xff0c;能不能简单一点&#xff0c;直接双击.exe就可以直接运行了&#xff0c;别搞那么多乱七八糟的。…

vue3-vuex持久化实现

vue3-vuex持久化实现 一、背景描述二、实现思路1.定义数据结构2.存值3.取值4.清空 三、具体代码1.定义插件2.使用插件 四、最终效果 一、背景描述 有时候我们可能需要在vuex中存储一些静态数据&#xff0c;比如一些下拉选项的字典数据。这种数据基本很少会变化&#xff0c;所以…

webassembly003 ggml ADAM (暂记)

Adam优化器的工作方式是通过不断更新一阶矩估计和二阶矩估计来自适应地调整学习率&#xff0c;并利用动量法来加速训练过程。这种方式可以在不同的参数更新方向和尺度上进行自适应调整&#xff0c;从而更有效地优化模型。 https://arxiv.org/pdf/1412.6980.pdf 参数 这些参数…

CSS实现白天/夜晚模式切换

目录 功能介绍 示例 原理 代码 优化 总结 功能介绍 在网页设计和用户体验中&#xff0c;模式切换功能是一种常见的需求。模式切换可以为用户提供不同的界面外观和布局方案&#xff0c;以适应其个人偏好或特定环境。在这篇博客中&#xff0c;我们将探索如何使用纯CSS实现一…

Python所有方向的学习路线图!!

学习路线图上面写的是某个方向建议学习和掌握的知识点汇总&#xff0c;举个例子&#xff0c;如果你要学习爬虫&#xff0c;那么你就去学Python爬虫学习路线图上面的知识点&#xff0c;这样学下来之后&#xff0c;你的知识体系是比较全面的&#xff0c;比起在网上找到什么就学什…

数据结构——哈希表

哈希表 这里没有讲哈希表底层的概念&#xff0c;什么转红黑树&#xff0c;什么链表的&#xff0c;这篇文章主要讲的是如何用C实现哈希表&#xff0c;以及哈希表的基本概念。后面我会出一篇文章来讲C中hashmap中的底层逻辑的知识。 哈希表的概念 哈希表是一种数据结构&#xff0…

Unity3D 如何在ECS架构下,用Unity引擎进行游戏开发详解

前言 Unity3D是一款强大的游戏引擎&#xff0c;它提供了丰富的功能和工具&#xff0c;可以帮助开发者快速构建高质量的游戏。而Entity Component System&#xff08;ECS&#xff09;是Unity3D中一种新的架构模式&#xff0c;它可以提高游戏的性能和可扩展性。本文将详细介绍在…

一米ip流量池系统

PC端快速切换移动网络IP 支持全网通sim卡槽&#xff0c;国内三大运营商IP池动态切换&#xff0c;实现真实移动端IP切换。从此换IP再也不用vpn或代理&#xff0c;一个设备搞定 1.兼容国内电信&#xff0c;移动&#xff0c;联通三网通的sim卡4G连接&#xff0c;快速稳定2.可直接…

layui框架学习(40:数据表格_主要事件)

Layui数据表格模块主要通过各类事件响应工具栏操作、单元格编辑或点击等交互操作&#xff0c;本文学习table数据表格模块中的主要事件及处理方式。   头部工具栏事件。通过代码“table.on(‘toolbar(test)’, function(obj))”获取lay-filter属性为test的数据表格的头部工具栏…

MySQL中日期、时间直接相减的坑

前言 在牛客网上写一道 SQL 题时&#xff0c;需要计算两个日期之间相隔的秒数&#xff0c;我在写的时候直接将两个日期进行相减&#xff0c;得出来的值却不是相差的秒数。 情景再现 我在 MySQL 中进行了测试&#xff0c;得出的结论是&#xff1a;如果日期类型直接相减&#…

LeetCode 面试题 02.04. 分割链表

文章目录 一、题目二、C# 题解 一、题目 给你一个链表的头节点 head 和一个特定值 x&#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你不需要 保留 每个分区中各节点的初始相对位置。 点击此处跳转题目。 示例 1&#…

【实操干货】如何开始用Qt Widgets编程?(四)

Qt 是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写&#xff0c;所有平台无差别运行&#xff0c;更提供了几乎所有开发过程中需要用到的工具。如今&#xff0c;Qt已被运用于超过70个行业、数千家企业&#xff0c;支持数百万设备及应用。 在本文中&#xff0…

性能测试工具Jmeter你所不知道的东西····

谈到性能测试&#xff0c;大家一定会联想到Jmeter和LoadRunner,这两款工具目前在国内使用的相当广泛&#xff0c;主要原因是Jmeter是开源免费&#xff0c;LoadRunner 11在现网中存在破解版本。商用型性能测试工具对于中小型企业很难承担相关的费用。国内的性能测试工具有&#…