TA的每日心情 | 开心 2023-5-15 15:14 |
---|
签到天数: 1 天 [LV.1]初来乍到
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来的。而对Unix发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间通信方面的侧重点有所不同。前者对Unix早期的进程间通信手段进行了系统的改进和扩充,形成了“system V IPC”,通信进程局限在单个计算机内;后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制。Linux则把两者继承了下来,如图示:
/ _$ F7 {8 @, t. x8 A
; M" q) M0 \7 ^其中,最初Unix IPC包括:管道、FIFO、信号;System V IPC包括:System V消息队列、System V信号灯、System V共享内存区 osix IPC包括:Posix消息队列、Posix信号灯、Posix共享内存区。有两点需要简单说明一下: |- j7 s3 ?3 Q" e- t
! s4 R: E' R; [2 E; N' W8 a7 C1)由于Unix版本的多样性,电子电气工程协会(IEEE)开发了一个独立的Unix标准,这个新的ANSI Unix标准被称为计算肪车目梢浦残圆僮飨低辰缑妫≒SOIX)。现有大部分Unix和流行版本都是遵循POSIX标准的,而Linux从一开始就遵循 POSIX标准;; I9 A8 @1 ^% `! S: h
- m1 h$ N% I7 i* M: H3 S* {2 F! ?
. j$ N0 G" F1 g4 k
2)BSD并不是没有涉足单机内的进程间通信(socket本身就可以用于单机内的进程间通信)。事实上,很多Unix版本的单机IPC留有BSD的痕迹,如4.4BSD支持的匿名内存映射、4.3+BSD对可靠信号语义的实现等等。( Q% p0 {7 G/ O6 H+ z- Y% `+ x4 }
9 V( G0 R6 K2 N/ V: |9 w
linux下进程间通信的几种主要手段简介:8 I, E1 y4 c9 v5 k! z1 K9 \5 L
1.管道
$ j. z! v0 R/ P: r: [9 \6 X/ J, e0 i: `3 b
管道是进程间通信中最古老的方式,它包括无名管道和有名管道两种,前者可用于具有亲缘关系进程间的通信,即可用于父进程和子进程间的通信,后者额克服了管道没有名字的限制,因此,除具有前者所具有的功能外,它还允许无亲缘关系进程间的通信,即可用于运行于同一台机器上的任意两个进程间的通信。8 o1 U& k5 n8 D6 j; H
8 S: ^, ?% d( S1 O无名管道由pipe()函数创建:8 ^' w- n' e3 V0 U
% R! u% h; o' H
#include
% _- ^1 x, X6 W9 ~) `
. J$ F+ T9 g" ]0 Q! S$ tint pipe(int filedis);
* y, i2 K5 J& g9 u! X8 o( F! z% ~: \, x4 ?; ~5 I
参数filedis返回两个文件描述符:filedes[0]为读而打开,filedes为写而打开。filedes的输出是filedes[0]的输入。! {4 p1 M) y) q1 B G( b' ~
$ X& Q. |- B0 @% h! e6 N在Linux系统下,有名管道可由两种方式创建:命令行方式mknod系统调用和函数mkfifo。下面的两种途径都在当前目录下生成了一个名为myfifo的有名管道:4 G0 N% ]9 ~: t: ~+ _. f! y7 I; V
" L \* @& E( [/ W# i* @0 F方式一:mkfifo("myfifo","rw");
. R8 q6 W7 x7 e$ b
: L4 [- w0 b. W1 K- q方式二:mknod myfifo p; @& H; p" d* T4 F) y
8 L/ L# p% f/ h
生成了有名管道后,就可以使用一般的文件I/O函数如open、close、read、write等来对它进行操作。. {5 e& n) y0 Q0 {+ }3 `
. q% ~+ s7 ^( f, f* i% h- }) b2.消息队列# x9 X i, t1 G6 K, {
0 T2 I( F) |# Z消息队列是消息的链接表,包括Posix消息队列system V消息队列。消息队列用于运行于同一台机器上的进程间通信,它和管道很相似,有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。% O' ?9 L" U' y0 v8 f1 L9 @: y
4 c9 b8 y* L& c" z- B
我们可以用流管道或者套接口的方式来取代它。
6 r+ V6 [$ u+ j' I" I* c0 K
9 u: S6 F+ M6 \3.共享内存
4 W3 K: ^+ G# V. Y; h8 d. D. q. j- c8 N; m, i
共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据不需要在不同的进程间复制。通常由一个进程创建一块共享内存区,其余进程对这块内存区进行读写。共享内存往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。. w! I v Z* g0 J) q
6 A' [" `2 N& m2 ~$ |首先要用的函数是shmget,它获得一个共享存储标识符。
9 g( Y9 Y; C" K" F, k( l' @0 I1 p+ C+ y0 F7 Q) _5 \# ]
#include4 J/ x3 a1 w9 q
, Q9 B2 ]- l! C5 C" D#include
. X# k' Z, _. A5 a, r5 L7 _2 X
8 K* m# C& ?8 I! U* w/ K4 K4 @, B#include r' w4 V8 m" t/ b. X9 Q$ x- u
+ a, X7 q4 s0 }5 x: Mint shmget(key_t key, int size, int flag);: T# Z5 v) A' |7 |0 S7 j c a2 N. i
8 Q% t& D! `5 }这个函数有点类似大家熟悉的malloc函数,系统按照请求分配size大小的内存用作共享内存。linux系统内核中每个IPC结构都有的一个非负整数的标识符,这样对一个消息队列发送消息时只要引用标识符就可以了。这个标识符是内核由IPC结构的关键字得到的,这个关键字,就是上面第一个函数的key。数据类型key_t是在头文件sys/types.h中定义的,它是一个长整形的数据。在我们后面的章节中,还会碰到这个关键字。
$ ^/ t. |+ \/ J' Q0 X9 ]
3 h7 O; f; @$ g q6 C# e当共享内存创建后,其余进程可以调用shmat()将其连接到自身的地址空间中。
- Y4 x& N4 Y2 a' I0 m! D$ f9 z8 e
void *shmat(int shmid, void *addr, int flag);
0 K, V5 p N. b, |9 u
6 z- V) ~1 ~( c% H( s3 wshmid为shmget函数返回的共享存储标识符,addr和flag参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址,进程可以对此进程进行读写操作。% p! _- j8 n x$ \ Q. X/ J
2 F+ F- W# m1 R b0 @$ T$ u6 d/ }
使用共享存储来实现进程间通信的注意点是对数据存取的同步,必须确保当一个进程去读取数据时,它所想要的数据已经写好了。通常,信号量被要来实现对共享存储数据存取的同步,另外,可以通过使用shmctl函数设置共享存储内存的某些标志位如SHM_LOCK、SHM_UNLOCK等来实现。
1 @' \$ ]3 t7 f8 A6 q( t3 ]; j/ n% Q: i
4. 信号量
5 [* O, ^8 W) H/ G
: \; M2 [% [* C. G g* c信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是前一节的共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:5 [5 B+ y" }1 E7 {' W
" s H% T) n8 X
(1) 测试控制该资源的信号量。/ z, {, j u( }$ @
8 k7 I$ D# _9 F. y
(2) 若此信号量的值为正,则允许进行使用该资源。进程将进号量减1。6 @7 A$ F* N1 J! _6 f7 [
& H8 v! @, @' K(3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。: ]7 Y: Z' v+ n3 I
3 K7 r0 x, I6 X$ v
(4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。
* d& f/ ~$ |& g* x5 T# [' ]% [+ P0 J* `) f) N, Y3 s
维护信号量状态的是Linux内核操作系统而不是用户进程。我们可以从头文件/usr/src/linux/include/linux /sem.h中看到内核用来维护信号量状态的各个结构的定义。信号量是一个数据集合,用户可以单独使用这一集合的每个元素。要调用的第一个函数是 semget,用以获得一个信号量ID。
# A3 W, d9 Y q& s1 H. s3 i, X: V
#include
4 d* v. `6 T/ [7 i! t1 J' g% F+ x" J3 @- ]$ I, {, S) U
#include
. d9 d, k9 F$ S+ P$ G) X3 l+ z, Q: Q: P0 K
#include
* |5 B* d* C. u; a- y9 f/ L' i o$ ^% ]9 D6 A0 u
int semget(key_t key, int nsems, int flag);
; A8 u9 e5 ~& a4 G' e2 Z( g; m5 [7 l" t& l
key是前面讲过的IPC结构的关键字,它将来决定是创建新的信号量集合,还是引用一个现有的信号量集合。nsems是该集合中的信号量数。如果是创建新集合(一般在服务器中),则必须指定nsems;如果是引用一个现有的信号量集合(一般在客户机中)则将nsems指定为0。
h- G2 C7 i' c* u; M2 R1 ?$ @8 U' O F C% u: y! X
sEMCtl函数用来对信号量进行操作。
0 A$ R: j1 K( C/ v/ n9 P0 Z+ c0 b2 T
" i3 { m+ O% p2 E. xint semctl(int semid, int semnum, int cmd, union semun arg);* u& e; }" X9 b, s+ ]% B
' l! J$ s# o S# ~# U
不同的操作是通过cmd参数来实现的,在头文件sem.h中定义了7种不同的操作,实际编程时可以参照使用。1 S0 k; \2 N \7 P0 u8 m4 I1 I
5 q9 m+ B% p# R
semop函数自动执行信号量集合上的操作数组。" W6 y# V9 \, e2 S
4 o- e9 x- y; ~int semop(int semid, struct sembuf semoparray[], size_t nops);
d& V! L& w, K0 K( p+ j; y* m, r9 X6 O5 [% ]
semoparray是一个指针,它指向一个信号量操作数组。nops规定该数组中操作的数量。
" c5 [8 N! r" C3 u% @; L& `
& j4 e+ Q' D! w0 v; o- v下面,我们看一个具体的例子,它创建一个特定的IPC结构的关键字和一个信号量,建立此信号量的索引,修改索引指向的信号量的值,最后我们清除信号量。
9 T% I: X5 e6 H& F, b4 {9 L- U$ O$ H2 o4 t& E
5.套接口
, c+ m6 X( g! |0 O% Y
* C7 l8 a3 Q) a: Y- ? ^/ W3 w4 H套接口(socket)编程是实现Linux系统和其他大多数操作系统中进程间通信的主要方式之一。我们熟知的WWW服务、FTP服务、 TELNET服务等都是基于套接口编程来实现的。除了在异地的计算机进程间以外,套接口同样适用于本地同一台计算机内部的进程间通信。
* i/ b! g. Z, x8 Q [1 c7 k! a" r$ o, j- A2 t) j
|
|