您的位置:首页 > 运维架构 > Linux

Linux 进程通信 和 IO模型

2013-09-11 22:16 295 查看
主要内容:

第一部分 信号

第二部分 PIPE 和 FIFO

第三部分 消息队列

第四部分 信号量

第五部分 共享内存

第六部分 I/O模型

概念:

在先了解Linux进程间通讯时,需要首先了解几个概念:

1)随进程持续:IPC一直存在,直到打开IPC的最后一个进程关闭

2)随内核持续:IPC一直存在,直到系统重启或删除该对象为止

3)随文件系统持续:IPC一直存在,直到删除该对象为止。

第一部分 信号

Linux 信号:

分类:

(1)可靠信号与不可靠信号 或实时信号与非实时信号

(2)不可靠信号信号值小于SIGRTMIN

(3)非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。

(4)Kill –l 查看系统支持的所有信号,信号在进程的生命周期有意义

信号的处理方式:

(1)忽略信号

(2)捕获信号:当信号发生时,执行对应的信号处理函数

(3)执行缺省操作:前32种信号每种都有各自的缺省操作函数,后32种信号缺省操作就是进程终止。

(4) SIGKILL和SIGSTOP不能被忽略也不能被捕获

信号的发送:kill()、raise()、 sigqueue()、alarm()、setitimer()、abort().

(1) int kill (pid_t pid,int signo) 发送信号到任何一个进程或进程组

(2) Int raise (int signo) 发送信号到进程本身

(3) Int sigqueue (pid_t pid, int sig, const union sigval val)发送信号到任何进程,支持信号带有参数。

注:sigqueue()发送非实时信号时,第三个参数包含的信息仍然能够传递给信号处理函数; 但是不支持排队。

信号的安装:signal()、sigaction()

(1) void (*signal (int signum, void (*handler)) (int))) (int)

typedef void (*sighandler_t)(int)

sighandler_t signal(int signum, sighandler_t handler))

(2) sigaction()

int sigaction (int signum,const struct sigaction *act,struct sigaction *oldact));

信号集操作函数和信号阻塞未决函数(略)

使用信号注意事项:

1)防止不该丢失的信号丢失。不可靠信号只注册一次

2) 程序的可移植性:尽量遵循POSIX

3)程序的安全性:信号处理函数需要使用可重入函数

a. 不应该调用使用了静态变量的库函数

b. 不要调用malloc和free

c. 不要调用标准IO函数

d. 进入处理函数时,首先要保存errno,返回时再恢复。

e. longjmp和siglongjmp不能保证安全。

4) 信号对系统调用的影响:可以中断系统调用。

again:

if ((n = read (fd, buf, BUFFSIZE)) < 0)

{

if (errno == EINTR) goto again;

}

sleep 和 alarm:

1)在某些系统中sleep是通过alarm 实现的,如果两个交叉使用会得不到想要的结果。

2)可以使用nanosleep 代替sleep

sigsetjmp和 siglongjmp :

如果信号处理函数不可避免的要使用setjmp或longjmp需要用sigsetjmp和siglongjmp 代替

第二部分 PIPE 和 FIFO

PIPE

函数:int pipe (int fd[2])

特点:

(1)管道是半双工的,双工通信时,需要建立起两个管道

(2)只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)

(3)数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加到管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

(4)支持阻塞、非阻塞、异步IO

注意事项:

(1)当写端不存在的时候,读取管道返回0,否则系统调用阻塞直到有数据返回,返回的数据小于等于要读取的字节数

(2)当读端不存在的时候,写管道将会产生SIGPIPE信号,当写小于等于PIPE_BUF大小的数据时保证是原子操作,当大于PIPE_BUF的时候,不保证原子操作。(man 7 pipe)(POSIX.1中规定PIPE_BUF不小于512,Linux下一般为4096,Linux Pipe中最大容量65536字节)

(3)移植性:在某些系统中实现了双工管道,但是为了移植性不建议使用



有名管道(FIFO)

int mkfifo (const char * pathname, mode_t mode)

第一个参数是一个普通的路径名,即创建后FIFO的名字。路径名只能位于本地文件系统中,并且如果路径名是一个已经存在的路径名时,会返回EEXIST错误。

第二个参数是打开模式。

打开规则(open):

(1)读打开:阻塞直到有进程为写而打开

(2)写打开:阻塞直到有进程为读而打开

读写规则类似于pipe()



PIPE或FIFO汇总(属于随进程持续IPC)

当前操作现存操作阻塞IO非阻塞IO
读打开FIFO写打开返回OK返回OK
没有写打开阻塞直到写打开返回OK
写打开FIFO读打开返回OK返回OK
没有读打开阻塞直到打开返回ENXIO错误
读空pipe或FIFOpipe或FIFO 写打开阻塞直到pipe 或FIFO中存在数据或者关闭写返回EAGAIN
pipe或FIFO 没有写打开返回0(文件结束)返回0(文件结束)
写pipe或FIFOpipe或FIFO读打开--PIPE_BUF --PIPE_BUF
pipe或FIFO没有打开SIGPIPESIGPIPE
OPEN_MAX: 最大描述符数, getconf OPEN_MAX ulimit (bsh/ksh)/ limit (csh)

第三部分 消息队列

消息队列:

目前系统中有两种消息队列:POSIX消息队列和系统V消息队列,目前系统V消息队列被广泛使用,但是考虑到可移植性,新开发程序尽量用POSIX消息队列。

不同点:

(1)当读取消息时,POSIX总是返回最早的最高优先级消息,而系统V 可以返回任何类型的消息

(2)当一个消息放到空队列中POSIX消息队列可以产生信号或者新线程,而系统V没有提供类似的支持。

相同点:它们都是随内核持续的,只有重启系统或者调用删除函数才能从系统中删除消息队列

POSIX消息队列

(1)消息队列可以看作一个单向链表

(2)消息头中含有队列的两个属性:队列允许的最大消息数量和一个消息的最大大小

(3)消息的长度可以为0,这样就没有了数据项



基本操作函数:

mq_open: 创建或打开一个现存的消息队列。

mq_close:关闭一个消息队列

mq_unlink:删除一个消息队列,只有在引用计数为0时,才会真正的删除消息队列。

mq_getattr/mq_setattr:读取或设置消息队列属性。

消息队列属性:

struct mq_attr

{

long mq_flags:队列标志, 0或O_NONBLOCK

long mq_maxmsg:队列中运行的最大消息数

long mq_msgsize:队列中消息的最大大小

long mq_curmsgs:当前消息队列中消息的数量

}

基本操作函数:

发送消息:

Int mq_send(mqd_t mqds, const char* ptr, size_t len, unsigned int prio);

接收消息:

ssize_t mq_send(mqd_t mqds, const char* ptr, size_t len, unsigned int* priop);

Limits:

MQ_OPEN_MAX:进程可以打开的最大消息队列数

MQ_PRIO_MAX: 最大优先级加1的值,POSIX要求不小于32

另外:

mq_maxmsg: 受HARD_MAX限制(x86是32768)

mq_msgsize:受INT_MAX限制(x86是2147483647)

其它特性:

(1)mq_notify: 实现信号或线程的异步调用。也可以通过AIO实现

(2)支持mmap

系统V消息队列



struct msqid_ds

{

struct ipc_perm msg_perm; /* 权限信息 */

msgqnum_t msg_qnum; /* 队列中的消息数目 */

msglen_t msg_qbytes; /* 队列中允许的最大字节数 */

pid_t msg_lspid; /* 上次发送的进程id */

pid_t msg_lrpid; /* 上次接收的进程id */

time_t msg_stime; /* 上次发送的时间 */

time_t msg_rtime; /* 上次接收的时间 */

time_t msg_ctime; /* 上次更改时间 */

. . .

};

创建或获得消息队列:

int msgget (key_t key, int flag);

其中key可以为IPC_PRIVATE 或有下面函数生成。

key_t ftok (char*pathname, char proj);

发送消息:

int msgsnd (int msqid, const void *ptr, size_t nbytes, int flag);

消息体格式如下:

struct msgbuf

{

long mtype; //消息类型

long mtext[];//消息数据 由用户自由定义

}

其中:消息类型必须大于0

如果flag不是IPC_NOWAIT 时,由可能会发生阻塞。

阻塞条件:

1) 队列中的消息字节数已经达到限制(msg_qbytes)

2) 消息的个数已经达到限制。

当满足以下条件时,将不再阻塞:

1)足够的空间存在

2)消息队列已经被删除,返回EIDRM错误

3)被信号中断,返回EINTR

接收消息:

ssize_t msgrcv (int msqid, void *ptr, size_t nbytes , long type, int flag);

接收消息说明:

Flag:

IPC_NOWAIT 如果没有满足条件的消息,调用立即返回,errno=ENOMSG

IPC_EXCEPT 与type >0配合使用,返回队列中第一个类型不为type的消息

IPC_NOERROR 如果队列中满足条件的消息内容大于所请求的nbytes字节,则把该消息截断,截断部分将丢失。

消息类型:

type ==0: 队列中第一个消息被返回

type > 0: 返回类型等于type的消息

type < 0: 返回类型小于等于type绝对值的消息(首先返回最低类型)

如果flag不是IPC_NOWAIT 时,可能会被阻塞。如果设置了IPC_NOWAIT则返回ENOMSG

阻塞返回的条件:

1)队列中存在满足条件的消息

2)消息队列已经被删除,返回EIDRM

2)被信号中断, 返回EINTR

控制:

int msgctl (int msqid, int cmd, struct msqid_ds *buf ); 可用户获取队列状态、更改队列参数已经删除队列,注意:只有创建者和超级用户才可以更改参数或删除队列。

Limit:

MSGMAX: 一个消息的最大字节数(8192)

MSGMNB: 一个消息队列的最大字节数(16384)

第四部分 信号量

信号量(semaphore):

Linux下共有三种类型的信号量

1) POSIX 基于文件系统的有名信号量

2) POSIX基于内存的无名信号量

3) System V信号量(内核中实现)



有名信号量:

1)具有内核持续性

2)可以在无亲属关系的进程之间使用。

有名信号量的基本操作函数:

1) 打开或创建信号量(Linux创建的信号量可以在/dev/sem/下找到,有些系统会不一样)

sem_t *sem_open(const char *name, int oflag, /*mode_t mode, unsigned int value*/);

2)关闭信号量(程序退出会自动关闭)

int sem_close(sem_t *sem)

3)删除信号量(引用计数为0时,才真正删除)

Int sem_ulink(const char* name);

4)获得信号量

int sem_wait(sem_t *sem);

Int sem_trywait(sem_t *sem);

5)释放信号量

int sem_post(sem_t *sem);

无名信号量:

1) 用在亲属关系的进程或线程间

2) 有的地方说是内核持续性,实际上一直存在知道用到的共享内存消失。对于多线程就是进程结束,对于多进程就是所有进程结束

初始化和创建函数:

int sem_init(sem_t *sem, int shared, unsigned int value);

参数shared为0表示在不同进程间使用,为1表示用在线程间。

Int sem_destroy(sem_t *sem);

Limits:

SEM_NSEMS_MAX :一个进程可以打开的最大信号量的数目,POSIX规定不小于256

SEM_VALUE_MAX:信号量的最大值,POSIX规定不小于32767

系统V信号量:

1)具有内核持续性

2)可以在无亲属关系的进程之间使用。

3)每一个信号量都具有一个信号集。

struct sem

{

ushort semval; //信号量的值,非负数

short sempid; //上次成功访问sem_op的PID

ushort_t semncnt;//等待semval>当前值的数量

Ushort_t semzcnt://等待semval为0的数量

}



基本操作函数:

1)创建或打开信号量

int semget (key_t key, int nsems, int oflag);

此函数对sem_otime 赋值0,对sem_nsems 赋值nsems,而具体的信号集并没有初始化。需要调用semctl的SETVAL或SETALL命令进行初始化。

注意:由于创建并初始化分两步执行,不能保证原子操作,所以非创建进程需要判断sem_otime 是否为0来判断信号量是否被初始化。只有初始化后才可以调用信号量操作函数

2)信号量操作函数

Int semop (int semid, struct sembuf* opsptr, size_t nops);

其中

struct sembuf

{

unsigned short sem_num; /* set 中成员(0, 1, ..., nsems-1) */

short sem_op; /* 具体操作 (负数, 0, 或正数) */

short sem_flg; /* IPC_NOWAIT, SEM_UNDO */

};

nops:为opsptr数组的大小

说明:

a. SEM_UNDO:如果设置了此标志,那么进程结束的时候,操作将被取消。也就是说,进程如果没有释放资源就退出,那么内核将将释放。

b. sem_op 大于0表示要释放sem_op数目的资源, sem_op等于0可以用于资源是否用完的测试,小于0表示要获得负的sem_op数目的资源.

c. semop保证了对多个资源操作的原子性。

3)信号量控制或删除函数。

int semctl (int semid,int semnum,int cmd,union semun arg)

该调用实现各种控制操作,

参数cmd指定具体的操作类型;

参数semnum指定信号量SET成员的索引操作,

参数arg用于设置或返回信号集信息。

Limits:

SEMMNS:系统中最大SEMID的个数。

SEMMSL:每个SEMID中信号量的最大数目。

SEMMNI: 每个信号量的SET的最大数目

第五部分 共享内存

共享内存-最快的进程间通讯方式

共分三部分介绍

1) mmap介绍

2) POSIX共享内存

3) SYSTEM V共享内存

mmap介绍

主要用于一下几种情况:

1) 为常规文件提供内存映射的IO访问。

2) 提供匿名内存映射

3) POSIX共享内存

主要操作函数如下:

void *mmap (void* addr, size_t len, int prot, int flags, int fd, off_t offset);

addr: 内存开始映射地址,通常是NULL表示让内核选择,addr 需要按照系统页对齐。

len: 内存映射的大小或长度

fd:要映射文件的句柄

offset:文件的偏移, len、fd、offset指定了映射的文件大小和偏移

prot:取PROT_READ, PROT_WRITE, PROT_EXEC, PROT_NONE

flags: 一般取MAP_SHARED, MAP_PRIVATE, MAP_FIXED。其中MAP_FIXED一般不使用,和addr连用表示如果地址不可用则返回失败。MAP_ANON表示匿名映射此时fd取-1,仅仅是内存映射而不涉及具体文件操作,可用于亲属关系的进程间通讯。

int munmap (void *addr, size_t len); 删除映射,对于常规文件,会把修改保存到文件中。而对于MAP_PRIVATE标志的常规文件映射,会把修改丢掉不保存到文件。

int msync (void *addr, size_t len, int flags) 把 MAP_SHARED映射的常规文件的修改保存到文件中。

flags:取MS_ASYNC, MS_SYNC, MS_INVALIDATE

常规文件映射说明见下图:

文件大小为5000字节,映射大小为15000字节, 系统页大小为4096字节。



其它说明:

mlock 或mlockall 使内存不被交换出去,常驻内存。

munlock或munlockall 是对应的解锁函数。

得到系统分页大小:

getpagesize()或sysconf(_SC_PAGESIZE)

POSIX共享内存

基本操作函数

创建或打开共享内存

int shm_open (const char* name, int oflag, mode_t mode);

删除共享内存

int shm_unlink (const char *name);

改变文件或内存对象的大小,(刚创建的共享内存为0)

int ftruncate (int fd, off_t lenth);

注意:对于常规文件,由于标准中改变文件大小没有对内容进行定义,所有不建议使用。

获得共享内存信息:

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

共享内存和文件映射的异同:



SYSTEM V共享内存

内核中维护了如下数据结构:

struct shmid_ds

{

struct ipc_perm shm_perm; /* 权限信息 */

size_t shm_segsz; /* 共享内存大小 */

pid_t shm_lpid; /* attach或detach的进程id */

pid_t shm_cpid; /* 创建者进程id */

shmatt_t shm_nattch; /* 当前attach的数量 */

time_t shm_atime; /* last-attach time */

time_t shm_dtime; /* last-detach time */

time_t shm_ctime; /* last-change time */

. . .

};

基本操作函数:

创建或获得共享内存

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

size:创建者指定共享内存的大小,而打开现有共享内存时设置为0

oflag: IPC_CREAT、IPC_EXCL

把共享内存attach到进程空间

void *shmat (int shmid, const void *shmaddr, int flag)

把共享内存从进程空间detach

int shmdt (const void*shmaddr);

读取状态或删除共享内存

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

查看系统中的共享内存ipcs –m

共享内存具有内核持续性。

Limits 略

第六部分 I/O模型

Linu基本IO模型

系统共提供四种IO,见下图:



同步阻塞IO模型

说明:用户程序执行一个系统调用,使得应用程序阻塞,直到系统调用完成或发生错误。



同步非阻塞IO模型

说明:用户通过设置O_NONBLOCK标志来实现非阻塞,如果IO操作不能立即满足则会返回一个错误码(通常为EAGAIN或EWOULDBLOCK)



异步阻塞IO模型

说明:通过阻塞select系统调用来阻塞实际的系统调用直到完成。 select可以同时处理多个系统调用



异步非阻塞IO模型(AIO)

说明:当请求(read)发出后,系统调用立即返回,进程可以做其它的处理,当请求在内核中完成后,就会产生一个信号或启动一个基于线程的回调函数来进行IO处理。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: