【1】进程间通信概述
每一个进程虽然独立,但也需要让不同的进程实现数据的传输、还有信号通知
通信方式:
传统的进程间通信:
无名管道、有名管道 --> 数据传输
信号 -》 异步通知
系统5(System V)通信方式:
共享内存、消息队列 --》 数据传输
信号量集 --》 同步和互斥
网络套接字通信
不同的电脑不同的进程之间实现通信
不同的进程间如何实现通信?
在操作系统的所运行的那段空间(内核空间3-4g),开辟一款缓冲区
【2】无名管道
是操作系统在内核空间所开辟的一块内存区域。内存区域有一定的大小,大小为64K
无名管道:就是在对当前文件系统找不到对应的名字,对应的操作函数会给你返回两个描述符,一个代表管道的读操作,一个代表管道的写操作
特点:只能在父子进程或者兄弟进程或者子孙进程之间实现通信,数据传输方向的单向,是一个半双工的通信方式。
int pipe(int pipdfd[2]);
功能:在内核空间开间一个内存区域用来实现进程间通信
参数:
pipefd[0] 代表管道的读端,也就是用来从pipefd[0]读取数据
pipefd[1] 代表管道的写端,也就是用来从pipefd[1]写入数据
返回值:
成功 0
失败 -1
当管道中没有数据的时候,执行读操作,read函数会产生阻塞
如果管道有 数据则依照读取的数据个数进行read操作
往管道中写数据,如果写满管道64k。则write会产生阻塞,只有读操作读取数据
管道的剩余空间大于4k时,write才能继续写数据
【3】有名管道
有名管道:可以在文件系统下找到某个文件,对文件对打开,执行相应的读写操作,内部实现与无名管道相同,文件中仅保存inode编号,指向内存中的区域。
int mkfifo(const char *pathname, mode_t mode);
功能:创建有名管道,用来实现不同的进程间通信
参数:
pathname: 指定管道文件的名字,文件类型是p,以后通过打开管道文件
往有名管道中写入数据、或者读出数据
mode: 文件权限(mode & ~umask)
返回值:
成功 0
失败 -1
有名管道与无名管道的使用方式相同,只需使用open系统调用获取文件描述符即可。
【4】信号
实现进程跟进程之间的通知,依据信号通知,具体执行什么操作,决定于信号的默认操作
但是信号有三种相应(操作)方式:
(1)默认,执行系统设定的操作方式
(2)忽略,进程接收到信号之后,不做任何操作
(3)自定义操作:需要自己在进程中定义一个信号处理函数,执行对应信号操作
kill -l
常用默认信号的操作方式:
2) SIGINT ctrl+c 结束进程
3) SIGQUIT ctrl+\ 结束进程
9) SIGKILL 结束进程
10) SIGUSR1
12) SIGUSR2 用户自定义信号 结束进程
13) SIGPIPE 管道破裂,结束进程
14) SIGALRM 闹钟信号,结束进程
17) SIGCHLD 子进程状态改变,会向父进程发送这个信号
18) SIGCONT 让暂停的进程恢复运行
20) SIGTSTP 暂停进程
19) SIGSTOP 暂停进程
【注意】9 和 19这两个信号的默认操作方式不能被改变
kill(pid_t pid, int sig) --> kill(1000, SIGKILL)
int raise(int sig);
功能: 都是用来发送信号
pid 进程的pid号
sig 信号种类中的一种
成功 0
unsigned int seconds alarm(unsigned int seconds)
功能: 用来设置闹钟,本身不具有阻塞作用
参数 senonds 设置闹钟秒数
返回值:
之前没有设置闹钟,返回值是0
之前有设置过闹钟,则返回之前闹钟剩余的时间值
typedef void (*sighandler_t)(int); --> sighandler_t
typedef别名重定义,sighandler_t表示函数指针类型
sighandler_t signal(int signum, sighandler_t handler);
功能:信号处理函数,首先需要向内核注册信号signum,注册完之后,signal本身不具有阻塞作用,会继续往后执行
handler 用户自定操作,也就是用户需要定义一个函数 void handler(int),这是函数的类型,形参表示的是内核向你这个进程发送的信号是什么?
void handler(int signo)
{
if(signo == SIGKILL)
{
处理操作,实现功能,需要用户自己定义
}
if(signo == SIGINT)
{
处理操作
}
}
【5】System V 进程间通信对象
共享内存、消息队列、信号量集
也是在内核当中去创建,创建好之后,一直保留系统当中,
如果不用操作内核对象了,需要手动的删除
a、【删除】内核对象命令
ipcrm [ -M key | -m id | -Q key | -q id | -S key | -s id ]
举例:ipcs -m id --》查看共享内存
b、在程序中【查看】内核对象
int system(const char *command);
参数 :
conmand shell命令
举例:system(“ls -l”)
内核对象操作流程:
1、创建key值
2、创建内核对象
3、操作内核对象,具体操作,依据内核对象
4、删除内核对象
key: 让进城找到内核对象。(共享内存、消息队列、信号量集)
IPC_PRIVATE 当前进程创建,只允许当前进程使用 0x00000000
ftok 当一个进程创建好之后,其他的进程可以通过这个key值,找到内核对象
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:创建一个key值,让不同的进程找到内核对象
参数:
pathname 文件名(必须存在,并且可以访问),取得是文件的inode节点号
proj_id 整形变量,值得范围是(1~255)
返回值:
成功 key
失败 -1
【6】共享内存
共享内存:是在内核空间中的一块区域,需要设定,大小也有用户自己定义,以字节为单位进行分配,并且是连续的地址空间,在所有通信当中,效率最高,可以直接方位内存空间的地址
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能: 创建共享内存
参数:
key 让不同的进程找到内核对象
size 共享内存的大小是多少? 以字节为单位
shmflg
IPC_CREAT 表示要创建共享内存
IPC_EXCL 如果已经创建,则会判断是否重复创建
mode_flags 指定9位有效权限位 0666
举例: IPC_CREAT | IPC_EXCL | 0666
返回值:
成功 shmid,用来对内和对象(共享内存)操作的变量
失败 -1
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能: 将内核空间的内存地址映射到调用的用户空间
参数:
shmid 通过shmget创建或者打开的共享内存
shmaddr NULL 表示系统自动选择合适的未使用的是给给用户空间
shmflg
0 表示可读可写
SHM_RDONLY 表示只读
返回值:
成功 映射后的地址,需要进行强制类型转换
失败 (void *)-1
int shmdt(const void *shmaddr);
功能:解除映射
参数:
shmaddr 映射后的地址(指针变量)
返回值:
成功 0
失败 -1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:共享内存控制函数
参数:
shmid 表示共享内存
cmd
IPC_RMID IPC_STAT
buf 信息结构体 NULL struct shmid_ds(定义变量取地址)
返回值:
成功 0
失败 -1
【注意】
在访问共享内存时,有可能出现多个进程共同访问的现象,造成数据的混乱或者说是不一致,需要使用信号量或者互斥锁来保护共享资源
【7】信号量集
信号量集:信号量的集合,代表一类资源,每一类资源都有数量,需要用户自己设定
创建信号量的数目,需要用户事先指定,并初始化设置每一个信号量的数量值同样也需要进行P(申请)V(释放)操作。
int semget(key_t key, int nsems, int semflg);
功能:创建信号量集合,里面可以包含多个信号量,具体数量自己指定
参数:
key 为了方便其他进程找到信号量集合
nsems 指定信号量集合中信号量的数目是多少个?
1 集合有一个信号量 0
2 集合有两个信号量 0 1
semflg
IPC_CREAT 创建信号量
IPC_EXCL 防止重复创建
0666 权限
返回值:
成功 操作标识符(操作信号量用的)
失败 -1
int semctl(int semid, int semnum, int cmd, ...);
功能:信号量的控制操作函数
参数:
semid 代表你要操作的信号量集合
sennum 表示你要操作第几个信号量
cmd
SETVAL 用来设置每一个信号量的数量是多少个?
IPC_RMID 用来删除信号量集合
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) */
};
int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:信号量的pv操作
参数:
semid 操作那个信号量集合
sops 如何对信号量进行操作
unsigned short sem_num; 对第几个信号量进行操作/* semaphore number */
short sem_op; 执行什么操作P(正数) V(负数)/* semaphore operation */
short sem_flg; 设置阻塞还是非阻塞方式 0阻塞 IPC_NOWAIT非阻塞/* operation flags */
nsops 表示连续操作几个信号量
返回值:
成功 0
失败 -1
【8】消息队列
(1)消息队列机制
特点:
1、先进先出
2、按照类型发送、读取消息
使用ipcs -q查看系统中的消息
(2)如何创建消息队列
int msgget(key_t key, int msgflg);
功能:创建或者打开消息队列
参数:
key:让不同的进程找到同一个消息队列
msgflg:
IPC_CREAT 创建
IPC_EXCL 防止重复创建
0666 权限
msqid:消息队列标识符
(3)发送消息/接收消息
结构体1:
struct msgbuf
{
long mtype; /* message type, must be > 0 */消息类型
char mtext[1]; /* message data */消息正文
};
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:发送消息到消息队列
参数:
msgid: 消息队列标示符 (用于操作消息队列发送或者接收消息)
msgp:首先定义一个消息队列的结构体,类型如上所示结构体1
msgsz:消息队列正文(text)的大小
msgflg:设置为阻塞的方式0 非阻塞的方式 IPC_NOWAIT
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);
功能:从队列接收消息
参数:
msgid: 消息队列标示符 (用于操作消息队列发送或者接收消息)
msgp:首先定义一个消息队列的结构体,类型如上所示结构体1
msgsz:消息队列正文(text)的大小
msgtyp:消息的类型(必须是大于0的整数)
msgflg:设置为阻塞的方式0 非阻塞的方式 IPC_NOWAIT
(4)删除消息队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:操作消息队列
参数:
msgid: 消息队列标示符 (用于操作消息队列发送或者接收消息)
cmd:操作消息队列的命令
IPC_STAT 获取消息队列属性信息的命令,需要定义一个struct msqid_ds结构体
IPC_RMID 删除消息队列的命令