IPC随记(更新至全文)

来源:互联网 发布:mysql不显示重复数据 编辑:程序博客网 时间:2024/06/11 01:32

<进程间通信>看了随便记录,晚些时候再整理。

 粗略的扫了一遍,有空再做详细的整理吧。

flag:
O_RDONLY 只读模式
O_WRONLY 只写模式
O_RDWR 读写模式
打开/创建文件时,至少得使用上述三个常量中的一个。以下常量是选用的:
O_APPEND 每次写操作都写入文件的末尾
O_CREAT 如果指定文件不存在,则创建这个文件
O_EXCL 如果要创建的文件已存在,则返回 -1,并且修改 errno 的值
O_TRUNC 如果文件存在,并且以只写/读写方式打开,则清空文件全部内容(即将其长度截短为0)
O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端。
O_NONBLOCK 如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O




oflag:
#define IPC_CREAT  00001000   /* create if key is nonexistent */
#define IPC_EXCL   00002000   /* fail if key exists */
#define IPC_NOWAIT 00004000   /* return error on wait */


POSIX 与system V 两种IPC的区别在于名字,一个是px_ipc_name另外一个是key_t键。key_t定义为一个整数,由fotk(const char * pathname,int)获得。如果pathname不存在,则fotk失败。
system还有一个ipc_perm结构体




打开或者创建IPC:
posix:mq_open,sem_open,shm_open
system:msgget,semget,shmget


flag之类的可参见教材23页




IPC权限:
posix:由创建是的权限flag所决定(O_RDONLY,O_WRONLY或O_RDWR)
SYSTEM:使用某个getXXX函数里,flag决定,其值可见24页




管道(先进先出):
创建管道:pipe(int fd[2]) 创建两个文件描述符,其中fd[0]用于打开文件读,fd[1]用于写
用于父子进程之间通信。
全双工模式的话(SVR4),写入fd[0]的数据只能从fd[1]读出,写入fd[1]的数据只能从fd[0]中读出。


FIFO(有名管道),创建:mkfifo(const char *pathname,mode_t mode);mode类似于open的第二个参数。例子可参见42页


设置管道和FIFO属性




POSIX消息队列跟system V的差别,见教材75页
创建消息队列:#include <mqueue.h>
mqd_t mq_open( const char * name, int oflag,... )
关闭消息队列:int mq_close(mqd_t mqdes)
关闭消息队列的话,没有从系统中删除,要从系统中删除的话
int mq_unlink(const char *name) name为open时的name


每个消息队列有四个属性,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);
struct mq_attr{
   long mq_flags;
   long mq_maxmsg;
   long mq_msgsize;
   long mq_curmsgs;
};


int mq_notify(mqd_t mqdes,const struct sigevent *motification);


队列发送与接收
int mq_send(mqd_t mqdes,const char *ptr,size_t len,unsigned int prio);
int mq_receive(mqd_t mqdes,char *ptr,size_t len,unsigned int *priop);
mq_send函数往消息队列中写入消息,mq_receive函数从消息队列中读出消息。


int mq_notify(mqd_t mqdes,const struct sigevent *motification);该函数为指定队列建立或删除异步事件通知


mq_receive 为阻塞函数


小结:Posix的消息队列比较简单,mq_open创建一个新队列或者打开一个已存在的队列,mq_close关闭队列,mq_unlink则删除队列名。往一个队列中放置消息使用mq_send,从一个队列中读取消息则用mq_receive。队列属性的查询与设置使用mq_getattr和mq_setattr,函数mq_notify则允许我们注册一个信号和线程,他们在有一个消息被放置到某个空队列上时发送(信号)或者激活(线程)。队列中的每个消息被赋予一个小整数优先级,mq_receive每次被调用时总是返回最高优先级的最早消息。
POSIX实时信号,他们在SIGGRMIN和SINGRTMAX之间。当设置SA_SIGINFO标志来安装这些信号的处理程序时,1、这些信号是排队的,2、排了队的信号是以FIFO顺序递交的,3、给信号处理程序传递两个额外的参数。






System V消息队列使用消息队列标示符标识。


msgget用于创建一个新的消息队列或者访问一个已存在的消息队列。返回值是一个整数标识符,其他三个msg函数就是用它来指代该队列。它是基于key_t产生的,而key既可以是ftok的返回值,也可以是常值IPC_PRIVATE
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
例:msgget(key,IPC_CREAT|0666)


msgsnd用来发送消息。成功返回0 出错返回-1
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msqid:消息队列的识别码。
msgp:指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构,形态如下
  struct msgbuf {
  long mtype; /* 消息类型,必须 > 0 */
  char mtext[1]; /* 消息文本 */
  };
msgsz:消息的大小。
msgflg: 用来指明核心程序在队列没有数据的情况下所应采取的行动。即可以是0,也可以是IPC_NOWAIT.IPC_NOWAIT标志是的msgsnd调用非阻塞


msgrcv用来从某个消息队列中读出一个消息
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msgtype:指定希望从所给定的队列中读出什么样的消息
0:返回该队列中的第一个消息
>0:返回其类型值为msgtype的第一个消息
<0:返回其值小于或等于msgtype参数的绝对值的消息中类型最小第一个参数。
msgflag另外多一个参数是MSG_NOERROR,如果所接收的消息真正数据大于msgsz,如果置该为,则只截取部分,否则的话报错


msgctl提供一个消息队列上得各种控制操作
int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );cmd :
IPC_STAT:读取消息队列的数据结构msqid_ds,并将其存储在b u f指定的地址中。
IPC_SET:设置消息队列的数据结构msqid_ds中的ipc_perm元素的值。这个值取自buf参数。
IPC_RMID:从系统内核中移走消息队列。
for:msgctl(msgqid,IPC_RMID,NULL);


struct mymesg
{
   long mesg_len;//消息长度 
   long mesg_type;//消息类型 
   long mesg_data[MAXMESGDATA];//数据域 
};




同步:
互斥锁,互斥锁通常用于多个线程或多个线程分享的共享数据
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mprt); //上锁(阻塞)
int pthread_mutex_trylock(pthread_mutex_t *mprt);//上锁(非阻塞)
int pthread_mutex_unlock(pthread_mutex_t *mprt); //解锁


等待与信号发送
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cptr,pthread_mutex_t *mptr);
int pthread_cond_signal(pthread_mutex_t *mptr);
调用pthread_cond_wait进入睡眠状态,执行以下两个动作:1、给mpstr解锁;2、把线程投入睡眠,直到外部唤醒,pthread_cond_signal。
调用pthread_cond_signal前必须锁住该互斥锁。


定时等待和广播
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cptr);
int pthread_cond_timewait(pthread_cond_t *cptr,pthread_mutex_t *mptr,const struct timespec *adstime);


读写锁
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
成功则返回0, 出错则返回错误编号.


int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
非阻塞的获取锁操作, 如果可以获取则返回0, 否则返回错误的EBUSY.


初始化以及销毁
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
如果attr为空,则先要初始化attr。
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destory(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_getpshared(pthread_rwlockattr_t *attr,int *valptr);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr,int value);


线程取消:
int pthread_cancel(pthread_t tid);


小结:与普通的互斥相比,被保护数据的读访问比写访问更为频繁时,读写锁能提供更高的并发度。读写锁可以只通过使用互斥和条件变量来实现。




记录上锁:
#include <fcntl.h>
int fcntl(int fd,int cmd,..../*stryct flock *arg */);




POSIX信号
sem_wait(&sem);信号量减1//等待
sem_post(&sem);信号量加1//挂出
互斥锁、信号量、条件变量之间的差异
1、互斥锁必须总是给它上锁的线程解锁,信号量的挂出却不必由执行过他的等待操作的同一个线程执行。
2、互斥锁要不被锁住,要不解开(二值状态)
3、既然信号量有一个与之关联的状态,那么信号量挂出操作总是被记住。
Posix提供两类信号量:有名的信号量和基于内存的信号量。
Posix有名信号量的三种实现:使用FIFO;使用内存映射IO以及互斥锁和条件变量;使用System V信号量
sem_open用于创建一个新的有名信号或者打开一个已存在的有名信号。sem_open的返回值是指向sem_t数据类型的指针。该指针随后用于其他函数的参数
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag, ...); //for:sem_open(char *name,flag,FILEM_MODE,value);


int sem_close(sem_t *sem);
int sem_unlink(sem_t *sem);


int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);两者的区别是当所指信号量已经是0时,后者并不调用线程投入睡眠,而是返回EAGAIN错误。


int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem,int *valp);//如果该信号量已经上锁,那么返回值或为0,或为某个负数,其绝对值就是等待该信号量解锁的线程数。


分配信号量的内存空间,然后由系统初始化他们的值
int sem_init(sem_t *sem,int shared, unsigned int value);
int sem_destory(sem_t *sem);


System V信号量
二值信号量,计数信号量,计数信号量集
 struct semid_ds{
 struct ipc_perm sem_perm;
 struct sem *sem_base;
 u_short sem_nsems;
 time_t sem_otime;
 time_t sem_ctime;
  };


struct sem{
ushort_t semval;
short    sempid;
ushort_t semncnt;
ushort_t semzcnt;
  };


semget创建一个信号量集或者访问一个已存在的信号量集,返回值是一个称为信号量标识符的整数,semop和semctl函数将使用它。
#include <sys/sem.h>
int  semget(key_t key,int nsems,int oflag);//成功返回非负数
nsems参数指定集合中的信号量数。


semop:打开一个信号量集后,对其中一个或者多个信号量的操作。
int semop(int semid, struct sembuf *opsptr,size_t nops);//返回值0为成功
nops参数指出opstr指向的sembuf结构数组中元素的数目
struct sembuf {
u_short sem_num; /* semaphore # */
short sem_op;/* semaphore operation */
short sem_flg;/* operation flags */
};
semval:信号量的当前值
semncnt:等待semval变为大于其当前值的线程数
semzcnt:等待semval变为0的线程数
semadj:所指定信号量对调用进程的调整值


semop:正数,0,负数
1、如果是正数,其值就加到semval上。如果指定了SEM_UNDO标识,那就从semadj值中减掉semop的值
2、0,等待semval变为0.如果semval已经是0 ,则立即返回
3、负数。等待semval大于或等于semop的绝对值。如果指定了SEM_UNDO标识,那就从semadj值中加上semop的绝对值


semctl:一个信号量执行各种控制操作
int semctl(int semid,int semnum,int cmd,...../*union semun arg*/);//成功返回非负数
union semun {
int val;/* value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT & IPC_SET */
u_short *array; /* array for GETALL & SETALL */
};
CMD:
GETVAL:把semval的当前值作为函数返回值返回。既然信号量绝不会是负数,那么成功的返回值总是非负数
SETVAL:把semval值设置为arg.val。如果操作成功,那么相应信号量在所有进程中信号量调整值(semadj)被置为0.
GETPID:把sempid的当前值作为函数返回值返回
GETNCNT:把semncnt的当前值作为函数返回值返回
GETZCNT:把semzcnt的当前值作为函数返回值返回
GETALL:返回所指定信号量集内每个成员的semval值。这些值通过arg.array指针返回,函数本身返回为0.
SETALL:设置所指定信号量集中每个成员的semval值。这些值通过arg.array指针所指定的。
IPC_RMID:把由semid指定的信号量集从系统中删除掉
IPC_SET:设置所指定信号量集的semid_ds结构中的以下三个成员,sem_perm.uid,sem_perm.gid,sem_perm.mode,这些值有arg.buf参数指向的结构体中的相应成员指定
IPC_STAT:通过arg.buf返回所指定信号量集当前的semid_ds结构


小结:从Posix信号量到System V信号量发生了如下变化:
1、System V信号量是有一组值构成的。
2、可应用到一个信号量集的每个成员的操作有三个:测试其值是否为0,往其值上加一个整数以及从其值中减掉一个整数。
   Posix信号量所允许的操作知识将其值加1或者减1
3、创建一个System V信号量集需要技巧,因为创建该集合并随后初始化其各个值需要两个操作,从而可能导致竞争状态
4、System V信号量提供“复旧”特性,该特性保证在进程终止时逆转某个信号量操作




共享内存:
共享内存区是可用IPC形式中最快的。一旦这样的内存区映射到共享他的进程的地址空间,这些进程间数据的传递就不在涉及内核。


两个或多个进程共享一个内存区,必须协调或同步该共享内存区的使用。


1、服务器使用一个信号量取得访问某个共享内存区对象的权力
2、服务器将数据从输入文件读入到该共享内存区对象。read函数的第二个参数所指定的数据缓冲区地址指向这个共享内存区对象
3、服务器读入完毕时,使用一个信号量同志客户。
4、客户将这些数据从该共享内存区对象写出到输出文件中。


mmap函数把一个文件或者一个Posix共享内存区对象映射到调用进程的地址空间。三个目的:
1、使用普通文件以提供内存映射I/O
2、使用特殊文件以提供匿名内存映射
3、使用shm_open以提供无亲缘关系进程间的Posix共享内存区
#include <sys/mman.h>
void *mmap(void *addr,size_t len,int port,int flags,int fd,off_t offset);//若成功则为被映射区的起始地址,若出错则为MAP_FAILED


addr通常为NULL,len为映射到调用进程地址空间的字节数,它从被映射文件开头起第offset个字节处开始算,offset通常设置为0.
prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
PROT_EXEC //数据可执行
PROT_READ //数据可读
PROT_WRITE //数据写
PROT_NONE //数据不可访问
flags:指定映射对象的类型,映射选项和映射页是否可以共享。
MAP_SHARED//变动是共享的
MAP_PRIVATE//变动是私自的
MAP_FIXED//准确地解释addr参数
从移植性上考虑,MAP_FIXED不应该指定。


父子进程之间共享内存区的方法之一是,父进程在调用fork之前先指定MAP_SHAERD调用mmap。Posix.1保证父进程中的内存映射关系存留到子进程中。而且父进程所作的修改子进程能看到,反过来也一样。
mmap成功后,fd参数可以关闭。


munmap从某个进程地址空间删除一个映射关系
int munmap(void *addr,size_t len);//若成功返回0


msync执行同步
int msync(void *addr,size_t len,int flags);
flags:刷新的参数设置 MS_ASYNC和MS_SYNC必须指定一个,但不能都指定。
MS_ASYNC(异步)
MS_SYNC(同步)
MS_INVALIDATE 使高速缓存的数据失效


小结:共享内存区是可使用IPC形式中最快的,因为共享内存区中的单个数据副本对于共享该内存区的所有线程或进程都是可用的。然后为协调共享内存区的各个线程或进程,通常需要某种形式的同步。
用mmap映射该文件的内存位置。


使用内存映射文件、使用4.4BSD匿名内存映射、使用/dev/zero匿名内存映射


Posix共享内存区
Posix提供了两种在无亲缘关系进程间共享内存区的方法 
1、内存映射文件 :由open函数打开,由mmap函数把得到的描述符映射到当前进程地址空间中的一个文件。
2、共享内存区对象:shm_open打开一个Posix.1 ipc名字(也许是在文件系统中的一个路径名),所返回的描述符由mmap映射到当前进程的地址空间。


shm_open创建一个新的共享内存区对象或打开一个已存在的共享内存区对象
int shm_open(const char *name ,int oflag,mode_t mode);//成功返回非负描述符,出错返回-1
name:Px_ipc_name(char *);
int shm_unlink(const char *name);


int ftruncate(int fd, off_t length);
对于普通文件,如果该文件大小大于length参数,额外的数据将会被丢弃掉。
对于一个共享内存区对象,把该对象大小设置成length字节


int fstat(int fd, struct stat *buf);//获取共享内存区对象信


小结:Posix共享内存区构筑在mmap函数上。首先指定待打开共享内存区的Posix IPC名字来调用shm_open,取得一个描述符后使用mmap把它映射到内存。


System V共享内存区
shmget创建一个新的共享内存区,或者访问一个已存在的共享内存区。返回值是其他三个shmXXX函数的调用的内存区对象
int  shmget(key_t key,size_t size,int oflag);//成功返回共享内存区对象,出错返回-1


shmat创建或打开一个共享内存区后,通过shmat把它附接到调用进程的地址空间。
void *shmat(int shmid,const void *shmaddr, int flag);
shamaddr如果是一个空指针,那么系统替调用者选择地址。推荐(可移植)


shmdt当一个进程完成某个共享内存区的使用时,它可调用shmdt断接这个内存区
int shmdt(const void *shmaddr);


shmctl对一个共享内存区的各种操作
int shmctl(int shmid,int cmd,struct shmid_ds *buff);
IPC_STAT    得到共享内存的状态,并存储在buf 中
IPC_SET    如果有足够的权限,就把共享内存的状态改变为buf 结构中的数据
IPC_RMID    删除共享内存段


小结:shmget获取一个标示符;shmat把一个共享内存区附接到调用者进程的地址空间;用IPC_STAT命令调用shmctl:获取共享内存区的大小;用IPC_RMID命令调用shmctl:删除一个共享内存区对象。



door_call函数由客户端调用,它会调用服务器进程的地址空间中执行的一个服务器过程
#include <door.h>
int door_call(int fd,door_arg_t *argp);//成功返回0,出错返回-1


typedef struct door_arg {
char *data_ptr;/* Argument/result data */
size_t data_size; /* Argument/result data size */
door_desc_t *desc_ptr; /* Argument/result descriptors */
uint_t desc_num; /* Argument/result num discriptors */
char *rbuf;/* Result area */
size_t rsize; /* Result size */
} door_arg_t;




door_create建立一个服务器
typedef void Door_server_proc(void *cookie,char *dataptr, size_t datasize,door_desc_t *descptr,size_t nedsc);
int door_create(Door_server_proc *proc,void *cookie,u_int attr);


door_return服务器过程完成工作时通过door_return返回。这会使客户中相关联的door_call调用返回
int door_return(char *dataptr,size_t datasize ,door_desc_t *descptr,size_t *ndesc);


door_cred.服务器过程能够获取每个调用对应的客户凭证
int door_cred(door_cred *cred);
typedef struct door_cred {
uid_t dc_euid;/* Effective uid of client */
gid_t dc_egid;/* Effective gid of client */
uid_t dc_ruid;/* Real uid of client */
gid_t dc_rgid;/* Real gid of client */
pid_t dc_pid;/* pid of client */
int dc_resv[4];/* Future use */
} door_cred_t;


door_info客户可通过调用door_info找出服务器相关的信息
int door_info(int fd,doo_info_t *info);
typedef struct door_info {
pid_t di_target;/* Server process */
door_ptr_t di_proc; /* Server procedure */
door_ptr_t di_data; /* Data cookie */
door_attr_t di_attributes; /* Attributes associated with door */
door_id_t di_uniquifier; /* Unique number */
int di_resv[4];/* Future use */
} door_info_t;


door-sever_create
typedef void Door_create_proc (door_info *);
Door_create_proc *door_server_create(Door_create_proc *proc);


int door_bind(int fd);
int door_unbind(void);
int door_revoke(int fd);
小结:门提供了调用同一台主机上另一个进程中某个过程的能力。
基本的API比较简单。服务器调用door_create创建一个门,并给它关联一个服务器过程,然后调用fattach给该门附接一个文件系统中的路径名。客户对该路径名调用open,然后调用door_call以调用服务器进程中的服务器过程,该服务器过程调用door_return返回。
门允许客户向服务器以及服务器向客户传递描述符。

原创粉丝点击