您的位置:首页 > 编程语言

UNIX环境高级编程(第15章 进程间通信)

2015-06-30 10:12 399 查看
进程间通信的方式包括管道、消息队列、信号量和共享存储。通过这些机制,同一台计算机上运行的进程可以互相通信。

1管道

管道是UNIX系统IPC的最古老形式,并且所有UNIX系统都提供此种通信机制。

管道有两种局限性:

1)它们是半双工的,即数据只能在一个方向上流动。

2)它们只能在具有公共祖先的进程之间使用。通常一个管道由一个进程创建,然后进程调用fork,此后父、子进程之间就可以应用该管道。

管道的两个特点:(转载)

1)单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,而是自立门户,单独构成一种文件系统,并且只存在于内存中。

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

1.1创建管道函数:

#include <unistd.h>

int pipe(int filedes[2]);

返回值:若成功则返回0,若出错则返回-1

说明:由参数filedes返回两个文件描述符:filedes[0]为读而打开;filedes[1]为写而打开。
1.2 popen和pclose函数

#include <stdio.h>

FILE *popen(cosnt char *cmdstring, const char *type);

返回值:若成功则返回文件指针,若出错则返回NULL

int pclose(FILE *fp);

返回值:cmdstring的终止状态,若出错则返回-1

说明:由参数filedes返回两个文件描述符:filedes[0]为读而打开;filedes[1]为写而打开。
 1.3协同进程
   当一个程序产生某个过滤程序的输入,同时又读取该过滤程序的输出,则该过滤程序就成为协同进程。

/*父进程通过管道向子进程传送数据*/
#include "apue.h"

int main(void)
{
int n;
int fd[2];
pid_t pid;
char line[MAXLINE];

if (pipe(fd) < 0)
err_sys("pipe error");

if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid > 0)/*父进程*/
{
close(fd[0]);
write(fd[1], "hello, pipe\n", 12);
}
else/*子进程*/
{
close(fd[1]);
n = read(fd[0], line, MAXLINE);
write(STDOUT_FILENO, line, n);
}

exit(0);
}


/*使父、子进程同步的例程*/
#include "apue.h"

static int pfd1[2], pfd2[2];

void TELL_WAIT(void)
{
if (pipe(pfd1) < 0 || pipe(pfd2) < 0)
err_sys("pipe error");
}

void  TELL_PARENT(void)
{
if (write(pfd2[1], "c", 1) != 1)
err_sys("write error");
}

void  WAIT_PARENT(void)
{
char c;

if (read(pfd1[0], &c, 1) != 1)
err_sys("read error");

if (c != 'p')
err_quit("WAIT_PARENT: incorrect data");
}

void TELL_CHILD(pid_t pid)
{
if (write(pfd1[1], "p", 1) != 1)
err_sys("write error");
}

void WAIT_CHILD(void)
{
char c;
if (read(pfd2[0], &c, 1) != 1)
err_sys("read error");

if (c != 'c')
err_quit("WAIT_CHILD: incorrect data");
}
//对两个数求和的简单过滤程序add2
#include "apue.h"

int main(void)
{
int n, int1, int2;
char line[MAXLINE];

while ((n = read(STDIN_FILENO, line, MAXLINE)) > 0)
{
line
= 0;
if (sscanf(line, "%d%d", &int1, &int2) == 2)
{
sprintf(line, "%d\n", int1 + int2);
n = strlen(line);
if (write(STDOUT_FILENO, line, n) != n)
err_sys("write error");
}
else
{
if (write(STDOUT_FILENO, "invalid args\n", 13) != 13)
err_sys("write error");
}
}
exit(0);
}
//驱动add2过滤程序的程序
#include "apue.h"

static void sig_piep(int);

int main(void)
{
int n,fd1[2], fd2[2];
pid_t pid;
char line[MAXLINE];

if (signal(SIGPIPE, sig_piep) == SIG_ERR)
err_sys("signal error");

if (pipe(fd1) <0 || pipe(fd2) <0)
err_sys("pipe error");

if ((pid = fork()) < 0)
{
err_sys("fork error");
}
else if (pid > 0)
{
close(fd1[0]);
close(fd2[1]);
while (fgets(line, MAXLINE, stdin) != NULL)
{
n = strlen(line);
if (write(fd1[1], line, n) != n)
err_sys("write error to pipe");
if ((n = read(fd2[0], line, MAXLINE)) < 0)
err_sys("read error from pipe");
if (n == 0)
{
err_msg("child closed pipe");
break;
}

line
= 0;
if (fputs(line, stdout) == EOF)
err_sys("fputs error");
}

if (ferror(stdin))
err_sys("fgets error on stdin");

exit(0);
}
else
{
close(fd1[1]);
close(fd2[0]);

if (fd1[0] != STDIN_FILENO)
{
if (dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO)
err_sys("dup2 error to stdin");
close(fd1[0]);
}

if (fd2[1] != STDOUT_FILENO)
{
if (dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO)
err_sys("dup2 error to stdout");
close(fd2[1]);
}

if (execl("./add2", "add2", (char *)0) < 0)
err_sys("execl error");
}
exit(0);
}

static void sig_pipe(int signo)
{
printf("SIGPIPE caught\n");
exit(1);
}


2命名管道

管道的第二个局限是只能在具有公共祖先的进程之间使用,因为管道没有名字。而命名管道(named pipe或FIFO)提供一个路径名与之关联,以FIFO的文件类型存在于文件系统中。故而,只要能访问到该路径,不相关的进程通过FIFO也能交换数据。从而克服了管道的第二个局限。
命名管道(FIFO)有两种用途:
1)FIFO由shell命令使用以便将数据从一条管道线传送到另一条,为此无需创建中间临时文件。
2)FIFO用于客户进程-服务器进程应用程序中,以在客户进程和服务器进程之间传递数据
创建FIFO
#include <sys/stat.h>

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

返回值:若成功则返回0,若出错则返回-1
         一旦已经用mkfifo创建了一个FIFO,就可用open打开它。其实,一般的文件I/O函数(close、read、write、unlink等)都可用于FIFO。 

XSI IPC    

有三种IPC称之为XSI
IPC,即消息队列、信号量以及共享存储器,它们之间有很多相似之处。
XSI,即X/OpenSystem Interface,X/Open系统接口。

0.1标识符和键

每个内核中的IPC结构(消息队列、信号量或共享存储段)都用一个非负整数的标识符加以引用。
标识符是IPC对象的内部名。为使多个合作进程能够在同一IPC对象上会合,需要提供一个外部名方案。为此使用了键(key),每个IPC对象都与一个键相关联,于是键就用作为该对象的外部名。
是IPC对象的外部名。
键的数据类型是基本系统数据类型key_t,通常在头文件<sys/types.h>中被定义为长整型。键由内核变换成标识符。
常见客户进程和服务器进程在同一IPC结构上会合的方法:
(1)客户进程和服务器进程认同一个路径名和项目ID(项目ID是0~255之间的字符值),接着调用函数ftok将这两个值变换为一个键。然后将此键作为一个公用头文件中定义一个客户进程和服务器进程都认可的键。接着服务器进程指定此键创建一个新的IPC结构。
(2)服务器进程可以指定键IPC_PRIVATE创建一个新IPC结构,将返回的标识符存放在某处(例如一个文件)以便客户进程取用。

#include <sys/ipc.h>

key_t ftok(const char *path, int id);

返回值:若成功则返回键,若出错则返回(key_t)-1

参数:

path:必须引用一个现存文件。

id:0~255的字符值。当产生键时,只使用id参数的低8位。

0.2权限结构

     XSI IPC为每一个IPC结构设置了一个ipc_perm结构。该结构规定了权限和所有者。它至少包括下列成员:#include
<sys/ipc.h>

struct ipc_perm

{

uid_t uid;//所有者的有效用户ID

gid_t gid;//所有者的有效组ID

uid_t cuid;//创建者的有效用户ID

gid_t cgid;//创建者的有效组ID

mode_t mode;//访问模式

……

}

 

0.3结构限制

三种形式的XSI IPC都有内置限制。这些限制的大多数可以通过重新配置内核而加以更改。
0.4优点和缺点
缺点:   
【1】主要问题:IPC结构是在系统范围内起作用的,没有访问计数。
例如,如果进程创建了一个消息队列,在该队列中放入了几则消息,然后终止,但是该消息队列及其内容并不会被删除。它们余留在系统中直至出现下述情况:由某个进程调用msgrcv或msgctl读消息或删除消息队列;或某个进程执行ipcrm命令删除消息队列;或由正在启动的系统删除消息队列。
【2】第二个问题:这些IPC结构在文件系统中没有名字。
故而不能用open、read、write等系统调用访问或修改它们的特性,不能用ls命令见到IPC对象,不能用rm命令删除它们。为支持它们不得不增加一些全新的系统调用,如msgget、semop、shmat等,不得不增加新的命令,如ipcs、ipcrm等。
优点:
【a】可靠
【b】流是受控的
【c】面向记录
【d】可以用非先进先出方式处理

3消息队列

消息队列是消息的链接表,存放在内核中并由消息队列标识符标识。

每个队列都有一个msqid_ds结构与其相关联

struct msqid_ds

{

struct ipc_perm msg_perm; //规定权限和所有者的结构

msgqunm_t   msg_qunm;//消息在队列中的序号

msglen_t     msg_qbytes;

pid_t        msg_lspid;

pid_t        msg_lrpid;

……

}
1创建一个新队列或打开一个现存队列

#include <sys/msg.h>

int msgget(key_t key, int flag);

返回值:若成功则返回消息队列ID,若出错则返回-1

参数:

key:键值

flag:标志位,可以为IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的或

当只有IPC_CREAT时,若不存在则创建消息队列并返回其ID;若存在,则返回其ID

当只有IPC_EXCL时,不管有无消息队列,都返回-1

当IPC_CREAT | IPC_EXCL时,如果没有消息队列,则创建并返回其ID;若存在则返回-1
2对队列执行多种操作

#include <sys/msg.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

返回值:若成功则返回0,若出错则返回-1

参数:

msqid:消息队列ID

cmd:

IPC_STAT 取此队列的msqid_ds结构,并将它存放在buf指向的结构。

IPC_SET  按由buf指向结构中的值,设置与此队列相关结构的字段。

IPC_RMID从系统中删除该消息队列以及仍在该队列中的所有数据。

buf:msqid_ds结构
3将数据放到消息队列中

#include <sys/msg.h>

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

返回值:若成功则返回0,若出错则返回-1

参数:

msqid:消息队列ID

ptr:指向一个长整型数,它包含了正的整型消息类型,在其后紧跟着消息数据。

   若nbytes为0,则无消息数据;若发送的最长消息是512字节,则可定义下列结构:

   struct mymesg{

   long mtype;    /*消息类型,必须为正整数*/

   char mtext[512]; /*消息数据*/

};

于是ptr就是一个指向mymesg结构的指针。接收者可以使用消息类型以非先进先出的次序取消息。

nbytes:字节大小

flag:标志位,可以为0,也可以为IPC_NOWAIT、MSG_EXCEPT、MSG_NOERROR。

   对发送消息来说,比较有意义的flag是IPC_NOWAIT。在消息队列满时,若指定IPC_NOWAIT则使得msgsnd立即出错返回EAGAIN;若没有指定,则进程阻塞直到下述情况出现为止:

   a)有空间可以容纳要发送的消息。

   b)从系统中删除了此队列。

   c)捕捉到一个信号,并从信号处理程序返回。
4从队列中取用消息

#include <sys/msg.h>

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

返回值:若成功则返回消息的数据部分的长度,若出错则返回-1

参数:

msqid:消息队列ID

ptr:指向一个长整型(返回的消息类型存放在其中),跟随其后的是存放实际消息数据的缓冲区。

nbytes:数据缓冲区的长度

type:消息类型,type值非0用于以非先进先出次序读消息。

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

   type > 0  返回队列中消息类型为type的第一个消息

   type < 0  返回队列中消息类型值小于或等于type绝对值的消息。

flag:标志位,可以为0,也可以为IPC_NOWAIT、MSG_EXCEPT、MSG_NOERROR。
/*消息队列服务器端server.c*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>

#define FILEPATH "/tmp/msg"
#define BUFSIZE 512

#define SERVERTYPE 1
#define CLIENTTYPE 2

/*自定义消息结构*/
struct mymesg{
long mtype;         /*消息类型*/
char mtext[BUFSIZE];
};

int main(void)
{
int fd;
key_t key;
int msgid;
struct mymesg msgbufp;

printf("server: wait client send message...\n");

fd = open(FILEPATH, O_RDONLY | O_CREAT, S_IRUSR | S_IRGRP | S_IROTH);
if (fd == -1)
{
printf("open error\n");
exit(1);
}
close(fd);

/*创建键值*/
if((key = ftok(FILEPATH,0)) == -1)
{
printf("ftok error\n");
exit(1);
}

/*创建一个新队列*/
if((msgid = msgget(key, IPC_CREAT)) == -1)
{
printf("msgget error\n");
exit(1);
}

/*接收客户端消息,并发送消息给客户端*/
while(1)
{
if(msgrcv(msgid, &msgbufp, BUFSIZE, CLIENTTYPE, 0) == -1)
{
printf("msgrcv error\n");
break;
}

if (msgbufp.mtype == CLIENTTYPE)
{
printf("client:%s",msgbufp.mtext);
if('q'==msgbufp.mtext[0]||'Q'==msgbufp.mtext[0])
{
break;
}
}

msgbufp.mtype=SERVERTYPE;
printf("server:");
fgets(msgbufp.mtext, BUFSIZE, stdin);

if(msgsnd(msgid, &msgbufp, BUFSIZE, 0) == -1)
{
printf("msgsnd error\n");
break;
}

if(msgbufp.mtext[0] == 'q' || msgbufp.mtext[0] == 'Q')
break;
}

if(msgctl(msgid, IPC_RMID, NULL) == -1)
{
printf("delete message queue error\n");
}

exit(0);
}
/*消息队列客户端client.c*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>

#define FILEPATH "/tmp/msg"
#define BUFSIZE 512

#define SERVERTYPE 1
#define CLIENTTYPE 2

/*自定义消息结构*/
struct mymesg{
long mtype;         /*消息类型*/
char mtext[BUFSIZE];
};

int main(void)
{
key_t key;
int msgid;
int fd;
struct mymesg msgbufp;

printf("client: send message to server...\n");

fd = open(FILEPATH, O_RDONLY | O_CREAT, S_IRUSR | S_IRGRP | S_IROTH);
if (fd == -1)
{
printf("open error\n");
exit(1);
}
close(fd);

/*创建键值*/
if((key = ftok(FILEPATH, 0)) == -1)
{
printf("ftok error\n");
exit(1);
}

/*创建一个队列*/
if((msgid = msgget(key, 0)) == -1)
{
printf("msgget error\n");
exit(1);
}

while(1)
{
printf("client:");
fgets(msgbufp.mtext,BUFSIZE,stdin);
msgbufp.mtype=CLIENTTYPE;

if(msgsnd(msgid, &msgbufp, BUFSIZE, 0) == -1)
{
printf("msgsnd error\n");
break;
}

if(msgbufp.mtext[0] == 'q' || msgbufp.mtext[0] == 'Q')
break;

if(msgrcv(msgid, &msgbufp, BUFSIZE, SERVERTYPE, 0) == -1)
{
printf("msgrcv error\n");
break;
}

if (msgbufp.mtype == SERVERTYPE)
{
printf("server:%s",msgbufp.mtext);
if(msgbufp.mtext[0] == 'q' || msgbufp.mtext[0] == 'Q')
break;
}
}

exit(0);
}


4信号量

信号量与管道、FIFO及消息队列不同,它是一个计数器,用于多进程对共享数据对象的访问。
为获得共享资源,进程需要执行下列操作:
1)测试控制该资源的信号量
2)若此信号量的值为正,则进程可以使用该资源。进程将信号量值减1,表示使用了一个资源单位。
3)若此信号量的值为0,则进程进入休眠状态,直至信号量值大于0。进程被唤醒后,返回值第1)步。

内核为每个信号量集设置了一个semid_ds结构:

struct semid_ds

{

struct ipc_perm sem_perm;

unsigned short sem_nsems;

time_t       sem_otime;

time_t       sem_ctime;

……

};

每个信号量由一个无名结构表示,至少包含下列成员:

struct

{

unsigned short semval;//信号量值

pid_t         sempid;

unsigned short semncnt;

unsigned short semzcnd;

……

};
1获得一个信号量集ID

#include <sys/sem.h>

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

返回值:若成功则返回信号量ID,若出错则返回-1

参数:

key:键值

nsems:该信号量集中的信号量数。如果是创建新集合(一般在服务器进程中),则必须指定nsems;如果引用一个现存的集合(一个客户进程),则将nsems指定为0.

flag:标志位,可以为(1)IPC_CREAT、IPC_EXCL、IPC_NOWAIT、(2)三者的或操作、(3)0

当只有IPC_CREAT时,若不存在则创建信号量集并返回其ID;若存在,则返回其ID

当只有IPC_EXCL时,不管有无信号量集,都返回-1

当IPC_CREAT | IPC_EXCL时,如果没有信号量集,则创建并返回其ID;若存在则返回-1
2信号量多种操作

#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, ../* union semun arg */);

返回值:

除GETALL以外的所有GET命令都返回相应的值。其他命令返回0。出错返回-1.

参数:

   semid:信号量集标识符

semnum:信号量集中的一个信号量成员,值为0~nsems-1之间。

cmd:有10种命令,IPC_STAT、IPC_SET、IPC_RMID、GETVAL、SETVAL、……..

arg:该参数是可选的,是多个特定命令参数的联合。

   union semun{

int  var;

   struct semid_ds *buf;

   unsigned short *array;

};
3自动执行信号量集合上的操作数组

#include <sys/sem.h>

int semop(int semid, struct sembuf semoparray[], size_t nops);

返回值:若成功则返回0,若出错则返回-1

参数:

semid:信号量集标识符

semoparray:操作数组

   struct sembuf{

       unsigned short sem_num;//信号量成员,值为0~nsems-1

       short sem_op;//指定成员的操作,值为负数、0、正数

       short sem_flg;//IPC_NOWAIT、SEM_UNDO

};

nops:数组中操作的数量(元素个数)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>

int semphore_p(int);
int semphore_v(int);

union semun
{
int val;/* Value for SETVAL */
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};

int main(void)
{
key_t key;
int semid;
pid_t pid;
int i;
int nsems = 1;
union semun semval;

/*创建键值*/
if((key = ftok("15semaphore.c", 0)) == -1)
{
printf("ftok error\n");
exit(1);
}

/*创建信号量集*/
if((semid = semget(key, nsems, IPC_CREAT)) == -1)
{
printf("semget error\n");
exit(1);
}

/*设置信号量的值*/
semval.val = nsems;
if(semctl(semid, 0, SETVAL, semval) == -1)
{
printf("semctl error\n");
exit(1);
}

if((pid = fork()) == -1)
{
printf("fork error\n");
exit(1);
}
else if(pid == 0)
{
int i;
if(semphore_p(semid) == -1)
{
printf("child semphore_p error\n");
exit(1);
}

printf("child start...\n");
for (i = 0; i < 10; i++)
{
printf("%c", 'C');
fflush(stdout);
sleep(1);
}
printf("\nchild end...\n");

if(semphore_v(semid) == -1)
{
printf("child semphore_v error\n");
exit(1);
}

exit(0);
}

if(semphore_p(semid) == -1)
{
printf("parent semphore_p error\n");
exit(1);
}

printf("parent start...\n");
for (i = 0; i < 10; i++)
{
printf("%c", 'P');
fflush(stdout);
sleep(1);
}
printf("\nparent end...\n");

if(semphore_v(semid) == -1)
{
printf("parent semphore_v error\n");
exit(1);
}

if(semctl(semid,0,IPC_RMID,NULL) == -1)
{
printf("semctl rmid error\n");
exit(1);
}

waitpid(pid, NULL, 0);

exit(0);
}

/*P操作,申请一个资源单位*/
int semphore_p(int semid)
{
struct sembuf sem_buf;
sem_buf.sem_num = 0;
sem_buf.sem_op = -1;
sem_buf.sem_flg = SEM_UNDO;
if(semop(semid, &sem_buf, 1) == -1)
return -1;

return 0;
}

/*V操作,释放一个资源单位*/
int semphore_v(int semid)
{
struct sembuf sem_buf;
sem_buf.sem_num = 0;
sem_buf.sem_op = 1;
sem_buf.sem_flg = IPC_NOWAIT;
if(semop(semid, &sem_buf, 1) == -1)
return -1;

return 0;
}
[root]# ./a.out
child start...
CCCCCCCCCC
child end...
parent start...
PPPPPPPPPP
parent end...
[root]#


5共享存储

   共享存储允许两个或更多进程共享一给定的存储区。因为数据不需要在客户进程和服务器进程之间复制,所以这是最快的一种IPC。使用共享存储时要掌握的唯一窍门是多个进程之间对一给定存储区的同步访问。通常,信号量用来实现对共享存储访问的同步。记录锁也可用于这种场合。
    共享存储是存在于内核级别的一种资源,在系统内核为一个进程分配内存地址时,通过分页机制可以让一个进程的物理地址不连续,也可以让一段内存同时分配给不同的进程,共享内存就是通过该原理实现的。
    在shell中可以通过ipcs和ipcrm来查看和删除XSI IPC(共享内存、信号量、消息队列)的状态。

内核为每个共享存储段设置了一个shmid_ds结构

struct shmid_ds

{

struct ipc_perm shm_perm;

size_t         shm_segsz;/*共享存储区字节大小*/

pid_t         shm_lpid;

pid_t         shm_cpid;

shmatt_t      shm_nattch;

time_t        shm_atime;

time_t        shm_dtime;

time_t        shm_ctime;

};
1获得一个共享存储标识符

#include <sys/shm.h>

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

返回值:若成功则返回共享存储ID,若出错则返回-1

参数:

key:键值

size:共享存储段的长度,单位字节。通常将其向上取为系统页长的整数倍。

   如果正在创建一个新段(一般是在服务器进程中),则必须指定其size。如果正在引用一个现存的段(一个客户进程),则将size指定为0.当创建一新段时,段内的内容初始化为0.

flag:标志位,可以为(1)IPC_CREAT、IPC_EXCL、IPC_NOWAIT、(2)三者的或操作、(3)0

当只有IPC_CREAT时,若不存在则创建信共享存储段并返回其ID;若存在,则返回其ID

当只有IPC_EXCL时,不管有无共享存储段,都返回-1

当IPC_CREAT | IPC_EXCL时,如果没有共享存储段,则创建并返回其ID;若存在则返回-1
当flag为0时,表示获取存在的共享内存标识,若共享内存不存在则报错。
注:上述的flag需要再与权限位(如0644等)进行或操作
2对共享存储段执行多种操作

#include <sys/shm.h>

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

返回值:若成功则返回0,若出错则返回-1

参数:

shmid:共享存储段标识符

cmd:有5种命令,IPC_STAT、IPC_SET、IPC_RMID、SHM_LOCK、SHM_UNLOCK,使其在shmid指定的段上执行。

buf:shmid_ds结构地址
3连接共享存储段到进程的地址空间

#include <sys/shm.h>

void *shmat(int shmid, const void *addr, int flag);

返回值:若成功则返回指向共享存储的指针,若出错则返回-1

参数:

shmid:共享存储段标识符

addr:共享存储段连接到调用进程的地址,通常为0,由内核选择地址。

flag:标志位,可为SHM_RDN、SHM_RDONLY、SHM_REMAP、SHM_EXEC,也可为0
4将共享存储与进程地址空间脱离连接

#include <sys/shm.h>

int shmdt(void *addr);

返回值:若成功则返回0,若出错则返回-1
    用shmat函数连接一共享存储段,在概念上与用mmap函数可将一个文件的若干部分映射至进程地址空间类似。两者之间的主要区别是,用mmap映射的存储段是与文件相关联的,而XSI共享存储段则并无这种关联。

/*shm_server.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/stat.h>

#define FILEPATH "/tmp/shm"
#define PAGE_SIZE getpagesize()

union semun
{
int val;
struct semid_ds *buf;
unsigned short  *array;
struct seminfo  *__buf;
};

int semphore_create(int key);
int semphore_delete(int semid);
int semphore_p(int);
int semphore_v(int);

int main(void)
{
int fd;
int shm_id;
int sem_id;
key_t key;
void *addr;

fd = open(FILEPATH, O_RDONLY | O_CREAT, S_IRUSR | S_IRGRP | S_IROTH);
if (fd == -1)
{
printf("open error\n");
exit(1);
}
close(fd);

key = ftok(FILEPATH, 0);
if (key == -1)
{
printf("ftok error\n");
exit(1);
}

/*创建共享存储段,并返回共享存储标识符*/
shm_id = shmget(key, PAGE_SIZE, IPC_CREAT | IPC_EXCL);
if (shm_id == -1)
{
printf("shmget error\n");
exit(1);
}

/*将共享存储段连接到进程地址空间*/
addr = (char *)shmat(shm_id, 0, 0);
if(-1 == (long)addr)
{
printf("shmat error\n");
shmctl(shm_id, IPC_RMID, NULL);
exit(1);
}

/*创建信号量并返回信号量标识符*/
sem_id = semphore_create(key);
if (sem_id == -1)
{
printf("semphore init error\n");
goto out;
}

/*信号量保证共享存储段的同步start...*/
if(semphore_p(sem_id) == -1)
{
printf("semphore_p error\n");
goto out;
}

/*对共享存储段的操作start...*/
.........
.........
/*对共享存储段的操作end...*/

/*信号量保证共享存储段的同步end...*/
if(semphore_v(sem_id) == -1)
{
printf("semphore_v error");
}

out:
/*解除共享存储段与进程地址空间的联系*/
if(shmdt(addr) == -1)
{
printf("shmdt error\n");
}

/*删除共享存储段*/
if(shmctl(shm_id, IPC_RMID, NULL) == -1)
{
printf("shmctl error\n");
exit(1);
}

exit(0);
}

int semphore_create(int key)
{
int ret;
int sem_id;
union semun semun_val;

/*创建信号量集*/
sem_id = semget(key, 1, IPC_CREAT);
if (sem_id == -1)
{
printf("semget error\n");
return -1;
}

/*设置信号量值*/
semun_val.val = 1;
ret = semctl(sem_id,0,SETVAL, semun_val);
if (ret ==-1)
{
printf("semctl error\n");
return -1;
}

return sem_id;
}

int semphore_delete(int semid)
{
if(semctl(semid, 0, IPC_RMID, NULL) == -1)
{
printf("semctl rmid error\n");
return -1;
}

return 0;
}

int semphore_p(int semid)
{
struct sembuf sem_buf;
sem_buf.sem_num =  0;
sem_buf.sem_op  = -1;
sem_buf.sem_flg = SEM_UNDO;

if(semop(semid, &sem_buf, 1) == -1)
return -1;

return 0;
}

int semphore_v(int semid)
{
struct sembuf sem_buf;
sem_buf.sem_num = 0;
sem_buf.sem_op  = 1;
sem_buf.sem_flg = SEM_UNDO;
if(semop(semid, &sem_buf, 1) == -1)
return -1;

return 0;
}
/*shm_client.c*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>

#define FILEPATH "/tmp/shm"

union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};

int semphore_p(int);
int semphore_v(int);

int main(void)
{
int fd;
int shm_id;
int sem_id;
key_t key;
void *addr;

fd = open(FILEPATH, O_RDONLY | O_CREAT, S_IRUSR | S_IRGRP | S_IROTH);
if (fd == -1)
{
printf("open error\n");
exit(1);
}
close(fd);

key = ftok(FILEPATH, 0);
if (key == -1)
{
printf("ftok error\n");
exit(1);
}

/*引用现存共享内存段*/
shm_id = shmget(key, 0, 0);
if(shm_id == -1)
{
printf("shmget error\n");
exit(1);
}

addr = shmat(shm_id, 0, 0);
if(-1 == (long)addr)
{
printf("shmat error\n");
exit(1);
}

sem_id = semget(key, 1, 0);
if(sem_id == -1)
{
printf("semget error\n");
exit(1);
}

if(semphore_p(sem_id) == -1)
{
printf("semphore_p error\n");
exit(1);
}

/*   对共享存储段的操作   省略......*/

if(semphore_v(sem_id) == -1)
{
printf("semphore_p error\n");
exit(1);
}

if(shmdt(addr) == -1)
{
printf("semphore_p error\n");
exit(1);
}

exit(0);
}

int semphore_p(int semid)
{
struct sembuf sem_buf;
sem_buf.sem_num =  0;
sem_buf.sem_op  = -1;
sem_buf.sem_flg = SEM_UNDO;

if(semop(semid, &sem_buf, 1) == -1)
return -1;

return 0;
}

int semphore_v(int semid)
{
struct sembuf sem_buf;
sem_buf.sem_num = 0;
sem_buf.sem_op  = 1;
sem_buf.sem_flg = SEM_UNDO;
if(semop(semid, &sem_buf, 1) == -1)
return -1;

return 0;
}

shell中显示和删除XSI IPC(共享内存、信号量、消息队列)状态的命令:ipcs和ipcrm

<1>查看

ipcs命令语法: ipcs [-m|-q|-s]

-m       输出有关共享内存(shared memory)的信息

-q        输出有关信息队列(message queue)的信息

-s         输出有关信号量(semaphore)的信息

#ipcs

------ Shared Memory Segments --------

key                     shmid      owner      perms       bytes      nattch     status

0x001c2c83   524288       root           666         4096        1

------ Semaphore Arrays --------

key        semid      owner      perms      nsems

------ Message Queues --------

key        msqid      owner      perms      used-bytes   messages

<2>删除
ipcrm命令语法:ipcrm -m|-q|-s shm_id




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