Linux进程通信

Linux进程通信

​ 由于进程采用的是虚拟空间+用户态/内核态机制,所以就导致进程与进程之间、进程与内核之间是互相独立的,如果想让多个进程协同工作解决复杂问题,就需要进程之间进行通信。

一、简单的进程间通信

命令行参数:

​ 情况1:在终端执行程序时,给子进程传递命令行参数。

​ 情况2:使用vfork+exec创建进程时,给子进程传递命令行参数。

环境变量表:

​ 情况1:使用fork创建子进程时,子进程会拷贝一份父进程的环境变量表。

​ 情况2:使用vfork+exec创建进程时,给子进程传递一份父进程的环境变量表,子进程拿到环境表后会进行拷贝。

​ 只能在父子进程之间,父进程创建子进程时使用,只能由父进行传递给子进程,单向通信,并且只能传递一些简单的字符串数据。

信号:

​ 情况1:使用kill向指定的进程发送信号进行通信。

​ 情况2:使用sigqueue向指定的进程发送信号,也可以附带一些简单的数据。

​ 可以在任意进程之间进行通信,但也只能是互相告知某某事件发生了,即使能使用sigqueue带一些数据,但只能传递一个int类型整数,只有通过fork创建的子进程才能传递内存地址。

文件:

​ 情况1:使用文件+信号,让任意两个进程之间传递大量数据,但要各自控制好位置指针,协调好读写时间。

​ 情况2:使用文件+文件锁,也可以让任意两个进程之间传递大量数据,但文件锁的操作比较麻烦,也有可能陷入死锁的情况。

​ 单纯的使用文件进行通信,无法协调读取和写入的时间,空间造成文件内的数据混乱,所以必须配合使用信号或文件锁。

二、传统的进程间通信

​ 管道是UNIX系统最古老的进程间通信方式,是一种特殊的文件读写机制。

​ 当进程从管道文件读取数据时,如果管道中没有数据则进程会进入阻塞状态,直到读取到数据才返回,所以就不需要使用信号、文件锁协调读写时间。

​ 管道中的数据一旦读取完毕就会消失,不需要管理文件位置指针,所以管道文件通信比直接使用文件进行通信要方便很多。

1、有名(命名)管道:

​ 在文件系统中创建出一个实体的管道文件,然后再使用系统I/O相关API进行相关操作。【半双工】

使用函数创建:
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个管道文件
pathname:管道文件的路径
mode:管道文件的权限
使用命令创建:
 mkfifo <filo>
 # echo hello > fifo    
 # cat fifo
2、匿名管道:

​ 只在内核中创建出管道文件对象并返回该对象的文件描述符,然后再使用系统I/O相关API进行相关操作。匿名管道不会在文件系统中显示,在创建时不需要提供路径,也不占用磁盘空间,而只使用内核空间的内存通信,关闭文件描述符后自动回收。

注意:只能用于父子进程(fork创建的)

#include <unistd.h>
int pipe (int pipefd[2]);
功能:创建出管道文件对象并返回该对象的文件描述符
pipefd:存储文件描述符的数组,其中pipefd[0]用于读,pipefd[1]用于写。
返回值:成功返回0,失败返回-1。
使用步骤:
	1、调用该函数在内核中创建管道文件,并通过其输出参数,获得分别用于读和写的两个文件描述符
	2、调用fork函数,创建子进程,子进程会拷贝pipefd数组。
	3、写数据的进程关闭读端(pipefd[0]),读数据的进程关闭写端(pipefd[1])
	4、父子进程传输数据
	5、父子进程分别关闭自己的文件描述符

三、XSI机制的进程间通信

IPC对象:

​ 它用于进程间通信的XSI-IPC内核对象,就是像匿名管道、文件内核对象一样,使用XSI方式进行进程间通信时,系统会在内核中创建一个XSI-IPC内核对象,让需要通信的进程共同访问。

​ XSI-IPC只存在于内核空间,不在文件系统显示,通过IPC键进行创建、获取。

IPC键值:

​ 用于创建XSI-IPC内核对象的凭证,是一个无符号整数,相当于IPC对象的名字,类似于文件的文件名。
​ 在创建XSI-IPC内核对象可以由创建者自定义,类似于给文件取名,但所有XSI-IPC内核对象都在同一个作用域下,所以有重名的风险,所以操作系统提供一个自动生成IPC键值的API。

#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);
功能:自动生成一个IPC键值
pathname:当前项目的路径,必须一个真实存在的路径,它靠的是路径的位置,而不是字符串。
proj_id:当前项目的编号
注意:只要提供相同的路径和项目编号,就可以获得相同的IPC键值。
IPC描述符:

​ 类似于文件描述符,是一个非负整数,它是IPC内核对象的给用户空间访问凭证,然后这个对象会以整数的形式提供给各个进程。

显示IPC对象命令:
ipcs -m - 显示共享内存(m: memory)
ipcs -q - 显示消息队列(q: queue)
ipcs -s - 显示信号量(s: semphore)
ipcs -a - 显示所有IPC对象(a: all)
删除IPC对象命令:
ipcrm -m ID - 删除共享内存
ipcrm -q ID - 删除消息队列
ipcrm -s ID - 删除信号量
1、共享内存:
基本原理:

​ 在内核中开辟一内存,可以让其它进程的虚拟地址与它进行映射,这样就达到多个进程共享一块内存的目的,当一个进程向这块内存写数据时,其它进程也都可以读取到,这样就达到了通信的目的。

​ 注意:这种通信方式不存在数据的复制,是最快的进程间通信方式。

使用方式:

​ 当一个进程向共享内存写入数据后,内核不会通知其它进程,进程从共享内存读取数据时,无法分辨是否是通信进程新写入的数据,所以有以下三种固定的使用方式。

​ 读写:一个进程只负责写入数据,另一个进程只负责读取数据,只要负责读的进程使用的是最新即可,这是单向通信的模式。

​ 轮询:配合闹钟或定时器,每隔一段时间读取一个。

​ 中断:配合信号,进程只要往里面写入数据就给对方发送一个信号,其它进程如果读取了数据也给对方发送一个已读取的信号。

相关API:
#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
功能:创建/获取一块内核中共享内存
key:IPC键值,相当于文件名
size:共享内存的字节数,建议取内存页字节数的整数倍,默认一页内存4096字节。
    若希望创建共享内存,则必需指定size参数,若只为获取已有的共享内存,则size参数可取0。
shmflg:
    0			- 表示该参数无效,获取共享内存,size的值也无效。
    IPC_CREAT   - 创建共享内存
    IPC_EXCL    - 互斥,如果共享内存已存在则创建失败
    mode 		- 共享内存的权限,创建共享内存时一定要加
返回值:成功返回共享内存描述符,失败返回-1void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:加载共享内存(把进程中的虚拟地址与内核中的共享内存建立映射关系)
shmid:IPC描述符
shmaddr:虚拟地址,也可以为NULL,操作系统会自动分配。
shmflg:
      0          - 以读写方式使用共享内存。
      SHM_RDONLY - 以只读方式使用共享内存。
      SHM_RND    - 只在shmaddr参数非NULL时起作用,对shmaddr向下取内存页的整数倍,作为映射地址。
返回值:成功返回映射后的虚拟地址,并且内核将该共享内存的加载计数加1,失败返回-1int shmdt(const void *shmaddr);
功能:卸载共享内存,也就是取消映射

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:销毁共享内存、获取共享内存的属性、设置共享内存的属性
shmid:IPC描述符
cmd:
    IPC_STAT    获取共享内存的属性
    IPC_SET     设置共享内存的属性,uid,gid,mode可以设置。
    IPC_RMID    并非真正删除共享内存,只是做一个删除标记,禁止其被继续加载,但已有加载依然保留,只有当该共享内存的加载计数为0时,才真正被删除。
    
struct shmid_ds {
    struct ipc_perm shm_perm;	// 所有者及权限
    size_t shm_segsz;   		// 共享内存的大小,以字节为单位
    time_t shm_atime;   		// 最后加载时间
    time_t shm_dtime;   		// 最后卸载时间
    time_t shm_ctime;   		// 最后修改时间
    pid_t shm_cpid;     		// 创建者的PID
    pid_t shm_lpid;     		// 最后加载/卸载的者PID
    shmatt_t shm_nattch;  		// 加载计数器
};

struct ipc_perm 
{
   key_t          __key; // 键值
   uid_t          uid;   // 有效属主ID
   gid_t          gid;   // 有效属组ID
   uid_t          cuid;  // 有效创建者ID
   gid_t          cgid;  // 有效创建组ID
   unsigned short mode;  // 权限字
   unsigned short __seq; // 序列号
};
2、消息队列:
基本原理:

​ 是由系统内核维护的一个链式队列,每个一条消息由类型、数据、数据长度组成。

​ 和管道类似,但它可以双向通信,还可以按类型接收消息,而不是顺序。

相关API:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);
功能:以key参数为键值 创建或获取消息队列
msgflg:
    0         - 获取,不存在即失败。
    IPC_CREAT - 创建,不存在即创建,已存在即获取
    IPC_EXCL  - 排斥,已存在即失败。
    mode 	  - 消息队列的权限
返回值:成功返回消息队列标识符,失败返回-1

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息队列发送数据
    
msgp:向一个包含消息类型和消息内容的内存块,以下是内存块的格式
    struct msgbuf
    {
		long mtype;		// 数据的长度,注意不包括消息类型。
		char mtext[n];	// 消息内容,也可以使用柔性数组
	};
msgsz:消息内容的字节数,不包含消息类型
msgflg:
    0 当消息队列中没有足够的空闲空间时,此函数会阻塞。
    IPC_NOWAIT 当消息队列中没有足够的空闲空间时,此函数不会阻塞,而是返回-1ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:从消息队列接收数据
msgp:消息内存块的首地址,消息由消息类型+消息数据组成,也叫消息数据缓冲区
msgsz:消息数据缓冲区的字节数,如果实际消息的字符数大于msgsz,则立即返回-1
msgtyp:
    >0 - 要接收的消息类型
    =0 - 返回消息队列中的第一条消息。
    <0 - 返回消息队列中类型小于等于msgtyp的绝对值的消息,如果有多个,则返回类型最小者。
msgflg:
    MSG_EXCEPT 返回消息队列中第一个类型不为msgtyp的消息,此时应 msgtyp>0
    IPC_NOWAIT 如果没有满足条件的消息,立即返回
    MSG_NOERROR 只读取该消息的前msgsz字节,如果有剩余则丢弃
返回值:返回接收到的消息的字节数

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:销毁消息队列、获取消息队列的属性、设置消息队列的属性
cmd取值:
	IPC_STAT - 获取消息队列的属性,通过buf参数输出
	IPC_SET	 - 设置消息队列的属性,通过buf参数输入,仅以下四个属性可设置:
         msqid_ds::msg_perm.uid
         msqid_ds::msg_perm.gid、
         msqid_ds::msg_perm.mode
         msqid_ds::msg_qbytes
	IPC_RMID - 立即删除消息队列同,此时所有阻塞在对该消息队列的,msgsnd和msgrcv函数调用,都会立即返回失败。

struct msqid_ds {
    struct ipc_perm msg_perm;     // 权限信息
    time_t          msg_stime;    // 随后发送时间
    time_t          msg_rtime;    // 最后接收时间
    time_t          msg_ctime;    // 最后改变时间
    unsigned long   __msg_cbytes; // 消息队列中的字节数
    msgqnum_t       msg_qnum;     // 消息队列中的消息数
    msglen_t        msg_qbytes;   // 消息队列能容纳的最大字节数
    pid_t           msg_lspid;    // 最后发送进程PID
    pid_t           msg_lrpid;    // 最后接收进程PID
};

struct ipc_perm {
    key_t          __key; // 键值
    uid_t          uid;   // 有效属主ID
    gid_t          gid;   // 有效属组ID
    uid_t          cuid;  // 有效创建者ID
    gid_t          cgid;  // 有效创建组ID
    unsigned short mode;  // 权限字
    unsigned short __seq; // 序列号
};
3、信号量
基本原理:

​ 所谓信号量就是内核中维护的一个全局变量,被当作计数器,用于限制多个进程对有限共享资源的访问。

​ 1、假定操作系统有n个共享资源,则需要把信号量的值设置为n。

​ 2、有m个进程需要以独占方式使用k个共享资源,且 m*k > n。

​ 3、每个进程在使用共享资源前要尝试执行 信号量-k 操作。

​ 4、如果 信号量-k >= 0,说明剩余的共享资源数量够用,当进程获得该资源后,需执行信号量 -= k,然后尽情的使用共享资源,当不再使用该资源时,需执行信号量 += k。

​ 5、如果信号量 -k < 0,说明剩余的共享资源数量不够用,进程就会阻塞等待该资源,直到信号量-k >= 0,进程被唤醒,执行步骤4;

注意:因为资源的互斥访问,所以很少用到。

相关API:
int semget (key_t key, int nsems, int semflg);
功能:以key参数为键值 创建或获取信号量集
nsems:参数表示集合中的信号量数,或获取已有的信号量集合则nsems取0
semflg取值:
      0         - 获取,不存在即失败
      IPC_CREAT - 创建,不存在即创建,已存在则获取
      IPC_EXCL  - 排斥,已存在即失败
      mode 	    - 消息队列的权限
返回值:成功返回信号量集合标识,失败返回-1int semop (int semid, struct sembuf* sops,unsigned nsops);
功能:对semid参数所标识的信号量集合
sops:struct sembuf结构数组
nsops:结构数组的长度
struct sembuf {
    unsigned short sem_num; // 信号量下标
    short          sem_op;  // 操作数
		// 若sem_op大于0,则将其加到第sem_num个信号量上,以表示对资源的释放;
		// 若sem_op小于0,则将其加到第sem_num个信号量上,以表示对资源的获取;
    short          sem_flg; // 操作标记
    	// 0 若第sem_num个信号量的计数值不够减(信号量不能为负),则进程会阻塞,直到该信号量够减为止,以表示对资源的等待;
    	// IPC_NOWAIT,则当第sem_num个信号量的计数值不够减时,此函数不会阻塞,而是返回-1,以便在等待资源的同时还可做其它处理;
      	// 若sem_op等0,则直到第sem_num个信号量的计数值为0时才返回,除非sem_flg包含IPC_NOWAIT位。
};

int semctl (int semid, int semnum, int cmd);
int semctl (int semid, int semnum, int cmd,union semun arg);
功能:获取信号量属性、设置信号量属性、删除信号量
cmd取值:
	IPC_STAT - 获取信号量集合的属性,通过arg.buf输出。
    IPC_SET	 - 设置信号量集合的属性,通过arg.buf输入,仅以下三个属性可设置:
    	semid_ds::sem_perm.uid
        semid_ds::sem_perm.gid
    	semid_ds::sem_perm.mode
	IPC_RMID立即删除信号量集合。
         此时所有阻塞在对该信号量集合的,semop函数调用,都会立即返回失败。
	GETALL	 - 获取信号量集合中每个信号量的计数值,通过arg.array输出。
	SETALL	 - 设置信号量集合中每个信号量的计数值,通过arg.array输入。
	GETVAL	 - 获取信号量集合中,第semnum个信号量的计数值,通过返回值输出。
	SETVAL	 - 设置信号量集合中,第semnum个信号量的计数值,通过arg.val输入。
semnum:
    只有针对信号量集合中具体某个信号量的操作,才会使用semnum参数。
    针对整个信号量集合的操作,会忽略semnum参数。

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
};

struct semid_ds {
    struct ipc_perm sem_perm;  // Ownership and permissions
    time_t          sem_otime; // Last semop time
    time_t          sem_ctime; // Last change time
    unsigned short  sem_nsems; // No. of semaphores in set
};

struct ipc_perm {
    key_t          __key; // 键值
    uid_t          uid;   // 有效属主ID
    gid_t          gid;   // 有效属组ID
    uid_t          cuid;  // 有效创建者ID
    gid_t          cgid;  // 有效创建组ID
    unsigned short mode;  // 权限字
    unsigned short __seq; // 序列号
};

struct  seminfo {
	int semmap; /* Number of entries in semaphoremap; unused within kernel */
	int semmni; /* Maximum number of semaphore sets */
	int semmns; /* Maximum number of semaphores in all semaphore sets */
	int semmnu; /* System-wide maximum number of undo structures; unused within kernel */
	int semmsl; /* Maximum number of semaphores in a set */
	int semopm; /* Maximum number of operations for semop(2) */
	int semume; /* Maximum number of undo entries per process; unused within kernel */
	int semusz; /* Size of struct sem_undo */
	int semvmx; /* Maximum semaphore value */
	int semaem; /* Max. value that can be recorded for semaphore adjustment (SEM_UNDO) */
};

四、Socket通信

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值