用户空间进程间是不可能通信的(因为两个进程间是独立的两个空间),通过linux内核(内核空间对象)来进行通信。
线程:
一个进程中的不同线程间可以通信,可以在用户空间通信,通过全局变量通信。
哪几种通信方式?(内核对象来划分的)
管道通信:无名管道、有名管道(文件系统中有名)
信号通信:信号(通知)通信包括:信号的发送、信号的接收和信号的处理。
IPC(Inter-Process Communication)通信:共享内存、消息队列和信号灯
以上都是同一内核下的通信方式
Socdet通信:存在于一个网络中两个进程间的通信(两个Linux内核)。
思想:每一种通信方式都是基于文件IO的思想。
open:创建或打开进程通信对象,函数形式不一样,有的是有多个函数完成。
write:向进程通信对象中写入内容。函数形式可能不一样。
read:从进程通信对象中读取内容,函数形式可能不一样。
close:关闭或删除进程通信对象,形式可能不一样。
各种内 核对象:
无名管道:在文件系统中无文件名称。
原理:管道其实是由队列来实现的。
由pipe函数创建管道,由系统调用。须包含头文件unistd.h
int pipe(int fd[2])
参数:有一个读端fd[0]和一个写端fd[1],这个规定不能变
成功返回0,失败返回-1.
注:
管道里面的东西,读完了就删除了。
如果管道中没有东西可读,就会读阻塞。也存在写阻塞,缓存大小5456
进程结束,管道就不存在了。
缺点:不能实现非父子进程间的通信。
有名管道:为了解决非父子进间的通信的问题。
注:linux七种文件类型
普通文件 -
目录文件 d
链接文件(特指软链接文件) l
管道文件 p
int mkfifo(const char *filename,mode_t mode);//创建管道文件
参数:管道文件名,权限,仍然和unmask有关系。
成功返回0,失败-1.
有名只是文件系统中存一个文件节点,inode号
管道文件、套接字、字符设备、块设备只有inode号不占磁盘空间。普通文件和符号链接文件、目录文件不仅有inode号还占磁盘空间。
mkfifo只创建文件节点号,没有在内核中创建管道,只有通过open函数打开这个文件时才会在内核空间创建管道。
2019-11-28信号通信
信号在内核中已经存在的,在linux中使用kill -l可以查看内核所能发的信号。共64种。
信号的通信框架:
信号的发送(发送信号的进程)kill raise alarm
信号的接收(接收信号的进程):pause sleep while(1)
信号的处理(接收信号进程)signal
信号发送:头文件 #include <signal.h> #include<sys/types.h>
原型:int keill(pid_t pid ,int sig)
注:
pid 正数代表:要接收信号的进程号
0信号被发送到所有和pid 进各一在同一个进各一组的进程
-1信号发送到所有在进程表中的进程。
sig: 发送什么信号 (64种其中之 一)
正确返回0,出错-1。
raise发信号给自己 == kill(getpid(),sig)
所需要头文件 #include <signal.h>
#include <sys/types.h>
int raise(int sig);成功返回0出错-1.
alarm闹钟信号:也是一种只发给自己的函数,当前的进程
1、只会发送SIGALARM信号,收到信号后终止当前的进程。
2、会让内核定时一段时间后发送信号,raise会立即执行。
所需要头文件 #include <nuistd.h>
unsigned int alarm(unsigned int seconds)
参数:指定秒数
成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟的剩余时间,否则返回0
出错-1.
信号的接收:接收信号进程的条件:想接收,就不要结束
pause:进程状态为S也就是sleep状态
头文件:#include <unistd.h>
int pause(void)
成功0,出错-1
2019-12-03
信号处理:自己处理信号的方法告诉内核,这样你的进程收到这个信号后就会采用你自己的处理方式。
所需要头文件#include <signal.h>
void (*signal(int signum,void(*handler)(int)))(int)
signalnum:指定信号
handler: SIG_IGN:忽略该信号
SIG_DFL:采用系统默认方式处理信号
自定义的信号处理函数指针
成功:设置之前的信号处理方式,出错-1.
signal第一个参数是告诉内核处理哪个信号
第二个参数,怎么处理这个信号。返回值是一个函数指针。
2019年12月10日
IPC通信,不同于以上的通信方式。
使用的基本流程
通过get系统调用创建或打开一个IPC对象
给定一个整数key,get调用会返回一个整数标识,即IPC标识符
通过这个标识符来引用IPC对象并进行各种操作
通过ctl系统调用获或设置IPC对象的属性、或者删除一个对象
IPC对象具有的权限定义在ipc.h头文件中
下表为system V 各个IPC对象之间的差异
接口 | 消息队列 | 信号量 | 共享内存 |
头文件 | <sys/msg.h> | <sys/sem.h> | <sys/shm.h> |
关联数据结构 | msqid_ds | semid_ds | shmid_ds |
创建/打开对象 | msgget() | semget() | shmget()+shmat() |
关闭对象 | (无) | (无) | shmdt() |
控制操作 | msgctl() | semctl() | shmctl() |
执行IPC | msgsnd()—写入消息 | semop()—测试/调整信号量 | 访问共享区中的内存 |
还是要以文件IO的思想来学习。
1、创建或打开共享内存(注:共享内存什么样???类似于创建一个数组或用malloc申请的空间)
所需要的头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
函数原型:int shmget(key_t key,int size,int shmflg)
key值:IPC_PRIVATE或ftok的返回值
size:共享内存区大小
shmflg:同open函数的权限位,也可以用8进制表示法。
返回值:共享内存段标识符-ID-文件描述符。出错-1.
查看IPC对象:ipcs -m
删除IPC对象 :ipcrm -m + id
返回值:共享内存段标识符 IPC的ID号。
ftok:创建key值
char ftok(const char *path,char key)
参数:第一个参数是文件路径和文件名
第二个参数:一个字符
正确返回一个key值,出错-1.
与IPC_PRIVATE不同,类似于有名与无名管道,IPC_PRIVATE可以实现有亲缘关系进程间通信。每次的key都是0.
用ftok创建的key可以实现无亲缘关系进程间通信。每次创建的key值都不一样。
共享内存读写操作:特点是使用地址映射的方式,到用户空间地址,这样很方便的进行读写操作。可以使用fgets,printf等用户读写函数操作共享内存。
void *shmat(int shmid,const void *shmaddr,int shmflg);
第一个参数:ID号,映射共享内存的哪一块。
第二个参数:映射到的地址,NULL为系统自动完成的映射。
第三个参数:shmflg: SHM_RDONLY共享内存只读
默认是0,表示共享内存可读写
成功:返回映射后的地址。失败:NULL。
shmdt:将进程(用户空间)里的地址映射删除
int shmdt(const void *shmaddr);
参数:shmaddr共享内存映射后的地址
成功:0失败-1.
共享内存的特点:
1、共享内存创建后,一直存在于内核中,直到被删除或系统关闭。
2、共享内存和管道不一样,读取后依然在内存中。
shmctl:删除共享内存对象(内核中的共享内存)
int shmctl(int shmid,int cmd,struct shmid_ds *buf)
shmid:要操作的共享内存标识符
cmd:IPC_STAT(获取对象属性) --实现了ipc -m
IPC_SET(设置对象属性)
IPC_RMID(删除对象) --实现了ipcrm -m命令
buf:指定IPC_STAT/IPC_SET时用以保存/设置属性
成功0出错-1.
2019-12-11消息队列
与管道队列的不同:是 一种链式队列,每个消息可以不同。而管道里的队列是一种顺序队列。
1、消息队列的创建或打开函数
所需要头文件:#include <sys/types.h>
#include <sys/pic.h>
#include <sys/msg.h>
int msgget(key_t key,int flag);
key:和消息队列关联的key值
flag:消息队列的访问权限
成功:消息队列的ID 出错:-1.
2、消息队列的删除
所需要头文件 #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msgqid,int cmd,struct msqid_ds *buf);
msqid:消息队列的队列ID
cmd: IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中
IPC_SET:设置消息队列的属性,这个值取自buf 参数。
IPC_RMID:从系统中删除消息队列。
buf :消息队列的缓冲区
成功0出错-1.
3、消息队列的写入
所需要头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msgid,xonst void #msgp,size_t size,int flag);
msgid:消息队列的ID
msgp:指向消息的指针,学用的消息结构msgbuf如下:
struct msgbuf
{
long mthype;//消息类型
char mtext[N]; //消息正文
};
size:发送的消息正文的字节数
flag: IPC_NOWAIT 消息没有发送完成函数也会立即返回。
0:直到发送完成函数才返回。
成功0出错-1.
4、消息队列的读函数:msgrcv
所需要头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv (int msgid, void *msgp; size_t size , long msgtype, int flag);
msgid:消息队列的ID
msgp:接收消息的缓冲区
size:要接收的消息的字节数
msgtype:0:接收消息队列中第一个消息。
大于0:接收消息队列中第一个类型为msgtyp的消息。
小于0:接收消息队列中类型值不大于msgtyp的绝对值且类型又最小的消息。
flag:0:若无消息函数会一直阻塞。
IPC_NOWAIT:若没有消息,进程会立即返回。
ENOMSG
成功:接收到消息的长度。
出错-1.
信号灯:
与posix信号量的区别
信号量指的是单个信号量,而信号灯是信号量的集合
所有针对信号灯的操作都是对一个集合的操作
所需要头文件
1、创建或打开一个信号灯
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key,int msems,int semflg);
key:和信号灯集关联的key值。
msems:信号灯集中包含的信号灯数目
semflg:信号灯集的访问权限
成功:信号灯集ID 出错-1。
2、删除一个信号灯:
所需要头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...union semnu ar(不是地址));
semid:信号民灯集ID
semnum:要修改的信号灯编号
cmd: GETVAL:获取信号灯的值
SETVAL:设置信号类的值
注:信号类的初始化,类似sem_init;
union semun
{
int val;//SETVAL:设置信号灯的值
struct semid_ds *buf;//IPC_STAT(获取对象属性)IPC_SET(设置对象属性)值
unsigned short *array;
struct seminfo *__buf;
}
IPC_RMID:从系统中删除信号灯集合
第四个参数可以有也可以没有
成功0出错-1.
3、信号灯的P操作
所需要头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid , struct sembuf *opsptr, size_t nops);
semid:信号灯集ID
struct sembuf
{
short sem_num; //要操作的信号灯的编号
short sem_op; // 0:等待,直到信号类的值变成0
//1:释放资源,V操作
// -1消耗资源,P操作。
short sem_flg;//0,IPC_NOWAIT,SEM_UNDO
};
nops:要操作信号灯的个数。
成功0出错-1.
总结:只是过了一遍知识点,要想记住,需要在用的过程中随时翻翻,然后再补充自己的体会。