1.概述
- 管道:匿名管道和命名管道。管道可用于有亲缘关系进程间的通信,命名管道克服了管道没有名字的限制。因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信
- 信号:用于通知接受进程有某种事件发生,除了用于进程间通信,还可以线程间
- 消息队列:消息的链表,有权限的进程可以向队列中添加消息,被赋予读权限的进程可以读走消息
- 共享内存:多个进程访问同一块内存空间,最快的IPC。需要配合其他通信机制,保证同步与互斥
- 信号量:进程间好线程间的通信方式
- 套接字:不同机器间的进程间通信
2.Linux的共享内存实现及例子
不同进程之间共享的内存通常安排为同一段物理内存,进程可以将同一段共享内存连接到它们自己的地址空间中,实现读写。
(1)函数介绍:
- shmget函数:int shmget(key_t key,size_t size,int shmflg);//创建共享内存
key:提供一个key,有效的为共享内存段命名;返回值:若成功,返回一个与key相关的共享内存标识符。若失败,返回-1(注:其他进程可以通过该返回值访问同一共享内存);size:以字节为单位指定需要共享的内存容量;shmflg:权限标志,比如IPC_CREAT表示若共享内存不存在则创建,0644表示当前进程可读写,其他进程只能读。
注:通常情况下,key通过ftok函数得到,由内核变成标识符,要想让两个进程看到同一个信号集,只需设置key值不变就可以。
- shmat函数:void *shmat(int shm_id, const void *shm_addr, int shmflg); //创建完共享内存,还不能被任何进程访问,shmat函数的作用就是启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间
shm_id:由shmget返回的共享内存标识符;shm_addr:共享内存连接到当前进程中的地址位置,通常为NULL,表示让系统来选择;shm_flg:标志位,通常为0
- shmdt函数:int shmdt(const void* shmadrr);//将共享内存从当前进程中分离,不是删除,只是不可用
shmadrr:shmat函数返回的地址指针,成功时返回0,失败返回-1
- shmctl函数:int shmctl(int shm_id, int cmd, struct shmid_ds *buf);//删除共享内存
shm_id:shmget的返回值,即共享内存标识符;cmd:采取的操作,IPC_STAT表示“共享内存的当前关联值覆盖shmid_ds的值”,IPC_SET表示“如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值”,IPC_RMID表示“删除共享内存段”;buf:一个结构指针,指向共享内存模式和访问权限的结构体。
struct
(2)例子:
其中一个文件shmread.c创建共享内存,并读取其中的信息,另一个文件shmwrite.c向共享内存中写入数据。为了方便操作和数据结构的统一,为这两个文件定义了相同的数据结构,定义在文件shmdata.c中。
shmdata.c
#ifndef _SHMDATA_H_HEADER
shmread.c
#include
shmwrite.c
#include
问题:对written的操作(+1和-1)是非原子的,所以是不安全的
(3)优缺点:
- 优点:直接访问内存,加快了程序的效率。不要求是父子进程
- 缺点:没有提供同步机制,需要借助信号量等其他手段同步
3.Linux的信号实现及例子
(1)概述:
- 信号本质:类似于中断机制,进程收到一个信号与处理器收到一个中断请求是一样的
- 信号种类:可靠信号&不可靠信号;实时信号&非实时信号
- 进程对信号的响应:①忽略信号,除了SIGKILL和SIGSTOP②捕捉信号,定义某种信号处理函数。③执行缺省操作,Linux对每种信号对规定了默认操作
(2)信号的发送
发送信号的主要函数有kill(),raise(),sigqueue(),alarm(),setitimer()以及abort()
- kill:int kill(pid_t pid, int signo)
signo是信号值,当为0时,不发送任何信号,主要用于检查目标进程是否存在,以及当前进程是否具有向目标发送信号的权限,以及当前进程是否具有向目标发送信号的权限(root权限的进程可以向任何进程发送信号,非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号)0值信号可用于检查pid的有效性以及当前进程是否有权限向目标进程发送信号
- raise:int raise(int signo) 向进程本身发送信号,参数即为发送的信号值
- sigqueue:int sigqueue(pid_t pid, int sig, const union sigval val)
针对实时信号提出的,支持信号带有参数,与函数sigaction()配合使用
pid:接收信号的进程ID;sig:即将发送的信号;union sigval:信号传递的参数,4字节值
typedef
注:比kill传递多了更多的附加信息,但只能向一个进程发送信号,而不能发送信号给一个进程组。
- alarm:unsigned int alarm(unsigned int seconds);
专门为SIGALRM信号而设,在指定的时间seconds秒后,将向进程本身发送SIGALRM信号,又称为闹钟时间。进程调用alarm后,任何以前的alarm()调用都将无效。如果参数seconds为零,那么进程内将不再包含任何闹钟时间。返回值,如果调用alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
- setitimer:int setitimer(int which, const struct itimerval *val, struct itimeval *oval));
比alarm功能强大,支持三种类型的定时器(which参数):①ITIMER_REAL:设定绝对时间;经过指定的时间后,内核将发送SIGALRM信号给本进程;②ITIMER_VIRTUAL设定程序执行时间,经过指定的时间后,内核将发送SIGVTALRM信号给本进程;③ITIMER_PROF设定进程执行以及内核因本进程而消耗的时间和,经过指定时间后,内核将发送ITIMER_VIRTUAL信号给本进程。
- abort:void abort(void);
向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数。即使SIGABORT被进程设置为阻塞信号,调用abort()后,SIGABORT仍然能被进程接收。该函数无返回值。
(3)安装信号
安装信号主要用来确定信号值及进程针对该信号值的响应动作之间的映射关系。
- signal函数:在可靠信号系统调用的基础上实现,是库函数。它只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装。
void (*signal(int signum, void (*handler))(int)))(int);可以看成
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler));
其中,signum:指定信号的值;handler:信号的处理,可以忽略该信号(设为SIG_IGN),可以采用默认方式处理(SIG_DFL),也可以自己实现处理函数(参数函数指针);返回值:若成功,返回最后一层为安装信号signum而调用signal时的handler值,若失败,返回SIG_ERR
- sigaction函数:(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与sigqueue()系统调用配合使用,当然,sigaction()同样支持非实时信号的安装。
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
signum:信号值(除了SIGKILL和SIGSTOP);act:指向结构sigaction的一个实例的指针,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;oldact:原来对相应信号的处理。若act和oldact都为NULL,则函数用于检查信号的有效性
struct
_sa_handler指定的处理函数只有一个参数,即信号值,所以信号不能传递除信号值之外的任何信息;由_sa_sigaction是指定的信号处理函数带有三个参数,是为实时信号而设的(当然同样支持非实时信号)
(4)阻塞信号
每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。
#include
- sigprocmask()函数能够根据参数how来实现对信号集的操作,操作主要有三种:
- sigpending(sigset_t *set))获得当前已递送到进程,却被阻塞的所有信号,在set指向的信号集中返回结果。
- sigsuspend(const sigset_t *mask))用于在接收到某个信号之前,临时用mask替换进程的信号掩码,并暂停进程执行,直到收到信号为止。sigsuspend返回后将恢复调用之前的信号掩码。
(5)例子:
sig_send.c
#include
sig_recv.c
#include
4.Linux的管道实现以及例子
- 匿名管道:
int pipe(int fd[2]);//返回两个文件描述符,fd[0]读操作,fd[1]写操作,默认阻塞方式。
(1)有读进程,无写进程:
- 管道内无数据时,立即返回
- 管道内数据不足,读出所有数据
- 管道内数据充足,读出期望数据
(2)有读进程,有写进程:
- 管道内无数据时,读进程阻塞
- 管道内数据不足,读出所有数据
- 管道内数据充足,读出期望数据
#include
- 命名管道:
int mkfifo(char* path,mode_t mode); //path为命名管道名字
mkfifo(path,*)--->open(path,*)--->write/read
阻塞:(1)当进程以写或读的方式打开管道文件,必须有另一个进程以相对应的读或写方式也打开该文件,否则该进程将阻塞在open()位置。
(2)若两个进程都已打开,但中途某进程退出,则:①读进程退出,返回SIGPIPE信号 ②写进程退出,读进程将不再阻塞,直接返回 0
5.Linux的信号量实现以及例子
(1)相关函数
- int semget(key_t key, int num_sems, int sem_flags);
key:整数值(唯一非零),不相关的进程可以通过它访问一个信号量。(程序对所有信号量的访问都是间接的,程序先通过调用semget()函数并提供一个键,再由系统生成一个相应的信号标识符(semget()函数的返回值),只有semget()函数才直接使用信号量键,所有其他的信号量函数使用由semget()函数返回的信号量标识符)
num_sems:指定需要的信号量数目,它的值几乎总是1
sem_flags:一组标志。当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
- int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
sem_id:由semget()返回的信号量标识符,sembuf定义如下
struct
- int semctl(int sem_id, int sem_num, int command, ...);
command通常是下面两个值中的一个:SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置;IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
(2)例子
#include
6.Linux中消息队列的实现以及例子
(1)相关函数:
- int msgget(key_t key, int msgflg);
和其他IPC创建方式类似,不再赘述
- int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
msg_ptr:一个指向准备发送消息的指针,但是消息的数据结构却有一定的要求,指针msg_ptr所指向的消息结构一定要是以一个长整型成员变量开始的结构体,接收函数将用这个成员来确定消息的类型。形如:
struct
msg_sz :msg_ptr指向的消息的长度,不是结构体的大小(即除了message_type的大小)
msgflg :控制当前消息队列满或队列消息到达系统范围的限制时将要发生的事情。① 0表示当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列 ② IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回 ③ IPC_NOERROR:若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程。
- int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
msg_ptr:存放消息的结构体;msg_st:要接收消息的大小,不含消息类型占用的4个字节
msgtype可以实现一种简单的接收优先级。如果msgtype为0,就获取队列中的第一个消息。如果它的值大于零,将获取具有相同消息类型的第一个信息。如果它小于零,就获取类型等于或小于msgtype的绝对值的第一个消息
msgflg:0: 阻塞式接收消息,没有该类型的消息msgrcv函数一直阻塞等待;IPC_NOWAIT:如果没有返回条件的消息调用立即返回,此时错误码为ENOMSG;IPC_EXCEPT:与msgtype配合使用返回队列中第一个类型不为msgtype的消息;IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的size字节,则把该消息截断,截断部分将被丢弃
- int msgctl(int msgid, int command, struct msgid_ds *buf);
command是将要采取的动作,它可以取3个值,① IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。② IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值③ IPC_RMID:删除消息队列
buf:指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构。
struct
(2)例子
recv.c
#include
send.c
#include
7.Linux中的socket实现以及例子