[linux]进程(七)——进程通信

时间:2021-11-07 15:49:14
进程间通信
一,管道,
管道的限制:
(1)半双工,数据只能在一个方向上流动
(2)管道一般只在具有公共祖先的进程之间使用,通常一个管道由一个进程创建,然后该进程调用fork()函数,此后父子进程可以使用该管道
管道的创建:
#include <unistd.h>
int pipe(int fileds[2]);  //filedes[0]为读而打开,filedes[1]为写而打开
向一个没有读进程关联的管道写数据,会产生SIGPIPE,内核对于SIGPIPE的默认动作是退出该进程,这个通常不是我们期望看到的,因此我们需要重载这个信号处理方法,signal(SIGPIPE,SIG_IGN);


二,FIFO
FIFO被称为命名管道,和PIPE不同的是,通过FIFO不相关的进程也能交换数据
创建FIFO的方式如下
int mkfifo(const char*pathname,mode_t mode);

在创建了fifo后,一般对文件的操作函数都可以用于它,open close read write等,
fifo被多个进程用于写很常见,所以应该考虑进程间的同步,并且多个写进程写的数据之间不会穿插。答:PIPE_BUF规定了内核中管道缓冲区的大小,所有写的进程写的大小必须要小于PIPE_BUF的大小
FIFO可以用于客户进程与服务进程之间的通信,如何保证同步?
答:服务进程创建一个众所周知的FIFO用来接收客户进程的请求,然后根据客户进程id不一样,再为每个客户进程创建一个fifo用来给客户进程发送服务进程的回应消息,这样的问题是服务进程如何知道客户进程已经退出了?
FIFO的缺点是不支持随机访问,因为PIPE和FIFO都是先入先出的特点


三,消息队列
查看linux系统共享内存和消息队列的情况:

ipcs [-m|-q|-s]
-m 输出有关共享内存(shared memory)的信息
-q 输出有关信息队列(message queue)的信息
ipcrm命令

用来手动解除linux使用的共享内存~


四,共享内存
以下共享内存大部分来自以下网址点击打开链接
共享内存适合比较大的数据集,因为它使用内存,支持快速的随机访问,也是最快的IPC形式共享内存并不是从某一进程拥有的内存中划分出来的;进程的内存总是私有的
shm_open():创建共享内存段或连接到现有的已命名内存段。这个系统调用返回一个文件描述符。
shm_unlink():根据(shm_open() 返回的)文件描述符,删除共享内存段。实际上,这个内存段直到访问
它的所有进程都退出时才会删除,这与在 UNIX 中删除文件很相似。但是,调用 shm_unlink() (通常由原来创建共享内存段的进程调用)之后,其他进程就无法访问这个内存段了。
mmap():把共享内存段映射到进程的内存。这个系统调用需要 shm_open() 返回的文件描述符,它返回指向内存的指针。(在某些情况下,还可以把一般文件或另一个设备的文件描述符映射到内存。mmap的实现也是基于上述原理,在使用mmap映射某个文件(或者文件的一部分)到进程的地址空间时,并没有加载文件的数据,而只是在进程的虚拟地址空间划分出一块区域,标记这块区域用于映射到文件的数据区域,mmap的操作就完成了。
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset ) 
munmap():作用与 mmap() 相反。
msync():用来让共享内存段与文件系统同步 — 当把文件映射到内存时,这种技术有用。
使用共享内存的步骤:
使用共享内存的过程是,用 shm_open() 创建内存段,用 write() 或 ftruncate() 设置它的大小,用 mmap() 把它映射到进程内存,执行其他参与者需要的操作。当使用完时,原来的进程调用 munmap() 和 shm_unlink(),然后退出。

示例程序

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/wait.h>
void error_and_die(const char *msg) {
  perror(msg);
  exit(EXIT_FAILURE);
}

int main(int argc, char *argv[]) {
  int r;

  const char *memname = "sample";
  const size_t region_size = sysconf(_SC_PAGE_SIZE);

  int fd = shm_open(memname, O_CREAT | O_TRUNC | O_RDWR, 0666);
  if (fd == -1)
    error_and_die("shm_open");

  r = ftruncate(fd, region_size);
  if (r != 0)
    error_and_die("ftruncate");

  void *ptr = mmap(0, region_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  if (ptr == MAP_FAILED)
    error_and_die("mmap");
  close(fd);

  pid_t pid = fork();

  if (pid == 0) {
    u_long *d = (u_long *) ptr;
    *d = 0xdbeebee;
    exit(0);
  }
  else {
    int status;
    waitpid(pid, &status, 0);
    printf("child wrote %#lx\n", *(u_long *) ptr);
  }
  r = munmap(ptr, region_size);
  if (r != 0)
    error_and_die("munmap");
  r = shm_unlink(memname);
  if (r != 0)
    error_and_die("shm_unlink");
  return 0;
}

以下为另外一个示例程序

点击打开链接

/*-------------map_normalfile1.c-----------*/
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct{
  char name[4];
  int  age;
}people;
main(int argc, char** argv) // map a normal file as shared mem:
{
  int fd,i;
  people *p_map;
  char temp;
  
  fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
  lseek(fd,sizeof(people)*5-1,SEEK_SET);
  write(fd,"",1);
  
  p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,
        MAP_SHARED,fd,0 );
  close( fd );
  temp = 'a';
  for(i=0; i<10; i++)
  {
    temp += 1;
    memcpy( ( *(p_map+i) ).name, &temp,2 );
    ( *(p_map+i) ).age = 20+i;
  }
  printf(" initialize over \n ");
  sleep(10);
  munmap( p_map, sizeof(people)*10 );
  printf( "umap ok \n" );
}
/*-------------map_normalfile2.c-----------*/
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct{
  char name[4];
  int  age;
}people;
main(int argc, char** argv)  // map a normal file as shared mem:
{
  int fd,i;
  people *p_map;
  fd=open( argv[1],O_CREAT|O_RDWR,00777 );
  p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,
       MAP_SHARED,fd,0);
  for(i = 0;i<10;i++)
  {
  printf( "name: %s age %d;\n",(*(p_map+i)).name, (*(p_map+i)).age );
  }
  munmap( p_map,sizeof(people)*10 );
}

 

共享内存的限制:

点击打开链接

正常大小为32M,可以通过shmmax更改~

/proc/sys/kernel/shmmax


 管道内存需要四次拷贝,而共享内存只需要两次内存拷贝

消息队列和管道基本上都是4次拷贝,而共享内存(mmap, shmget)只有两次。 
4次:1,由用户空间的buf中将数据拷贝到内核中。2,内核将数据拷贝到内存中。3,内存到内核。4,内核到用户空间的buf
2次: 1,用户空间到内存。 2,内存到用户空间。 
消息队列和管道都是内核对象,所执行的操作也都是系统调用,而这些数据最终是要存储在内存中执行的。因此不可避免的要经过4次数据的拷贝。但是共享内存不同,当执行mmap或者shmget时,会在内存中开辟空间,然后再将这块空间映射到用户进程的虚拟地址空间中,即返回值为一个指向一个内存地址的指针。当用户使用这个指针时,例如赋值操作,会引起一个从虚拟地址到物理地址的转化,会将数据直接写入对应的物理内存中,省去了拷贝到内核中的过程。当读取数据时,也是类似的过程,因此总共有两次数据拷贝。



五,信号
软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。进程之间可以互相通过系统调用kill发送软中断信号。
信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。
在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号,当有信号发送给进程时,对应位置位。由此可以看出,进程对不同的信号可以同时保留,但对于同一个信号,进程并不知道在处理之前来过多少个。
信号处理函数如下
void (*signal(int signo,void (*func)(int)))(int)
示例如下
if(signal(SIGUSR1,sig_usr)==SIG_ERR)
	printf("can not catch SIG_USR1");
kill函数用来将信号发送给进程或者进程组,raise函数则允许进程向自身发送信号
int kill(pid_t pid,int signo);
int raise(int signo);
信号的缺点是:不能用来传输数据,一般用来在进程之间的异常通知



六:socket通信



七,文件

有点是可以共享大量的数据,缺点是共享速度慢,因为涉及到了磁盘的读写,磁盘的速度远比不上内存,另外文件本身不安全,有些特权用户可以将文件删除。