system V IPC进程间通信机制一网打尽

目录

必备IPCS命令解析

ipcs  

ipcrm   

Linux IPC消息队列

msgget

msgsnd

msgrcv

msgctl

Linux IPC信号量

理解信号量

semget

semop

semctl

Linux IPC共享内存

shmget

shmat

shmdt​ 

shmctl


本文纯粹就是小杰对于自己学完Linux操作系统之后回过头来对于Linux中的核心重点知识的一个梳理. 
小杰会尽量地将其梳理清楚, 大家一起学习,共同进步, 知识不分高低, 计算机的学习小杰认为也是一个    
量变   --->   质变    的过程
天道酬勤, 水滴石穿, 在不同的阶段就干好自己当前阶段力所能及之事,  至少是没有在寝室的床上瘫着消磨时光                                                 --------   愿大家都学有所成,所获

必备IPCS命令解析

  • ipcs  

功能 : 查看 system V IPC进程间通信设施的信息

常用选项

-a : 查看所有通信设施信息, 不加选项默认-a

-m : 指定查看共享内存

-q  : 指定查看消息队列

-s   : 指定查看信号量 

system V IPC进程间通信机制一网打尽_第1张图片

  • ipcrm   

功能:删除System V进程间通信(IPC)对象和关联的数据结构

使用方式:  ipcrm 选项 id号

常用选项

-m : 删除共享内存

-q : 删除消息队列

-s : 删除信号量

eg : 指定删除shmid = 18 和  msqid = 8的 IPC对象.

system V IPC进程间通信机制一网打尽_第2张图片

Linux IPC消息队列

消息队列提供了一个进程向另外一个进程传输一个数据块的方法

消息队列优势: 对比管道通信来看

  1.  避免了管道通信的同步阻塞问题    eg : 读进程从管道中读取数据, 但是管道中并没有数据可读, 此刻读进程就需要同步阻塞等待写进程写入数据.
  2. 消息队列不必像管道那样先进先出的接收数据了, 消息队列可以根据类型(type值)来有选择地接收数据
  • msgget

system V IPC进程间通信机制一网打尽_第3张图片 

函数功能: 创建一个全局消息队列IPC对象.   (内核数据结构)

参数分析:  key 唯一标识, 唯一标识一个全局的消息队列.   msgflg指定权限

返回值: 成功返回一个正整数, 代表消息队列的句柄, 失败返回 -1,并且设置errno

插一个权限介绍:     r : 读权限    w : 可写权限   X : 可执行权限.   所谓的9位权限标识位, 就是针对: 文件拥有者, 文件拥有组人员, 其他人员的访问权限的限制

  • msgsnd

函数功能:发送一条消息到消息队列中

参数分析: 

  1. msqid 之前msgget返回的消息队列句柄
  2. msgp 指针, 指向待发送的一条消息结构体, 结构体形式如下
  3. msgsz  消息正文的存储空间, 最多可以存下msgsz个字节的正文
  4. msgflg 控制发送消息的形式  eg: 常用的 IPC_NOWAIT 表示非阻塞发送, 意思就是说正常没有设置非阻塞的情况下, 如果消息队列满了, 就会阻塞等待, 但是设置了IPC_NOWAIT则会立刻返回, 并且设置errno = EAGAIN
struct msgbuf {
    long mtype;       /* message type, must be > 0 */
    char mtext[1];    /* message data */
};
//我们必须按照这个形式设置msgbuff

返回值:成功返回 0, 失败返回 -1 并且设置errno

  • msgrcv

函数功能:从消息队列中接收一条消息

参数分析:

  1. msqid 之前msgget返回的消息队列句柄
  2. msgp 指针, 指向待发送的一条消息结构体, 结构体形式如下
  3. msgsz  消息正文的存储空间, 最多可以存下msgsz个字节的正文
  4. msgflg 控制接收消息的形式  eg: 常用的 IPC_NOWAIT 表示非阻塞接收, 意思就是说正常没有设置非阻塞的情况下, 如果消息队列没有消息, 就会阻塞等待, 但是设置了IPC_NOWAIT则会立刻返回, 并且设置errno = ENOMSG

msgtyp 指定接受哪种类型的消息:

  1. 等于0,指定接收消息队列第一条消息
  2. 大于0,指定接收消息队列中第一条类型等于msgtyp的消息
  3. msgtyp 小于0, 读取消息队列中第一个类型值比msgtyp的绝对值小的消息

返回值:  成功返回接收的消息正文长度, 失败返回 -1 并且设置 errno

  • msgctl

函数功能: 控制消息队列, 对于消息队列IPC进行设置, 操作

参数分析:

  1. msqid 之前msgget返回的消息队列句柄
  2. cmd 对于消息队列的操作, 一般用的最多是 IPC_RMID,删除消息队列标识符

返回值:成功返回 0, 失败返回 -1 并且设置 errno

实际案例, 父子进程之间通信, 父进程向消息队列中写入消息, 子进程按照消息的类型接收消息

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 



#define ERR_EXIT(m) \
    do { perror(m); exit(EXIT_FAILURE); } while (0);

#define TEXTSIZE 1024


//消息结构体
typedef struct msgbuff {
	long int type;
	char  text[TEXTSIZE];
} msgbuff;



int main(int argc, char* argv[]) {
	if (2 != argc) {
		fprintf(stderr, "usage: %s ", argv[0]);
		exit(EXIT_FAILURE);
	}

	key_t key = ftok(argv[1], 'q');
	pid_t pid = fork();
	int msgid;


	if (pid == -1) {
		ERR_EXIT("fork");
	}

	if (pid == 0) {
		//son process
		//获取消息队列
		msgid = msgget(key, 0666 | IPC_CREAT);

		msgbuff msg;

		//然后接收类型为1的消息并且打印
		int read_size = msgrcv(msgid, &msg, TEXTSIZE, 1, 0);
		if (-1 == read_size) {
			ERR_EXIT("msgrcv");
		}

		printf("type: %d, text: %s\n", msg.type, msg.text);

		memset(&msg, 0, sizeof(msg));
		//再接收一下类型为2的消息并且打印
		read_size = msgrcv(msgid, &msg, TEXTSIZE, 2, 0);
		if (-1 == read_size) {
			ERR_EXIT("msgrcv");
		}

		printf("type: %d, text: %s\n", msg.type, msg.text);

	} else {
		//fa process
		//创建消息队列
		msgid = msgget(key, 0666 | IPC_CREAT);
		//创建消息结构体
		msgbuff msg1, msg2;
		memset(&msg1, 0, sizeof(msg1));
		memset(&msg1, 0, sizeof(msg2));

		msg1.type = 1;
		memcpy(msg1.text, "hello msg queue I am type1", strlen("hello msg queue I am type1"));
		msgsnd(msgid, &msg1, TEXTSIZE, 0);

		msg2.type = 2;
		memcpy(msg2.text, "hello msg queue, I am type2", strlen("hello msg queue, I am type2"));
		msgsnd(msgid, &msg2, TEXTSIZE, 0);

		waitpid(pid, NULL, 0);

    	if (-1 == msgctl(msgid, IPC_RMID, NULL)) {
        	ERR_EXIT("msgctl");
        	exit(1);
    	}
	}

	return 0;
}

Linux IPC信号量

  • 理解信号量

信号量是一种资源, 临界资源, 是为了多进程间或者多线程间的同步互斥问题而存在的.

信号量本身也是一个IPC对象,是临界共享资源, 利用它使得多进程或者多线程之间可以实现通信.

信号量本身就是具有原子性的计数器, 意思就是说对于信号量的操作是自带原子性的, 就不用担心多进程或者多线程互斥写操作的问题了.

何为同步问题: 同步, 两个事件之间具有同步的性质, 意味着两个事件之间是存在条件约束的, 是有等待关系的.    eg  B 的要发生必须先发生 A ,否则B只能挂起等待,  则称 A, B是同步的

Linux最常见的同步问题: 条件变量, 满足一个条件, 才能执行一个操作. 在满足条件之前挂起等待, 条件的满足其实依赖的是 另一个事件的发生.

通信的时候通信双方也必须保持同步,比如说共享内存就需要借助信号量这样的同步机制,维护通信双方进程之间的同步.

何为互斥问题:    多个进程或者线程对于同一份资源进行写操作, 写操作一定是互斥的, 即为同一时刻只能允许一个进程或者线程对这份资源进行一个写操作.

  • semget

system V IPC进程间通信机制一网打尽_第4张图片

函数功能:创建或者获取一个信号量集合IPC对象

参数分析:

  1. key :标识全局信号量集合
  2. nsems: 表示要创建/获取的信号量集中信号量的数目,如果是创建信号量,这个值必须指定。如果是获取已经存在的信号量,可以把它设置成0.
  3. semflg:指定权限

插一个权限介绍:     r : 读权限    w : 可写权限   X : 可执行权限.   所谓的9位权限标识位, 就是针对: 文件拥有者, 文件拥有组人员, 其他人员的访问权限的限制

返回值:成功返回信号集合句柄, 失败返回 -1 并且设置errno

  • semop

函数功能:对于信号量资源进行 P V 操作,改变信号量的值

参数分析:

  1. semid 之前semget返回的信号量集合句柄
  2. nsops:nsops参数指定要执行的操作个数,即是sops数组中元素的个数,semop对数组sops中的每个元素按照数组顺序依次执行操作,并且这个过程是原子操作。
  3. sops 结构体指针, 指向struct sembuf 数组首地址 结构体定义如下
    struct sembuf
    {
      unsigned short int sem_num;/* semaphore number */
      short int sem_op; /* semaphore operation */
      short int sem_flg;/* operation flag */
    };
    if (sem_op is positive integer)  
        add the val to semval
    if (sem_op is zero) 
        wait-for-zero" operation
    if (sem_op is less than zero) 
        add the val to semval (加负数, 相当于减)
  4. sem_flg 可选值是 IPC_NOWAIT, SEM_UNDO,IPC_NOWAIT指,无论信号量集操作是否成功,semop调用都立刻返回。并且设置errno = EAGAIN , SEM_UNDO含义是,当进程退出时,取消正在进行的semop操作      

返回值:成功返回 0, 失败返回 -1 并且设置 errno

  • semctl

函数功能:对于指定信号量IPC进行操作

参数分析:

  1. semid 之前semget返回的信号量集合句柄
  2. semnum 指定操作信号量在信号量集合中的编号, (The semaphores in a set are numbered starting at 0. 也就是说集合中的信号量编号从0开始. 
  3. cmd 对指定信号量执行的操作
  4. 有的命令需要传入第4个参数,这个参数类型由用户定义,但是,内核给出了它的固定形式
 union semun {
     int              val;    /* Value for SETVAL */
     struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
     unsigned short  *array;  /* Array for GETALL, SETALL */
     struct seminfo  *__buf;  /* Buffer for IPC_INFO
                             (Linux-specific) */
 };

cmd常用列举:

  1. SETVAL 将信号量的semval值设置为semun.val

  2. IPC_RMID 立即移除信号量集,唤醒所有等待信号量集的进程

返回值:成功返回 0, 失败返回 -1 并且设置 errno

实际案例:利用信号量实现一个简单的父子进程之间的同步等待, 通信, 也就是利用P V实现了跟锁一样的效果, 实现了父子进程同步打印, 不会出现乱入(汇编指令中间重入)问题, 保证原子性操作

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#define ERR_EXIT(m) \
    do { perror(m); exit(EXIT_FAILURE); } while (0);

//联合结构体


union semun {
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO*/
};


int initsem(key_t key) {
    int semid = -1;
    //创建一个信号量集合IPC, 其中包含一个信号量
    if (-1 == (semid = semget(key, 1, 0666 | IPC_CREAT))) {
        ERR_EXIT("semget");
    }
    //设置信号量val
    union semun sem_un;
    sem_un.val = 1;

    if (-1 == semctl(semid, 0, SETVAL, sem_un)) {
        ERR_EXIT("semctl");
    }

    return semid;
}

//销毁
void destorysem(int semid) {

    if (-1 == semctl(semid, 0, IPC_RMID)) {
        ERR_EXIT("semctl destorysem");
    }
}

void P(int semid) {
    //因为信号量集合中就只是一个
    //所以sembuf无需设置为vector
    struct sembuf op;//操作
    op.sem_num = 0;
    op.sem_op = -1;// - 1
    op.sem_flg = SEM_UNDO;
    if (-1 == semop(semid, &op, 1)) {
        ERR_EXIT("semop P");
    }
}

void V(int semid) {
    struct sembuf op;//操作
    op.sem_num = 0;
    op.sem_op = 1;// + 1
    op.sem_flg = SEM_UNDO;
    if (-1 == semop(semid, &op, 1)) {
        ERR_EXIT("semop V");
    }

}

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

    if (2 != argc) {
        fprintf(stderr, "usage: %s ", argv[0]);
        exit(EXIT_FAILURE);
    }

    //1.获取唯一标识key
    key_t key = ftok(argv[1], 'a');

    //2. 创建信号量集合
    int semid = initsem(key);
    int cnt = 10;
    pid_t pid = fork();
    if (pid == -1) {
        ERR_EXIT("fork");
    }

    if (pid == 0) {
        //son process
      while (cnt > 0) {
         // P(semid);
          printf("I am son process\n");
          sleep(1);
          //V(semid);
          cnt -= 1; 
      }

    } else {
      //fa process
      while (cnt > 0) { 
        //P(semid);
        printf("I am fa process\n");
        sleep(1);
        //V(semid);
        cnt -= 1;
      }
      waitpid(pid, NULL, 0);
      destorysem(semid);
    }


    return 0;
}

Linux IPC共享内存

共享内存是最快的IPC机制, 因为它不涉及任何的数据传输. 而是采取利用一块公共物理内存映射, 挂载到进程地址空间, 使得多个进程对于同一块物理内存可视的方法实现通信.

但是也存在一定的弊端, 就是无法支持同步, 我们就需要借助其他通信机制手段来同步进程对共享内存的访问

eg : 我 A 进程对于共享内存写入了数据, 需要跟 B 进程通信, 建立同步, 可以这个时候存在一个很大的问题, B 进行如何可以得知 A 进行已经写入了数据了.    此时我们就需要借助semaphore来实现同步.       ---   这一点很重要, 理解为何使用共享内存就需要配合其他通信机制来完成同步, 因为共享内存本身就不支持同步访问. 不同于信号量是本身支持原子操作的.

 

  • shmget

system V IPC进程间通信机制一网打尽_第5张图片

功能:申请共享内存

shmflag : 9位权限标志位:    我们通常使用  0x666  |  IPC_CREAT | IPC_EXCL

插一个权限介绍:     r : 读权限    w : 可写权限   X : 可执行权限.   所谓的9位权限标识位, 就是针对: 文件拥有者, 文件拥有组人员, 其他人员的访问权限的限制

  • shmat

功能:将物理内存连接,挂载,关联到进程地址空间中去

shmaddr :NULL 自动选取连接地址,         不为NULL, 人为指定到希望挂载的地址上

我们还是填写 NULL

Return Val : 返回一个ptr, 指向共享物理内存的首地址. 虚拟空间中的连接首地址

  • shmdt

 

功能:解除挂载, 将共享内存和当前进程进行脱离.

shmaddr :  之前 shmat 的 return val, 共享内存映射在虚拟地址空间上的首地址. 

Return Val:          success return 0     failure return -1

  • shmctl

功能:对于共享内存IPC进行操作

cmd:  IPC_RMID  删除共享内存

buf 传 NULL 即可

实际案例:利用共享内存实现 父子进程之间的 通信, 父进程向共享内存中写入数据, 子进程在写入时候同步打印写入的数据即可.

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define ERR_EXIT(m) \
    do { perror(m); exit(EXIT_FAILURE); } while (0);
#define SHMSIZE 1024

//联合结构体

union semun {
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO*/
};



int initsem(key_t key) {

	int semid = -1;

	if (-1 == (semid = semget(key, 1, 0666 | IPC_CREAT))) {
		ERR_EXIT("semid");
	}
	//设置信号量集合中编号为0号的信号量val 为 2
	union semun sem_un;
	sem_un.val = 1;

	if (-1 == semctl(semid, 0, SETVAL, sem_un)) {
		ERR_EXIT("semop set val");
	}

	return semid;
}

void destorysem(int semid) {
	if (-1 == semctl(semid, 0, IPC_RMID)) {
		ERR_EXIT("semctl destorysem");
	}
} 


void P(int semid) {
	struct sembuf op;
	op.sem_num = 0;
	op.sem_op = -1;
	op.sem_flg = SEM_UNDO;
	if (-1 == semop(semid, &op, 1)) {
		ERR_EXIT("semop P");
	}
}


void V(int semid) {
	struct sembuf op;
	op.sem_num = 0;
	op.sem_op = 1;
	op.sem_flg = SEM_UNDO;
	if (-1 == semop(semid, &op, 1)) {
		ERR_EXIT("semop V");
	}
} 


void waitforzero(int semid) {
	struct sembuf op;
	op.sem_num = 0;
	op.sem_op = 0;
	op.sem_flg = SEM_UNDO;
	if (-1 == semop(semid, &op, 1)) {
		ERR_EXIT("semop zero");
	}
} 


int main(int argc, char* argv[]) {
    
	if (2 != argc) {
		fprintf(stderr, "usage: %s ", argv[0]);
		exit(EXIT_FAILURE);
	}

	key_t key1 = ftok(argv[1], 'c');
	key_t key2 = ftok(argv[1], 'C');
	//创建信号量集合IPC
	int semid = initsem(key1);
	int shmid = -1;

	pid_t pid = fork();

	if (pid < 0) {
		ERR_EXIT("fork");
	}

	if (pid == 0) {
		//son process
		waitforzero(semid);

		if (-1 == (shmid = shmget(key2, SHMSIZE, 0666 | IPC_CREAT))) {
			ERR_EXIT("shmget");
		}

		void* p = shmat(shmid, NULL, 0);

        if ((void*)-1 == p)
        {
            ERR_EXIT("shmat");
        }

        char buf[SHMSIZE] = {0};
        memcpy(buf, p, SHMSIZE);
        printf("%s\n", buf);

        //卸载共享内存
        if (-1 == shmdt(p))
        {
            ERR_EXIT("shmdt in parent");
        }

		V(semid);//为父进程归还这个资源.


	} else {
		// fa process

		if (-1 == (shmid = shmget(key2, SHMSIZE, 0666 | IPC_CREAT))) {
			ERR_EXIT("shmget");
		}

		void* p;
		p = shmat(shmid, NULL, 0);

 		if ((void*)-1 == p)
        {
            ERR_EXIT("shmat");
        }


		char buff[SHMSIZE];
		printf("请输入要传输给子进程的数据\n");
		scanf("%s", buff);
		memcpy(p, buff, strlen(buff));

		P(semid);

		waitpid(pid, NULL, 0);
		//通信完成, 销毁所有IPC
		destorysem(semid);
		//卸载内存, 销毁shmid共享内存
		if (-1 == shmdt(p)) {
			ERR_EXIT("shmdt");
		}

		if (-1 == shmctl(shmid, IPC_RMID, NULL)) {
			ERR_EXIT("shmctl destoryshm");
		}

	}

	return 0;
}

你可能感兴趣的:(计算机操作系统,面试,Linux,通信,进程)