unix 环境编程 进程通信_unix网络编程 进程通信目录 - CSDN
精华内容
参与话题
  • 前面介绍的《管道进程通信》是半双工管道,本届

            前面介绍的《管道进程间通信》是半双工管道,本节基于 STREAMS 的管道是属于全双工的管道,半双工和全双工管道的区别如下:半双工只能在一端进行读或写;全双工可以在某一端同时进行读写;


    基于 STREAMS 的管道

            基于 STREAMS 的管道是一个全双工的管道,单个 STREAMS 管道就能实现父、子进程之间的双向的数据流操作。下面是基于 STREAMS 的管道两种方式:


            基于 STREAMS 管道的内部结构,它包含两个流首,每个流首的写队列(WQ)指向另一个流首的读队列(RQ),写入管道一端的数据被放入另一端的读队列的消息中。 STREAMS 管道是一个流,可将一个 STREAMS 模块压入到该管道的任一端,但是,如果在一端压入了一个模块,那么并不能在另一端弹出模块,若要删除,只能从原压入端删除。下图是基于 STREAMS 管道的内部结构和带模块的 STREAM 管道的内部结构:


    创建基于 STREAMS 管道只是对普通管道进行接口实现,其实现如下:

    #include "apue.h"
    
    /*
    * Return a STREAMS-based pipe, with the two file descirptors 
    * returned in fd[0] and fd[1].
    */
    int
    s_pipe(int fd[2])
    {
        return(pipe(fd));
    }

    命名的 STREAMS 管道

            命名的 STREAMS 管道和 FIFO 管道一样克服了管道的局限性,使其可以在没有亲缘关系的进程间通信,命名的 STREAMS 管道机制通过一种途径,使得进程可以给予管道一个文件系统中的名字,使其能够实现双向通信,避免了 FIFO 管道的单向通信。为了使进程给予管道一个文件系统中的名字,可以调用函数 fattach 使进程给予 STREAMS 管道一个文件系统中的名字,其定义如下:

    /* 命名STREAM 管道 */
    /*
     * 函数功能:使进程给予STREAM管道一个文件系统中的名字;
     * 返回值:若成功则返回0,若出错则返回-1;
     * 函数原型:
     */
    #include <stropts.h>
    int fattach(int filedes, const char *path);
    /*
     * 说明:
     * path必须是引用一个现有的文件,且对该文件具有写权限;
     */
    /*
     * 函数功能:撤销STREAM管道与一个文件系统中的名字的关联;
     * 返回值:若成功则返回0,若出错则返回-1;
     * 函数原型:
     */
    #include <stropts.h>
    int fdetach(const char *path);
    
            一旦 STREAMS 管道连接到文件系统名字空间,那么原来使用该名字的底层文件就不再是可访问的。打开该名字的任一进程将能访问相应管道,而不是访问原先的文件。在调用 fattach 之前打开底层文件的任一进程可以继续访问该文件。确实,一般而言,这些进程并不知道该名字现在引用了另外一个文件。

           在调用 fdetach 函数之后,先前依靠打开 path 而能访问 STREAMS 管道的进程仍可继续访问该管道,但是在此之后打开 path 的进程将访问驻留在文件系统中的底层文件。


    唯一连接

            将 STREAMS 管道的一端连接到文件系统的名字空间后,如果多个进程都使用命名  STREAMS 管道与服务器进程通信时,会出现通信混乱。为了解决多进程访问出现的问题,在  STREAMS 管道压入一个模块,即服务器进程将模块压入到要被连接管道的一端。其实现如下图所示:


    /*
     * 函数功能:创建在无关进程之间的唯一连接;
     * 函数原型:
     */
    #include "apue.h"
    
    int serv_listen(const char *name);
    /* 返回值:若成功则返回要侦听的文件描述符,出错则返回负值;*/
    
    int serv_accept(int listenfd, uid_t *uidptr);
    /* 返回值:若成功则返回新文件描述符,出错则返回负值 */
    
    int cli_conn(const char *name);
    /* 返回值:若成功则返回文件描述符,出错则返回负值 */
    
    /*
     * 说明:
     *
     * 服务器进程调用serv_listen函数声明要在文件系统中的某个路径侦听客户进程的连接请求;
     * 当客户端想要连接至服务器进程,就将使用该文件系统中的名字,该函数返回值是STREAMS管道的服务器进程端;
     *
     * 服务器进程调用serv_accept函数等待客户进程连接请求的到达,当一个请求到达时,系统自动创建一个新的STREAMS管道,
     * 该函数向服务器进程返回该STREAMS管道的一端,客户进程的有效用户ID存放在uidptr所指向的存储区中;
     *
     * 客户端进程调用cli_conn函数连接至服务器进程,客户端进程指定的参数name必须和服务器进程调用serv_listen函数时所用的参数name相同;
     * 该函数返回时,客户端进程得到连接至服务器进程的文件描述符;
     *
     */
    

    上面三个函数的具体实现如下:这些函数可以套用在前面介绍《基于 socket 的编程》的框架中。

    #include "apue.h"
    #include <fcntl.h>
    #include <stropts.h>
    
    /* pipe permissions: user rw, group rw, others rw */
    #define FILE_MODE    (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
    
    /*
    * Establish an endpoint to listen for connect requests.
    * Returns fd if all ok, <0 on error
    */
    int 
    serv_listen(const char *name)
    {
        int     tempfd;
        int     fd[2];
    
        /* 
        * Create a file: mount point for fattach().
        */
        unlink(name);
        if((tempfd = creat(name, FIFO_MODE)) < 0)
            return(-1);
        if(close(tempfd) < 0)
            return(-2);
        if(pipe(fd) < 0)
            return(-3);
        
        /*
        * Push connld & fattach() on fd[1]. 
        */
        if(ioctl(fd[1], I_PUSH, "connld") < 0)
        {
            close(fd[0]);
            close(fd[1]);
            return(-4);
        }
        if(fattach(fd[1], name) < 0)
        {
            close(fd[0]);
            close(fd[1]);
            return(-5);
        }
        close(fd[1]);    /* fattach holds this end open */
        return(fd[0]);    /* fd[0] is where client connections arrive */
        
    }

    #include "apue.h"
    #include <stropts.h>
    
    /*
    * Wait for a client connection to arrive, and accept it.
    * We also obtain the client's user ID.
    * Return new fd if all ok, <0 on error.
    */
    int serv_accept(int listenfd, uid_t *uidptr)
    {
        struct strrecvfd    recvfd;
        
        if(ioctl(listenfd, I_RECVFD, &recvfd) < 0)
            return(-1);    /* could be EINTR if signal caught */
        if(uidptr != NULL)
            *uidptr = recvfd.uid;    /* effective uid of caller */
        return(recvfd.fd);    /* return the new descriptor */
    }

    #include "apue.h"
    #include <fcntl.h>
    #include <stropts.h>
    
    /*
    * Create a client endpoint and connect to a server.
    * Return fd if all ok, <0 on error.
    */
    
    int
    cli_conn(const char *name)
    {
        int    fd;
    
        /* open the mounted stream */
        if((fd = open(name, O_RDWR)) < 0)
            return(-1);
        if(isastream(fd) == 0)
        {
            close(fd);
            return(-2);
        }
        return(fd);
    }

    参考资料:

    《UNIX高级环境编程》

    展开全文
  • 基于流的管道(STREAMS-Based Pipes) 所谓基于流的管道实际上就是一种全双工管道,它必须在...关于流机制,我在 Unix环境高级编程学习笔记(九) 高级IO中曾经介绍过,知道可以在流首处加入处理模块,对于基于流的

    基于流的管道(STREAMS-Based Pipes)

    所谓基于流的管道实际上就是一种全双工管道,它必须在基于流的系统上才能实现,linux 默认对它是不支持的,而同样的逻辑,我们通常可以用基于 UNIX domain 的 socket 来实现,所以这里对它只作简单介绍。

    关于流机制,我在 Unix环境高级编程学习笔记(九) 高级IO中曾经介绍过,知道可以在流首处加入处理模块,对于基于流的管道而言,管道的两端都是流首,所以可以在两端都加入处理模块,但同时,我们也只能在处理模块加入的一端删除它。

    有时候,为了达到在不相关的进程间通信的目的,我们可以将这种管道的一端和某个实际的文件关联起来,也就相当于给了它一个名字,使用 fattach 函数:

    int fattach(int filedes, const char *path);

    调用进程必须拥有该文件,且对它具有写权限,或是调用进程具有超级用户的权限。而一旦流管道的一端和某个文件关联上后,该文件就不能再被访问了,任何对该文件执行的打开操作,都是获得管道的访问权,而不再是那个文件本身。不过,对于管道关联之前就已经打开了文件的进程,仍然可以继续访问该文件,而不必受管道的影响。

    使用 fdetach 函数可以解除关联:

    int fdetach(const char *path);

    在该函数被调用以后,已经获得管道访问权的用户将继续拥有该管道的访问权,而之后打开该文件的进程获得的是对文件本身的访问权。

    UNIX 域的 SOCKET

    Unix环境高级编程学习笔记(十一) 网络IPC:套接字中,我介绍了 socket 使用,关于 domain,一共可以有4种情况,今天,我们来看一下用于同一机器下不同进程间通信的 UNIX 域。UNIX 域同时支付流和数据报接口,不同于英特网域,这两种接口都是可靠的。

    我们可以像使用普通的 socket 那样来使用它,在 UNIX 域的限定下,其地址格式由 sockaddr_un 数据结构来呈现。在Linux 2.4.22 以及 Solaris 9 上,sockaddr_un 的定义如下:

    struct sockaddr_un {
    		sa_family_t 	sun_family;/* AF_UNIX */
    		char 		sun_path[108];/* pathname */
    };

    sun_path 成员指定了一个文件名,当将一个 UNIX domain socket 和该地址绑定在一起之后,系统将为我们创建一个 S_IFSOCK 类型的该文件。当该文件已经存在时,bind 函数将失败。当我们关闭该 socket 之后,文件不会自动被删除,需要我们手动 unlink 它。

    需要注意以几点:

    1. 在connect调用中指定的路径名必须是一个当前绑定在某个打开的Unix域套接口上的路径名。如果对于某个Unix域套接口的connect调用发现这个监听套接口的队列已满,调用就立即返回一个ECONNREFUSED错误。这一点不同于TCP,如果TCP监听套接口队列已满,TCP监听端就忽略新到达的SYN,而TCP连接发送端将数次发送SYN进行重试。

    2. 在一个未绑定的Unix域套接口上发送数据报不会自动给这个套接口捆绑一个路径名,这一点与UDP套接口不同。所以在客户端,我们也必须显示地bind一个路径名到我们的套接口。

    来看一个实际的例子。

    服务端:

    /*
     *author: justaipanda
     *create time:2012/09/05 21:51:27
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    
    #define FILE_NAME "1.txt"
    #define BUF_SIZE 1024
    
    int serv_listen(const char* name) {
    
    	int fd;
    	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
    		return -1;
    
    	unlink(name);
    	struct sockaddr_un un;
    	memset(&un, 0, sizeof(un));
    	un.sun_family = AF_UNIX;
    	strcpy(un.sun_path, name);
    	int len = (int)((void*)&(un.sun_path) - (void*)&un) 
    		+ strlen(un.sun_path);
    	if (bind(fd, (struct sockaddr*)&un, len) < 0) {
    		close(fd);
    		return -2;
    	}
    
    	if (listen(fd, 128) < 0) {
    		close(fd);
    		return -3;
    	}
    
    	return fd;
    }
    
    int serv_accept(int listenfd) {
    
    	int fd;
    	struct sockaddr_un un;
    	int len = sizeof(un);
    
    	if ((fd = accept(listenfd, (struct sockaddr*)&un, &len)) < 0) 
    		return -1;
    
    	((char*)(&un))[len] = '\0';
    	unlink(un.sun_path);
    
    	return fd;
    }
    
    int main() {
    	
    	int fd;
    	if ((fd = serv_listen(FILE_NAME)) < 0) {
    		printf("listen error!\n");
    		exit(fd);
    	}
    
    	while(1) {
    
    		int sclient = serv_accept(fd);
    		if (sclient < 0) {
    			printf("accept error!\n");
    			exit(sclient);
    		}
    
    		char buffer[BUF_SIZE];
    		ssize_t len = recv(sclient, buffer, BUF_SIZE, 0);
    		if (len < 0) {
    			printf("recieve error!\n");
    			close(sclient);
    			continue;
    		}
    
    		buffer[len] = '\0';
    		printf("receive[%d]:%s\n", (int)len, buffer);
    		if (len > 0) {
    			if('q' == buffer[0]) {
    				printf("server over!\n");
    				exit(0);
    			}
    				
    			char* buffer2 = "I'm a server!";
    			len = send(sclient, buffer2, strlen(buffer2), 0);
    			if (len < 0)
    				printf("send error!\n");
    		}
    		close(sclient);
    	}
    
    	return 0;
    }
    


    客户端:

    /*
     *author: justaipanda
     *create time:2012/09/06 10:36:43
     */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    #include <fcntl.h>
    
    #define FILE_NAME "1.txt"
    #define CLI_PATH "/var/tmp/"
    #define BUF_SIZE 1024
    
    int cli_conn(const char* name) {
    
    	int fd;
    	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
    		return -1;
    
    	struct sockaddr_un un;
    	memset(&un, 0, sizeof(un));
    	un.sun_family = AF_UNIX;
    	sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid());
    	unlink(un.sun_path);
    	int len = (int)((void*)&(un.sun_path) - (void*)&un) 
    		+ strlen(un.sun_path);
    	if (bind(fd, (struct sockaddr*)&un, len) < 0) {
    		close(fd);
    		return -2;
    	}
    
    	if(chmod(un.sun_path, S_IRWXU) < 0) {
    		close(fd);
    		return -3;
    	}
    
    	memset(&un, 0, sizeof(un));
    	un.sun_family = AF_UNIX;
    	strcpy(un.sun_path, name);
    	len = (int)((void*)&(un.sun_path) - (void*)&un) 
    		+ strlen(un.sun_path);
    	if (connect(fd, (struct sockaddr*)&un, len) < 0) {
    		close(fd);
    		return -4;
    	}
    
    	return fd;
    }
    
    int main() {
    
    	int fd;
    	if ((fd = cli_conn(FILE_NAME)) < 0) {
    		printf("connect error!\n");
    		exit(fd);
    	}
    
    	char buffer[BUF_SIZE];
    	printf("input:");
    	scanf("%s", buffer);
    	int len = send(fd, buffer, strlen(buffer), 0);
    	if (len < 0) {
    		printf("send error!\n");
    		exit(len);
    	}
    
    	len = recv(fd, buffer, BUF_SIZE, 0);
    	if (len < 0) {
    		printf("recieve error!\n");
    		exit(len);
    	}
    
    	buffer[len] = '\0';
    	printf("receive[%d]:%s\n", (int)len, buffer);
    
    	return 0;
    }
    

    对于关联的进程,我们也可以使用 socketpair 函数简化工作:

    int socketpair(int domain, int type, int protocol, int sockfd[2]);

    这个函数的功能实际上就相当于创建了一个全双工的管道,其中,domain 只能被指定为 AF_UNIX。

    传送文件描述符

    许多时候,如果可以在进程间传递文件描述符,这将极大的简化我们程序的设计。

    所谓传送文件描述符实际上是指——让接收进程打开一个文件描述符,该描述符的值不一定和发送进程发送的文件描述符相同,但它们都指向同一文件表。

    如果发送进程关闭了文件描述符,这并不会真的关闭文件或设备,因为系统认为,接收进程仍然打开着文件,即使此时,接收进程可能还未收到该文件描述符。

    传送文件描述符一般可以有两种方式:基于流的管道和 UNIX domain socket,对于前者,这里不多下文笔,主要讲后者。利用 UNIX domain socket 传递文件描述符需要使用前面讲过的 sendmsg 和 recvmsg 函数(参见Unix环境高级编程学习笔记(十一) 网络IPC:套接字)。利用 msghdr 结构体中的 msg_constrol 成员传递描述符。该成员指向 cmsghdr 结构:

    struct cmsghdr {
    	socklen_t cmsg_len;/* data byte count, including header */
    	int cmsg_level; /* originating protocol */
    	int cmsg_type;/* protocol-specific type */
    	/* followed by the actual control message data */
    };
    

    为了发送文件描述符,将 cmsg_len 设置为 cmsghdr 结构的长度再加上一个整型(描述符)的长度,cmsg_level 字段设置为 SOL_SOCKET,cmsg_type 字段设置为 SCM_RIGHTS,用以指明我们在传送访问权限。

    对于这个结构体的操作宏:

    unsigned char *CMSG_DATA(struct cmsghdr *cp);
    //Returns: pointer to data associated with cmsghdr structure
    
    struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *mp);
    //Returns: pointer to first cmsghdr structure associated with the msghdr structure, or NULL if none exists
    
    struct cmsghdr *CMSG_NXTHDR(struct msghdr *mp, struct cmsghdr *cp);
    //Returns: pointer to next cmsghdr structure associated with the msghdr structure given the current cmsghdr structure, or NULL if we're at the last one
    
    unsigned int CMSG_LEN(unsigned int nbytes);
    //Returns: size to allocate for data object nbytes large
    

    下面看一个使用 UNIX domain socket 传递文件描述符的例子,该例子是由前面的例子修改而来,其中 serv_listen,serv_accept,以及 cli_conn 函数和原来一样,就不再重复了。

    服务端:

    int send_fd(int fd, int fd_to_send) {
    
    	struct msghdr msg;
    	msg.msg_name = NULL;
    	msg.msg_namelen = 0;
    
    	struct cmsghdr *cmptr = NULL;
    	char buffer[BUF_SIZE];
    	struct iovec iov;
    
    	if (fd_to_send >= 0) {
    		int cmsg_len = CMSG_LEN(sizeof(int));
    		cmptr = malloc(cmsg_len);
    
    		cmptr->cmsg_level = SOL_SOCKET;
    		cmptr->cmsg_type = SCM_RIGHTS;
    		cmptr->cmsg_len = cmsg_len;
    		*(int*)CMSG_DATA(cmptr) = fd_to_send;
    
    		msg.msg_control = cmptr;
    		msg.msg_controllen = cmsg_len;
    
    		sprintf(buffer, "OK!");
    	}
    	else {
    
    		if (-1 == fd_to_send)
    			sprintf(buffer, "cannot open file!");
    		else
    			sprintf(buffer, "wrong command!");
    		
    		msg.msg_control = NULL;
    		msg.msg_controllen = 0;
    	}
    
    	msg.msg_iov = &iov;
    	msg.msg_iovlen = 1;
    	iov.iov_base = buffer;
    	iov.iov_len = strlen(buffer);
    
    	sendmsg(fd, &msg, 0);
    	if (cmptr)
    		free(cmptr);
    	return 0;
    }
    
    int main() {
    	
    	int fd;
    	if ((fd = serv_listen(FILE_NAME)) < 0) {
    		printf("listen error!\n");
    		exit(fd);
    	}
    
    	while(1) {
    
    		int sclient = serv_accept(fd);
    		if (sclient < 0) {
    			printf("accept error!\n");
    			exit(sclient);
    		}
    
    		char buffer[BUF_SIZE];
    		ssize_t len = recv(sclient, buffer, BUF_SIZE, 0);
    		if (len < 0) {
    			printf("recieve error!\n");
    			close(sclient);
    			continue;
    		}
    
    		buffer[len] = '\0';
    		printf("receive[%d]:%s\n", (int)len, buffer);
    		if (len > 0) {
    			if('q' == buffer[0]) {
    				printf("server over!\n");
    				exit(0);
    			}
    			else if ('f' == buffer[0]) {
    				int new_fd = open(buffer + 2, O_RDWR);
    				send_fd(sclient, new_fd);
    			}
    			else
    				send_fd(sclient, -2);
    		}
    		close(sclient);
    	}
    
    	return 0;
    }

    客户端:

    int recv_fd(int fd, char *buffer, size_t size) {
    
    	struct cmsghdr *cmptr;
    	int cmsg_len = CMSG_LEN(sizeof(int));
    	cmptr = malloc(cmsg_len);
    
    	struct iovec iov;
    	iov.iov_base = buffer;
    	iov.iov_len = size;
    
    	struct msghdr msg;
    	msg.msg_name = NULL;
    	msg.msg_namelen = 0;
    	msg.msg_iov = &iov;
    	msg.msg_iovlen = 1;
    	msg.msg_control = cmptr;
    	msg.msg_controllen = cmsg_len;
    
    	int len = recvmsg(fd, &msg, 0);
    	if (len < 0) {
    		printf("receve message error!\n");
    		exit(0);
    	}
    	else if (len == 0) {
    		printf("connection closed by server!\n");
    		exit(0);
    	}
    
    	buffer[len] = '\0';
    	int cfd = -1;
    	if (cmptr->cmsg_type != 0)
    		cfd = *(int*)CMSG_DATA(cmptr);
    	free(cmptr);
    	return cfd;
    }
    
    int main() {
    
    	int fd;
    	if ((fd = cli_conn(FILE_NAME)) < 0) {
    		printf("connect error!\n");
    		exit(fd);
    	}
    
    	char buffer[BUF_SIZE];
    	printf("input:");
    	fgets(buffer, BUF_SIZE, stdin);
    	buffer[strlen(buffer) - 1] = '\0';
    	int len = send(fd, buffer, strlen(buffer), 0);
    	if (len < 0) {
    		printf("send error!\n");
    		exit(len);
    	}
    
    	int cfd = recv_fd(fd, buffer, BUF_SIZE);
    	printf("data:%s\n", buffer);
    	if (cfd >= 0) {
    		printf("received open file:%d\n", cfd);
    	}
    
    	return 0;
    }


    展开全文
  • 共享内存是允许两个或多个进程共享同一块内存区域

            共享内存是允许两个或多个进程共享同一块内存区域,并通过该区域实现数据交换的进程间通信机制。通常是由一个进程开辟一块共享内存区域,然后允许多个进程对此区域进行访问。由于不需要任何介质,而是数据由内存直接映射到进程空间,即数据不需要在客户进程和服务进程之间复制,所以共享内存是最快的 IPC 机制。共享内存必须解决多个进程之间同步访问的问题,必须控制同一时刻只允许一个进程对共享内存区域进行写入数据操作,否则会造成数据混乱。同步访问问题可以使用信号量或者记录锁进行解决。

    每个共享内存都有相对应的 shmid_ds 结构,其定义如下:

    /* 共享内存 */
    
    /* shmid_ds 结构 */
    struct shmid_ds
    {
        struct ipc_perm shm_perm;   /* operation permission struct */
        size_t          shm_segsz;  /* size of segment in bytes */
        pid_t           shm_lpid;   /* pid of last shmop() operation */
        pid_t           shm_cpid;   /* pid of creator */
        shmatt_t        shm_nattch; /* number of current attaches */
        time_t          shm_atime;  /* last-attach time */
        time_t          shm_dtime;  /* last-detach time */
        time_t          shm_ctime;  /* last-change time */
    };
    

    共享内存创建与打开

            为了能够使用共享内存,我们要创建一个共享内存区域,并获取该共享内存的标识符,可以调用函数 shmget 实现该功能:

    /*
     * 函数功能:创建一个新的共享内存区域或打开一个现有的共享内存区域;
     * 返回值:若成功则返回共享内存ID,若出错则返回-1;
     * 函数原型:
     */
    #include <sys/shm.h>
    
    int shmget(key_t key, size_t size, int flag);
    
    /*
     * 说明:
     * key是共享内存的键值;
     * size是共享内存区域的大小;
     * flag设置共享内存的访问权限;
     * (1)当key为IPC_PRIVATE时,此时flag的取值对该函数不起作用;
     * (2)当key不为IPC_PRIVATE时,且flag设置为IPC_CREAT,则执行操作有key值决定;
     * (3)当key不为IPC_PRIVATE时,且flag同时设置为IPC_CREAT | IPC_EXCL,则只执行创建共享内存操作,此时key必须不同于内核已存在的共享内存的键值,否则出错返回;
     */
    


    共享内存与地址空间的连接和断开

            当一个共享内存被创建或打开之后,进程若要使用该共享内存,则必须要是该共享内存连接到它的地址空间,可以调用函数 shmat 实现该功能,若共享内存不再使用时,必须从内核系统中删除,可以调用函数 shmdt 实现:

    /*
     * 函数功能:将共享内存连接到它的地址空间;
     * 返回值:若成功则返回指向共享内存的指针,若出错则返回-1;
     * 函数原型:
     */
    #include <sys/shm.h>
    void  *shmat(int shmid, void *addr, int flag);
    /*
     * 说明:
     * shmid是共享内存的ID;
     * adrr和flag共同决定函数的返回:
     * (1)若addr为0,则此段连接到由内核选择的第一个可用地址上,此时flag取任何值都无效;
     * (2)若addr不为0,而flag未设置SHM_RND,则共享内存区域连接到由addr指定的地址处;
     * (3)若addr不为0,而flag设置了SHM_RND,则共享内存区域连接到由(adrr-(addr mod ulus SHMLBA))指定的地址处;
     * 其中SHM_RND表示取整,SHMLBA表示低边界地址倍数;
     * 若flag指定SHM_RDONLY,则以只读方式连接,否则以读写方式连接;
     */
    /*
     * 函数功能:将共享内存与它的地址空间断开;
     * 返回值:若成功则返回0,若出错则返回-1;
     * 函数原型:
     */
    #include <sys/shm.h>
    int shmdt(void *addr);
    

    共享内存的控制操作

            对共享内存区域进行多中控制操作可以通过函数 shmctl 实现,其定义如下:

    /*
     * 函数功能:对共享内存进行控制操作;
     * 返回值:若成功则返回0,若出错则返回-1;
     * 函数原型:
     */
    #include <sys/shm.h>
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    

    该函数提供了三个 cmd 命令:

    1. IPC_RMID:从系统中删除由 shmid 标识的共享内存区并拆除它。
    2. IPC_SET :给所指定的共享内存区设置其 shmid_ds 结构的以下三个成员:shm_perm.uid,shm_perm.gid和shm_perm.mode,他们的值来自buff参数指向的结构中的相应成员。shm_ctime的值也用当前时间替换。
    3. IPC_STAT :(通过buff参数)向调用者返回所指定共享内存区当前的 shmid_ds 结构,将其保存在buf所指向的缓冲区。

    另外 Linux 提供了下列另外两种命令:

    1. SHM_LOCK:将共享内存锁定在内存中,此命令只能超级用户才可以执行。
    2. SHM_UNLOCK:解锁共享内存,此命令只能超级用户才可以执行。
    测试程序:创建一个共享内存区,然后打印该共享内存区的属性;
    #include "apue.h"
    #include <sys/shm.h>
    #include <fcntl.h>
    #include <sys/ipc.h>
    #include <sys/types.h>
    
    #define ARRAY_SIZE 4000
    #define MALLOC_SIZE 10000
    #define SHM_SIZE 10000
    #define SHM_MODE (SHM_R | SHM_W)
    void printf_shm(struct shmid_ds *buf);
    key_t MakeKey(const char *pathname);
    
    char array[ARRAY_SIZE];
    
    int main(int argc, char *argv[])
    {
        if(argc != 2)
            err_quit("usage: a.out <pathname>");
    
        int shmid;
        char *ptr;
        void *shmptr;
        struct shmid_ds shmids;
        key_t key;
        key = MakeKey(argv[1]);
    
        printf("array[] from %x to %x\n", &array[0],&array[ARRAY_SIZE]);
        printf("stack around %x\n", &shmid);
    
        if((ptr = (char *)malloc(MALLOC_SIZE)) == NULL)
            err_sys("malloc error");
        printf("malloced from %x to %x\n", ptr, ptr+MALLOC_SIZE);
    
        if((shmid = shmget(key, SHM_SIZE, SHM_MODE | IPC_CREAT)) < 0)
            err_quit("shmget error");
    
        if((shmptr = shmat(shmid, 0, 0)) == (void *)-1)
            err_sys("shmat error");
        if(shmctl(shmid, IPC_STAT, &shmids) == -1)
            err_sys("shmctl error");
    
        printf_shm(&shmids);
    
        if(shmctl(shmid, IPC_RMID, 0) < 0)
            err_sys("shmctl error");
    
        exit(0);
    }
    key_t MakeKey(const char *pathname)
    {
        int fd;
        if((fd = open(pathname, O_CREAT, 0666)) < 0)
            err_quit("open error");
        close(fd);
    
        return ftok(pathname, 0);
    }
    void printf_shm(struct shmid_ds *buf)
    {
        printf("Struct shmid_ds:\n");
        printf("\tshm_nattch = %d\n", buf->shm_nattch);
        printf("\tshm_segsz = %d\n", buf->shm_segsz);
        printf("\tStruct ipc_perm:\n");
        printf("\t\tuid = %d\n", buf->shm_perm.uid);
        printf("\t\tgid = %d\n", buf->shm_perm.gid);
        printf("\t\tcuid = %d\n", buf->shm_perm.cuid);
        printf("\t\tcgid = %d\n", buf->shm_perm.cgid);
    
        return;
    }
    
    输出结果:
    ./shm Shm
    array[] from 804b0a0 to 804c040
    stack around bfa5a47c
    malloced from 8b6e008 to 8b70718
    Struct shmid_ds:
    	shm_nattch = 1
    	shm_segsz = 10000
    	Struct ipc_perm:
    		uid = 1000
    		gid = 1000
    		cuid = 1000
    		cgid = 1000


    参考资料:
    《UNIX高级环境编程》


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

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

    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

    
    
    
    展开全文
  • unix环境高级编程》--- 进程通信

    千次阅读 2018-06-01 14:08:15
    管道 1、半双工,即数据只能在一个方向上流动。...创建一个从父进程到子进程的管道,由父进程向子进程发送数据。 #include &quot;apue.h&quot; int main(void) { int n; int fd[2]; pid_t pid; ...
  • UNIX网络编程 卷2:进程通信(第2版)PDF 及 源代码; PDF 是中文扫描版的; 源代码里面有 .tar.gz 【在MAC/Linux/Unix 环境下使用 “tar zxvf xxx.tar.gz”解压】 以及 .zip 两种格式的文件【它们内容是一样的】...
  • UNIX网络编程 卷2 进程通信(第2版)

    千次下载 热门讨论 2020-07-29 14:18:46
    UNIX网络编程 卷2 进程通信(第2版)
  • UNIX网络编程第二版 卷2 进程通信 书还算比较清晰,并且有书签,看起来方便
  • 二.有名管道  管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系...这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信,因此,通过FIFO不相关的进程
  • UNIX环境高级编程

    千次下载 热门讨论 2020-07-29 14:21:28
    UNIX环境高级编程》很好的...这本书概括了Linux 编程所需的一切理论框架、主要系统函数、多进程编程、乃至 Linux 网络通信。 如果你是一个初学者,可以先看这本书:《unix/linux编程实践教程》 这本书下载地址为: ...
  • 进程通信 --- IPC1. 进程通信的目的a. 数据传输: 一个进程需要将他的数据发送给另一个进程b. 资源共享: 多个进程之间共享同样的资源c. 通知事件: 一个进程需要向另一个或一组进程发送消息,通知它们发生了...
  • 进程通信(IPC)是处理一个多进程系统中
  • unix进程通信方式总结(上)

    千次阅读 2016-08-06 00:52:21
    本文将《unix环境高级编程》一书中所涉及的几种重要的进程通信方式(Inter-Process Communication)进行简单总结,总的来说,进程通信有以下几种: (1).
  • Unix中的IPC(InterProcess Communication)是各种进程通信的统称,在Unix中有很多线程间通信方法,但是他们并不是兼容Unix的各种实现,下图列出了Linux系统不同实现所支持的不同形式的IPC。 本文将介绍上诉表中...
  •  IPC是进程间通信的简称,所谓进程通信,就是不同进程之间进行一些"接触",这种接触有简单,也有复杂。机制不同,复杂度也不一样。通信是一个广义上的意义,不仅仅指传递一些massege。还包含进程之间的合作等。 二...
  • 下载地址:UNIX网络编程卷2:进程通信(第2版) 带完整书签:
  • UNIX环境高级编程(中文第三版)

    千次下载 热门讨论 2020-07-30 23:32:15
    书中除了介绍UNIX文件和目录、标准I/O库、系统数据文件和信息、进程环境进程控制、进程关系、信号、线程、线程控制、守护进程、各种I/O、进程通信、网络IPC、伪终端等方面的内容,还在此基础上介绍了众多应用...
  •  四个月《unix环境高级编程》学习后,便开始了《unix网络编程卷2进程间的通信》的学习,本人直接跳过《unix网络编程 卷1》的学习,原因在《unix环境高级编程》中接触到进程时,想更深入的学习进程及线程,在怀着...
  • Unix编程进程通信(IPC)

    千次阅读 2015-04-21 20:52:06
    Linux进程通信(IPC) 进程通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。...
  • 什么是进程间通信 IPC是进程间通信的简称,所谓进程通信,就是不同进程之间进行一些"接触",这种接触有简单,也有复杂。机制不同,复杂度也不一样。通信是一个广义上的意义,不仅仅指传递一些massege。还包含进程...
1 2 3 4 5 ... 20
收藏数 51,895
精华内容 20,758
关键字:

unix 环境编程 进程通信