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

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

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 popenpclose函数

#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[n] = 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[n] = 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 pipeFIFO)提供一个路径名与之关联,以FIFO的文件类型存在于文件系统中。故而,只要能访问到该路径,不相关的进程通过FIFO也能交换数据。从而克服了管道的第二个局限。

命名管道(FIFO)有两种用途:

1FIFOshell命令使用以便将数据从一条管道线传送到另一条,为此无需创建中间临时文件。

2FIFO用于客户进程-服务器进程应用程序中,以在客户进程和服务器进程之间传递数据

创建FIFO

#include <sys/stat.h>

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

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

         一旦已经用mkfifo创建了一个FIFO,就可用open打开它。其实,一般的文件I/O函数(closereadwriteunlink等)都可用于FIFO 

XSI IPC    

有三种IPC称之为XSI IPC,即消息队列、信号量以及共享存储器,它们之间有很多相似之处。

XSI,即X/OpenSystem InterfaceX/Open系统接口。

0.1标识符和键

每个内核中的IPC结构(消息队列、信号量或共享存储段)都用一个非负整数的标识符加以引用。

标识符IPC对象的内部名。为使多个合作进程能够在同一IPC对象上会合,需要提供一个外部名方案。为此使用了键(key),每个IPC对象都与一个键相关联,于是键就用作为该对象的外部名。

IPC对象的外部名。

键的数据类型是基本系统数据类型key_t,通常在头文件<sys/types.h>中被定义为长整型。键由内核变换成标识符。

常见客户进程和服务器进程在同一IPC结构上会合的方法:

1)客户进程和服务器进程认同一个路径名和项目ID(项目ID0~255之间的字符值),接着调用函数ftok将这两个值变换为一个键。然后将此键作为一个公用头文件中定义一个客户进程和服务器进程都认可的键。接着服务器进程指定此键创建一个新的IPC结构。

2)服务器进程可以指定键IPC_PRIVATE创建一个新IPC结构,将返回的标识符存放在某处(例如一个文件)以便客户进程取用。

#include <sys/ipc.h>

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

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

参数:

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

id0~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结构是在系统范围内起作用的,没有访问计数。

例如,如果进程创建了一个消息队列,在该队列中放入了几则消息,然后终止,但是该消息队列及其内容并不会被删除。它们余留在系统中直至出现下述情况:由某个进程调用msgrcvmsgctl读消息或删除消息队列;或某个进程执行ipcrm命令删除消息队列;或由正在启动的系统删除消息队列。

2】第二个问题:这些IPC结构在文件系统中没有名字。

故而不能用openreadwrite等系统调用访问或修改它们的特性,不能用ls命令见到IPC对象,不能用rm命令删除它们。为支持它们不得不增加一些全新的系统调用,如msggetsemopshmat等,不得不增加新的命令,如ipcsipcrm等。

优点:

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_CREATIPC_EXCLIPC_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从系统中删除该消息队列以及仍在该队列中的所有数据。

bufmsqid_ds结构

3将数据放到消息队列中

#include <sys/msg.h>

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

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

参数:

msqid:消息队列ID

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

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

   struct mymesg{

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

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

};

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

nbytes:字节大小

flag:标志位,可以为0,也可以为IPC_NOWAITMSG_EXCEPTMSG_NOERROR

   对发送消息来说,比较有意义的flagIPC_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_NOWAITMSG_EXCEPTMSG_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_CREATIPC_EXCLIPC_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_STATIPC_SETIPC_RMIDGETVALSETVAL……..

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_NOWAITSEM_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_CREATIPC_EXCLIPC_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_STATIPC_SETIPC_RMIDSHM_LOCKSHM_UNLOCK,使其在shmid指定的段上执行。

bufshmid_ds结构地址

3连接共享存储段到进程的地址空间

#include <sys/shm.h>

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

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

参数:

shmid:共享存储段标识符

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

flag:标志位,可为SHM_RDNSHM_RDONLYSHM_REMAPSHM_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




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Unix网络编程卷2:进程间通信PDF是一本非常有用的书籍,它涵盖了关于进程间通信的所有关键知识。 进程间通信是指进程交换数据或信息的过程,这对于理解操作系统以及网络编程非常重要。本书不仅讲解了进程通信的基础知识,还深入解释了信号、管道消息队列、共享内存等高级通信方法。 在本书中,作者详细介绍了如何使用各种系统调用和库函数实现不同类型的进程间通信。读者将学习如何在不同进程共享文件句柄,如何创建匿名和命名管道,以及如何使用信号和信号处理程序等。 此外,本书还说明了如何以面向对象的方式编写并发程序。作者展示了C++ STL标准库和Boost库的使用方法,这些工具可以帮助程序员编写更高效的并发程序。 总之,Unix网络编程卷2:进程间通信PDF是一本非常有用的书籍,可以通过实例和详细解释帮助读者更好地理解进程通信的概念和技术。 ### 回答2: 《Unix网络编程 卷2:进程间通信》是一本经典的计算机网络编程书籍,主要讲解了在UNIX环境进程如何进行通信,并介绍了常用的进程间通信机制和技术。 该书包含了进程间通信的基本概念和理论知识,从分析进程地址空进程控制、信号处理、进程间通信等多个方面详细阐述了进程间通信的各种实现方式,并通过实际的例子和代码提供了丰富的实践经验。同时,该书还包含了大量的参考文献和附录,方便读者深入学习和进一步研究进程间通信技术。 该书涵盖的主要内容包括UNIX进程模型、基本进程管理、进程资源和限制、信号、管道消息队列、共享内存、信号量、套接字、RPC、XSI IPC等多个进程间通信机制和技术。其中,对于常用的进程间通信方式如管道消息队列、共享内存、信号量等都进行了详细的介绍。同时,该书还介绍了进程间通信高级技术,如分布式进程间通信(RPC)和XSI IPC等,帮助读者更好地实现进程的通信。 总之,《Unix网络编程 卷2:进程间通信》是一本非常重要的计算机网络编程参考书籍,对于了解UNIX进程模型、深入理解进程间通信技术以及开发UNIX网络应用程序有很大的帮助。该书不仅适合计算机专业的学生和研究人员,也适合从事UNIX网络编程工作的程序员和工程师阅读。 ### 回答3: 《UNIX网络编程 卷2:进程间通信》是由W.Richard Stevens和Stephen A. Rago合作编写的,是一本关于UNIX如何实现进程间通信的技术指南,是一本精华之作。该书的主要内容包括:管道FIFO消息队列信号量、共享内存以及套接字等多种进程间通信方式。同时,书中也介绍了如何在不同的进程进行信息交换、如何保证进程的同步性和互斥性以及如何利用各种进程间通信工具实现并发编程。 该书在介绍管道FIFO消息队列进程间通信机制时,都给出了详细的代码实现,并给出了该技术的优缺点以及适用场景。同时,书中还提供了丰富的案例分析,让读者能通过实践更好地理解和掌握这些技术。此外,书中还介绍了一些进程和线程相关的基础知识,如进程的创建、精灵进程、线程的创建、线程的同步等,这些知识为读者更好地理解进程间通信技术提供了背景和基础。 总的来说,该书是一本深入浅出、全面系统的进程间通信技术指南,它通过具体的代码实现和案例分析,使得读者能在实践中更好地理解和掌握这些技术,并能够开发出高效可靠的应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值