《UNIX网络编程 卷2:进程间通信》笔记

本文详细介绍了进程间通信(IPC)的多种方式,包括管道、FIFO、消息队列、信号量及共享内存等,并深入探讨了Posix和SystemV两种标准下的IPC特性。

第一部分 简介

IPC是进程间通信(interprocess communication)的简称。

1.IPC三种类型的持续性
这里写图片描述
随进程持续: IPC对象一直存在到打开着该对象的最后一个进程关闭该对象为止。例如管道和FIFO。
随内核持续: IPC对象一直存在到内核重新自举或显式删除该对象为止。例如消息队里、信号量和共享内存。
随文件系统持续: IPC对象一直存在到显式删除该对象为止。例如使用映射文件实现的Posix消息队里、信号量和共享内存。

2.Posix IPC 和 System V IPC

两种标准的进程通信

SYSTEM V进程通信。包含消息队列、共享内存和信号量。
POSIX 进程通信。新标准的进程通信。

这里写图片描述

这里写图片描述

系统为每一个IPC对象保存一个ipc_perm结构体,该结构说明了IPC对象的权限和所有者,每一个版本的内核各有不用的ipc_perm结构成员。若要查看详细的定义请参阅文件 <sys/ipc.h>。

ipc_perm 结构定义于中,原型如下:
struct ipc_perm
{
key_t        key;                        调用shmget()时给出的关键字
uid_t           uid;                      /*共享内存所有者的有效用户ID */
gid_t          gid;                       /* 共享内存所有者所属组的有效组ID*/ 
uid_t          cuid;                    /* 共享内存创建 者的有效用户ID*/
gid_t         cgid;                   /* 共享内存创建者所属组的有效组ID*/
unsigned short   mode;    /* Permissions + SHM_DEST和SHM_LOCKED标志*/
unsignedshort    seq;          /* 序列号*/
};

3.ipcs 和 ipcrm程序

ipcs输出有关System V IPC特性的各种信息,ipcrm 则删除一个System V消息队列、信号量集或共享内存。

第二部分 消息传递

1.管道和FIFO

  • 管道

单向数据流。

#include <unistd.h>
int pipe(int fd[2]);

成功返回0,出错为-1

这里写图片描述

特殊情况:
读一个空管道会阻塞。写一个满管道(塞进了65535个字符)也会阻塞。
写一个读端被关闭的管道,会发出SIGPIPE信号。
读一个写端关闭的管道,则read直接返回0。

  • 双管道

这里写图片描述

  • FIFO

FIFO指先进先出数据流,也称为有名管道。性质和管道是一样的。

注意:FIFO不能打开来既读又写,它是半双工。

原型:int mkfifo(const char *pathname, mode_t mode);

pathnaem路径名,mode 如果指定O_CREAT | O_EXCL ,要么创建一个新的FIFO,要么返回一个EEXIST错误。
成功为0 ,出错返回为-1

对管道FIFO的write总是往末尾添加数据,对它们的read则总是从头开始返回数据。如果对管道FIFO调用lseek,则返回ESPIPE错误。

  • 管道和FIFO的额外属性
两种方式设置成非阻塞
(1open时指定O_NONBLOCK标志。
writefd = open(FIFO1,O_WRONLY | O_NONBLOCK,0);

(2)如果描述符已经打开,用fcntl启用O_NONBLOCK标志。

2.Posix 消息队列

Posix消息队列的读总是返回最高优先级的最早消息。随内核持续。

#include <mqueue.h>

创建新的消息队列或打开一个已存在的消息队列:
 mqd_t mq_open(const char *name, int oflag,...
                  /*mode_t mode,struct mq_attr *attr */);

返回:成功为消息队列描述符,出错为-1
消息队列的名字只能以一个 '/'开头,名字中不能包含其他的'/'

oflag 是O_RDONLY | O_WRONLY | O_RDWR 之一,可能按位或上O_CREAT 、O_EXCL或O_NONBLOCK 。
如果指定O_CREAT标志,mode和attr参数是必须要的,attr属性,如果为NULL,则使用默认属性。

关闭消息队列:
int mq_close(mqd_t modes);
返回:成功为0,出错为-1。

要想把消息队列从系统中删除,用mq_unlink。
int mq_unlink(const char *name);
返回:成功为0,出错为-1。


属性:
mq_getattr - 获取消息队列的属性
mq_setattr - 设置消息队列的属性

int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
int mq_setattr(mqd_t mqdes, const struct mq_attr *attr
                struct mq_attr *oattr);
返回:成功为0,出错为-1。

mq_attr结构体:
struct mq_attr {
               long mq_flags;       /* Flags: 0 or O_NONBLOCK */
               long mq_maxmsg;      /* Max. # of messages on queue */
               long mq_msgsize;     /* Max. message size (bytes) */
               long mq_curmsgs;     /* # of messages currently in queue */
};

往队列中放置一个消息或从队列中取走一个消息:mq_send和mq_receive函数

每个消息有一个优先级,小于MQ_PRIO_MAX的无符号整数

mq_receive总是返回指定队列中的优先级最高的最早消息,而且该优先级能随该消息的内容及其长度返回

#include <mqueue.h>
int mq_send(mqd_t mqdes, const char *ptr, size_t len, unsigned int prio);   
成功返回0,失败返回-1

int mq_receive(mqd_t mqdes, char *ptr, size_t lne, unsigned int *prio);  
成功返回消息中的字节数,失败返回-1

消息队列的限制:
有两个限制:

(1) MQ_OPEN_MAX:一个进程同时打开的消息队列的最大数目

(2)MQ_PRIO_MAX:任意消息的最大优先级值 加1

mq_notify函数:

适用情况:往空队列中放置了一个消息

两种方式 :

(1)产生一个信号
(2)创建一个线程执行一个指定的函数

函数定义

#include <mqueue.h>
int mq_notify(mqd_t mqdes, const struct sigevent *notification);

成功返回0,失败返回 -1

信号常值 都 定义在<signal.h>头文件中

union sigval{
   int sival_int;
   void *sival_ptr;
};

struct sigevent{
    int sigev_notify;//通知类型,有SIGEV_(NONE, SIGNAL, THREAD)
    int sigev_signo;//SIGEV_SIGNAL时的信号值
    uniong sigval sigev_value;//传递给信号处理函数或者线程的值
    void (*sigev_notify_function)(union sigval);
    pthread_attr_t *sigev_notify_attributes;
};

函数的若干规则:

(1)notification非空,表示有一个消息到达该队列并且先前为空时得到通知

(2)notification为空,表示注册被撤销

(3)任意时刻只有一个进程可以注册为接收某个队列的通知

(4)当有一个消息到达先前为空的队列时,且有一个进程注册为接收该队列的通知时,只有没有任何线程阻塞在该队列的mq_receive调用的前提下,通知才会发出。即mq_receive调用中的阻塞优先于任何通知的注册

(5)当通知发送给注册进程时,其注册被撤销,须再次调用 mq_notify注册

3.System V 消息队列

先进先出的结构。
这里写图片描述

#include <sys/msg.h>

1.键的生成:
为了得到同一个键,必须使用双方都看得到的东西。文件名是一个选项,为了避免使用不同的文件名而碰巧得到同一个键,再加上一个数字来共同构造一个键。

key_t ftok(const char *pathname, int proj_id);

ftok的原理是:
读取路径(文件)的属性,取出它的dev_no和i_no,前者取8位,后者取16位,然后加上id的低8位,组成一个key。

2.创建或获取消息队列:

int msgget(key_t key, int msgflg);

返回:成功为非负标识符,出错为-1

key是大家共同生成的,msgflg类似于文件的权限,多了一个标志位。
这个标志位有:
IPC_CREAT   //创建
IPC_EXCL     //排他,如果创建时存在则返回-1

在终端可以使用ipcs查看消息队列,使用ipcrm删除以创建的消息队列

3.发送消息:

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

成功返回0,出错为-1

msgp就是指向我们要发送的消息结构体。
这个结构体需要自己创建,要求该结构体第一个参数是long就行了。
典型的结构体如下:
struct msg_t {
long mtype;  //消息的自定义类型
char mtext[1024];  //存储特定的消息字符
};

struct msg_t {
long mtype;  //消息的自定义类型
unsignd int data;//发送的数据
short flag; //同上
};

msgsz是消息的大小,是整个结构体的大小-4 (mtype的大小)。

msgflg是标志位,通常为0,也有一个常用选项就是IPC_NOWAIT,如果消息队列满了就不等待直接返回一个错误。

4.读取消息:

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
                      int msgflg);
返回:成功为读入缓冲区中数据的字节数,出错为-1

其中msgtype指定了要读取的消息类型,如果>0表示读取该消息类型的消息,
=0表示读取队列里 第一条消息,<0读取比该值绝对值小的类型的第一条消息

msgrcv的msgflag指定所请求的类型不在所指定的队列时该如何处理。如果设置了IPC_NOWAIT位,msgrcv立即返回ENOMSG错误,否则阻塞一下事件发生为止:
(1)有一个请求类型的消息可获取
(2)有msqid标识的消息队列被从系统中删除(返回EIDRM错误)
(3)调用线程被某个捕获的信号所中断(返回EINTR错误)

5.消息队列的管理:

一个消息队列对象可以存放16条消息,每条消息最多可以有8192个字节。
这些属性可以通过一个函数设置或者获取。
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

msgid是生成的id,
cmd是选项:
IPC_SET:设置属性
IPC_INFO:获取属性
IPC_RMID:删除前面生成的消息队列
IPC_STAT 给调用者返回所指定消息队列对应打枊前msqid_ds结构体(查头文件)

第三部分 同步

1.互斥锁和条件变量


 - 1.互斥锁
用于保护临界区。


锁的初始化:
(1) 静态分配 
例:static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
(2) 动态分配
用pthread_mutex_init 函数初始化。

互斥锁上锁和解锁:
pthread_mutex_t  :数据类型
int pthread_mutex_lock(pthread_mutex_t *mptr):  上锁
int pthread_mutex_unlock(pthread_mutex_t *mptr): 解锁
返回:成功为0,出错为正的Exxx值    都为阻塞函数

int pthread_mutex_trylock(pthread_mutex_t *mptr);
非阻塞函数,如果互斥锁已经锁住,返回一个EBUSY错误

 - 2.条件变量
互斥锁用于上锁,条件变量用于等待。

pthread_cond_t : 数据类型
pthread_cond_wait(pthread_cond_t *cptr,pthread_mutex_t *mptr): 睡眠等待
pthread_cond_signal(pthread_cond_t *cptr):  唤醒等待的线程
pthread_cond_broadcast(pthread_cond_t *cptr): 通知所有等待的线程

通常pthread_cond_signal只是唤醒等待在相应条件变量上的一个线程,在某些情况下需要唤醒多个线程(例如读写者问题),可以调用pthread_cond_broadcast唤醒阻塞在相应条件变量上的所有线程。


pthread_mutex_init:  初始化互斥锁
pthread_mutex_destroy:  从系统中摧毁锁
pthread_cond_init:  初始化条件变量
pthread_cond_destroy: 

2.读写锁


读写锁的分配规则:
(1)只要没有线程持有某个特定的读写锁用于写,那么任意数目的线程可以持有该读写锁用于读。
(2)仅当没有线程持有某个给定的读写锁用于读或用于写时,才能分配该读写锁用于写。

获取与释放读写锁:
#include<pthread.h>  
int pthread_rwlock_rdlock(pthread_rwlock_t *rwptr)  读出锁
int pthread_rwlock_wrlock(pthread_rwlock *rwptr)   写入锁
int pthread_rwlock_unlock(pthread_rwlock *rwptr)  解锁

返回:成功为0 ,出错为正的Exxx值

下面两个函数尝试获取一个读出锁或写入锁,但是如果不能马上获得,那就返回一个EBUSY错误,而不是把调用线程投入睡眠,也就是不阻塞。

int pthread_rwlock_tryrdlock(pthread_rwlock *rwptr)  
int pthread_rwlock_trywrlock(pthread_rwlock *rwptr)  

读写锁属性:

读写锁变量可以通过pthread_rwlock_init来动态初始化,当一个线程不再需要某个读写锁时,可以调用pthread_rwlock_destory来摧毁它

int  pthread_rwlock_init(pthread_rwlock_t *rwptr,const pthread_rwlockattr_t *attr)  
int pthread_rwlock_destory(pthread_rwlock_t *rwptr)  

初始化时,attr是个空指针,那么读写锁使用默认属性。如果要赋予它非默认属性,需要使用下面两个函数

int pthread_rwlockattr_init(pthread_rwlockattr_t *attr)  

int pthread_rwlockattr_destory(pthread_rwlockattr *attr)  

当期定义了唯一的属性是PTHREAD_PROCESS_SHARED,它指定读写锁在不同进程间共享,二不仅仅是在单个进程内的不同线程共享,下面两个函数分别获取和设置这个属性

int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr,int *valptr)  

int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int value)  

 - 线程取消

 一个线程可以被同一进程的任何其他线程所取消。
int pthread_cancel(pthread_t tid);

如果pthread_rwlock_rdlock的调用线程阻塞在pthread_cond_wait调用上,随后被统一进程中的其他线程取消,它就在仍然持有互斥锁的情况下终止。

我们可以在原有的代码中加入两行:

rw->rw_nreaders++;  
pthread_cleanup_push(rwlock_cancelrdwait,(void *arg))//添加清理处理程序      
result=pthread_cond_wait(&rw->rw_condreaders,&rw->rw_mutex);  
pthread_cleanup_pop(0);   //删除清理处理程序  
 rw->rw_nreaders--;  

3.记录上锁

记录上锁是读写锁的一种扩展类型,它可用于任意两个进程间共享某文件的读和写。执行上锁的函数是fcntl,锁由内核维护,其属主由进程ID标识。

Unix内核没有记录这一概念,对记录的解释是由读写文件的应用进行的。每个记录就是文件中的一个字节范围。

Posix记录上锁的粒度是单个字节,粒度越小,允许同时使用的用户数就越多。
Posix记录锁为劝告性上锁。

Posix fcntl 记录上锁

int fcntl(int fd, int cmd, .../* struct flock *arg*/);

第三个参数(flockptr),指向一个flock结构指针,flock的结构如下:
 struct flock {
        short l_type;/*F_RDLCK, F_WRLCK, or F_UNLCK*/
        off_t l_start;/*相对于l_whence的偏移值,字节为单位*/
        short l_whence;/*从哪里开始:SEEK_SET, SEEK_CUR, or SEEK_END*/
        off_t l_len;/*长度, 字节为单位; 0 意味着缩到文件结尾*/
        pid_t l_pid;/*returned with F_GETLK*/
 };

fcntl函数的cmd参数为以下三个值时,执行记录上锁的相关操作:

1.   F_SETLK 获取(l_type成员为F_RDLCK或F_WRLCK)或释放(F_UNLCK)指定的锁,若无法完成该操作则返回出错而不阻塞。

2.   F_SETLKW 阻塞版本的F_SETLK。

3.   F_GETLK 检查arg指向的锁是否与某个已存在的锁冲突。

F_GETLK后紧接着F_SETLK不是原子操作。

fcntl不能对只读打开的文件获取写锁,也不能对只写打开的文件获取读锁。

锁住整个文件的两个方式:

1.   l_whence成员为SEEK_SET,l_start的成员为0,l_len成员为02.   用lseek把读写指针放到文件头,然后令l_whence为SEEK_SET,l_start为0,l_len为0。

某个文件描述符被关闭时,与它关联的记录锁都被删除。记录锁不能通过fork子进程继承。

记录锁不应该同标准I/O函数一起使用,因为标准I/O库使用了缓冲。

NFS可以使用记录锁。

4.Posix 信号量

信号量(semaphore)是一种提供不同进程间或者一个给定进程不同线程之间的同步。
Posix两类信号量:有名信号量和基于内存的信号量。

Posix信号量的三种操作:
(1)创建(create)
(2)等待(wait)
(3)挂出(post)

1.sem_init()初始化无名信号量(基于内存的信号量)

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);

int sem_destroy(sem_t *sem); //摧毁基于内存的信号量

//pshared指定该信号量用于进程还是线程同步
//0-表示用于线程同步(所有线程可见)
//非0-表示用于进程同步(需要放在共享内存中)

2.sem_open()初始化有名信号量

#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);

成功返回指向信号量的指针,出错为SEM_FAILED

Link with -pthread.

//oflag可以设置为O_CREAT 或 O_CREAT | O_EXCL (如果存在则返回错误)。
//mode可以设置为0644 自己可读写,其他用户和组内用户可读
//value表示信号量初始化的值,二值信号量的初始值通常为1,计数信号量的初始值往往大于1。

不同进程间可以访问同一个有名信号量,sem_open时指定相同名字就行。

关闭函数:

int sem_close(sem_t *sem);
成功为0,出错为-1

从系统中删除有名信号量:

int sem_unlink(const char *name);
成功为0,出错为-1

3.sem_wait()和sem_post()等待和挂出函数

#include <semaphore.h>
int sem_wait(sem_t *sem);//P操作  -1
int sem_post(sem_t *sem);//V操作 +1

int sem_trywait(sem_t *sem);
信号量为0 不睡眠,返回EAGAIN错误
Link with -pthread.


4.sem_getvalue 函数

int sem_getvalue(sem_t *sem , int *valp);

valp 指向整数中返回指定信号量的当前值。如果该信号量已上锁,返回当前值或为0,或为负数,其绝对值就是等待该信号量解锁的线程数。

5.System V 信号量

System V信号量一般指信号量集,而Posix 信号量一般指单个计数信号量。
与消息队列和共享内存一样,信号量集也有自己的数据结构:

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

同样地,第一个条目sem_perm 也是共有的ipc 对象内核结构,剩下的是私有成员。

struct sem{
unsigned short semval; /* semaphore value */
unsigned short semzcnt; /* # waiting for zero */
unsigned short semncnt; /* # waiting for increase */
pid_t sempid; /* process that did last op */
}

即每一个在信号量集中的信号量都有上述4个相关的变量。

典型的做法:

令i=1;
A进程进入临界区,先-1,看看是否==0,如果真则进入临界区。
B进程同时准备进入临界区,也要-1,但是结果不为0,睡眠等待。

A出临界区,+1,唤醒其它进程。
B 将i-1,此时结果为0,进入临界区,执行。

以上的操作中,-1这个过程叫做P操作。+1的过程叫做V操作。总的来说就是PV操作。

具体由信号量(semaphore,也叫旗语)来实现。

相关的函数都是以sem开头的。

(1)信号量对象的创建
int semget(key_t key, int nsems, int semflg);

nsems参数指定集合中的信号量数。访问一个已存在的集合,该参数指定为0。
semflag 为IPC_CREAT 或 IPC_CREAT | IPC_EXCL。


(2)信号量的管理
int semctl(int semid, int semnum, int cmd, .../*union senum arg*/);

semnum是信号量对象里的信号量的编号。从0开始的。

cmd有如下的选项:
GETVAL: 获取单个信号量的值。
SETVAL:  设置单个信号量的值。
GETALL:  获取所有信号量的值。
SETALL:  设置所有信号量的值。

还有其他命令参考操作手册。

设置和读取需要第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) */
           };
设置单个信号量,只需赋值val既可以。如果要同时设置多个信号量,就要使用array数组。

(3)pv操作
int semop(int semid, struct sembuf *sops, size_t nsops);

struct sembuf{
    unsigned short sem_num;  /* semaphore number */
    short          sem_op;   /* semaphore operation */
    short          sem_flg;  /* operation flags */

};

第一个成员是信号量编号,第二个是操作,有+n,-n操作,减是p操作就是获取信号量,加是v操作,释放信号量。
第三个成员的标识,通常可以设置为0,有两个选项:
SEM_UNDO: 进程崩溃或退出后,内核会恢复信号量的初始值。
SEM_NOWAIT: 不等待。

参数中nsops是结构体数组的大小。本函数可以同时操作多个结构体,只需要告诉数量就可以了。

第四部分 共享内存区

1.共享内存区介绍

文件的内存映射:

将文件按一页一页的方式放入内存的页中,然后进行映射。

mmap函数和munmap函数

内存映射用mmap完成。
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr是内存中开始映射的地址,通常为0,由系统选择。
length是映射的长度,不足1页,也会使用1页。
prot是保护方式,有PROT_READ, PROT_WRITE, PROT_EXCUTE
flags是共享方式, 有MAP_SHARED 共享, MAP_PRIVATE私有, MAP_ANON匿名
fd是要映射的文件描述符,如果是匿名映射则使用-1,falg用MAP_SHARED |MAP_ANON
offset是偏移的量,必须是页的整数倍。

返回值就是文件映射到内存里的起始地址。如果失败,返回的是MAP_FAILED宏。

注意:
mmap里的读写权限要和文件打开时的读写权限一致,另外不要对只读文件进行内容修改。

从某个进程的地址空间删除一个映射关系:

int munmap(void *addr ,size_t len);

返回:成功为0,出错为-1

2.Posix 共享内存区

两种无亲缘关系进程间共享内存的方法:

  1. 内存映射文件:open一个普通文件,用它的fd进行mmap。

  2. 共享内存区对象:shm_open一个IPC名字,用它的fd进行mmap。步骤:指定名字调用shm_open(..)创建共享内存 ,然后调用mmap将共享内存映射到调用进程。

int shm_open(constchar *name, int oflag, mode_t mode); (调用shm_open前一般先调用shm_unlink以提防所需共享内存区对象已经存在的情况)

返回:成功为非负描述符,出错为-1。
创建或打开一个Posix共享内存区对象。Posix没有指定一个新建的共享内存区对象的初始内容。

int shm_unlink(constchar *name);(删除一个名字不会影响对于其底层支撑对象的现有引用,知道对于该对象引用的全部关闭为止,删除一个名字仅仅防止后续的open mq_open  sem_open调用取得成功)

引用计数删除。

int ftruncate(int fd, off_t length);

改变文件或共享内存区对象的大小。

int fstat(int fd,struct stat *buf);

获取文件信息,对共享内存区对象只有4个成员有信息:


注意:

同一共享内存区对象内存映射到不同进程的地址空间时,起始地址可以不一样。

3.System V 共享内存区

步骤:先调用shmget,再调用shmat。

int shmget(key_t key, size_t size, int shmflg);

返回:成功为共享内存区对象,出错为-1。

void *shmat(int shmid, const void *shmaddr, int shmflg);

将创建的内存附到本进程,就可以访问,多个进程都执行的话,就可以访问同一块内存。

返回:成功为映射区的起始地址,出错为-1。

shmid为shmget返回的标识符。
shmaddr为NULL,则系统自动选择地址(可移植性最好的方法)
shmflag通常为0int shmdt(const void *shmaddr);

dt是detattach的意思,脱离本进程。不删除内存。如果要删除使用shmctl函数加上SHM_RMID这个选项。

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

cmd:一些命令

    IPC_STAT 得到共享内存的状态
    IPC_SET 改变共享内存的状态
    IPC_RMID 删除共享内存 

IPC_RMID 命令实际上不从内核删除一个段,而是仅仅把这个段标记为删除,实际的删除发生在最后一个进程离开这个共享段时。 

注意:
共享内存不会随着程序结束而自动消除,要么调用shmctl删除,要么自己用手敲命令去删除,否则永远留在系统中。
目录 封面 -12 封底 -11 扉页 -10 版权 -9 版权声明 -8 前言 -7 目录 -3 第一部分 简介 1 第1章 简介 2 1.1 概述 2 1.2 进程、线程与信息共享 3 1.3 IPC对象的持续性 4 1.4 名字空间 5 1.5 fork、exec和exit对IPC对象的影响 7 1.6 出错处理:包裹函数 8 1.7 Unix标准 9 1.8 书中IPC例子索引表 11 1.9 小结 13 习题 13 第2章 Posix IPC 14 2.1 概述 14 2.2 IPC名字 14 2.3 创建与打开IPC通道 16 2.4 IPC权限 18 2.5 小结 19 习题 19 第3章 System V IPC 20 3.1 概述 20 3.2 key_t键和ftok函数 20 3.3 ipc_perm结构 22 3.4 创建与打开IPC通道 22 3.5 IPC权限 24 3.6 标识符重用 25 3.7 ipcs和ipcrm程序 27 3.8 内核限制 27 3.9 小结 28 习题 29 第二部分 消息传递 31 第4章 管道和FIFO 32 4.1 概述 32 4.2 一个简单的客户-服务器例子 32 4.3 管道 32 4.4 全双工管道 37 4.5 popen和pclose函数 39 4.6 FIFO 40 4.7 管道和FIFO的额外属性 44 4.8 单个服务器,多个客户 46 4.9 对比迭代服务器与并发服务器 50 4.10 字节流与消息 51 4.11 管道和FIFO限制 55 4.12 小结 56 习题 57 第5章 Posix消息队列 58 5.1 概述 58 5.2 mq_open、mq_close和mq_unlink函数 59 5.3 mq_getattr和mq_setattr函数 61 5.4 mq_send和mq_receive函数 64 5.5 消息队列限制 67 5.6 mq_notify函数 68 5.7 Posix实时信号 78 5.8 使用内存映射I/O实现Posix消息队列 85 5.9 小结 101 习题 101 第6章 System V消息队列 103 6.1 概述 103 6.2 msgget函数 104 6.3 msgsnd函数 104 6.4 msgrcv函数 105 6.5 msgctl函数 106 6.6 简单的程序 107 6.7 客户-服务器例子 112 6.8 复用消息 113 6.9 消息队列上使用select和poll 121 6.10 消息队列限制 122 6.11 小结 124 习题 124 第三部分 同步 125 第7章 互斥锁和条件变量 126 7.1 概述 126 7.2 互斥锁:上锁与解锁 126 7.3 生产者-消费者问题 127 7.4 对比上锁与等待 131 7.5 条件变量:等待与信号发送 132 7.6 条件变量:定时等待和广播 136 7.7 互斥锁和条件变量的属性 136 7.8 小结 139 习题 139 第8章 读写锁 140 8.1 概述 140 8.2 获取与释放读写锁 140 8.3 读写锁属性 141 8.4 使用互斥锁和条件变量实现读写锁 142 8.5 线程取消 148 8.6 小结 153 习题 153 第9章 记录上锁 154 9.1 概述 154 9.2 对比记录上锁与文件上锁 157 9.3 Posix fcntl记录上锁 158 9.4 劝告性上锁 162 9.5 强制性上锁 164 9.6 读出者和写入者的优先级 166 9.7 启动一个守护进程的唯一副本 170 9.8 文件作锁用 171 9.9 NFS上锁 173 9.10 小结 173 习题 174 第10章 Posix信号量 175 10.1 概述 175 10.2 sem_open、sem_close和sem_unlink函数 179 10.3 sem_wait和sem_trywait函数 180 10.4 sem_post和sem_getvalue函数 180 10.5 简单的程序 181 10.6 生产者-消费者问题 186 10.7 文件上锁 190 10.8 sem_init和sem_destroy函数 191 10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值