linux 进程间通信库_linux多线程、多进程和linux进程间通信技术 - CSDN
精华内容
参与话题
  • Linux进程间通信——so共享的使用学习笔记

    共享库so的认识:
    即使不同进程调用同一个so文件,通过共享库并不能实现不同进程间的通信,因为同一个so被不同进程加载到不同的内存空间。这类似windows下的dll动态库。

    下面以一个实例来介绍linux下so共享库的使用方法:
    1、实现一个so库文件名称为listupper.so,so文件中实现一个函数,函数名为void upper(const char *src, char *desc),调用update后将参数src所指向的字符串中所有字符转化为大写字母,结果放入desc字符串中。分别用C语言编写一个程序test1,调用libupper.so中的upper函数,用C++语言编写一个程序test2,调用libupper.so中的upper函数。

    第一步:生成so共享库文件
    1、so文件不要有main函数,即使有也不会被执行。
    2、编译:gcc要加上-fPIC选项,这可以使gcc产生与位置无关的代码。
    3、链接:gcc要加上-shared选项,指示生成共享库文件。
    4、共享库文件名要以lib开头,扩展名为.so
    编译出so文件还需要头文件,头文件包含各函数的申明。

    .SUFFIXES: .c .o
    
    CC=gcc
    
    SRCS=upper.c
    
    OBJS=$(SRCS:.c=.o)
    EXEC=libupper.so
    
    all: $(OBJS)
        $(CC) -shared -o $(EXEC) $(OBJS)
        @echo '-------------ok--------------'
    
    .c.o: 
        $(CC) -Wall -g -fPIC -o $@ -c $<
    
    clean:
        rm -f $(OBJS)
        rm -f core*
    
    
    /* upper.h*/
    
    #ifndef UPPER_H_
    #define UPPER_H_
    
    #ifdef __cplusplus
    extern "C"
    {
    #endif
    
    void upper(const char *src, char *desc);
    
    #ifdef __cplusplus
    }
    #endif
    
    #endif /* UPPER_H_ */
    
    /* upper.c */
    
    #include <stdio.h>
    #include <stdlib.h>
    #include<string.h>
    
    void upper(const char *src, char *desc)
    {
        if ((src==NULL)|(desc==NULL))
        {
            return;
        }
        int i=0;
        char buf[1024];
        memset(buf,0,sizeof(buf));
        strcpy(buf,src);
        int len=strlen(buf);
        while(buf[i]!='\0')
        {
            if((buf[i]>='a')&&(buf[i]<='z'))
            {
                buf[i]=buf[i]-('a'-'A');
            }
            i++;
        }
        strcpy(desc,buf);
        desc[len]='\0';
    }

    生成libupper.so
    这里写图片描述

    第二步:如何使用.so文件(配置环境)
    1、cd 回到宿主目录
    2、vi .bash_profile 修改.bash_profile文件
    3、添加 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. 添加当前路径
    4、. .bash_profile 重新运行bash_profile文件
    5、链接:gcc -L. -lupper -o a a.o
    其中-L.意思为在当前路径寻找so文件
    -lupper 意思是链接libupper.so这个库文件

    .SUFFIXES: .c .o
    
    CC=g++
    
    SRCS=test2.cpp
    
    OBJS=$(SRCS:.cpp=.o)
    EXEC=test2
    
    all: $(OBJS)
        $(CC) -L. -lupper -o $(EXEC) $(OBJS)
        @echo '-------------ok--------------'
    
    .c.o: 
        $(CC) -Wall -g -o $@ -c $<
    
    clean:
        rm -f $(OBJS)
        rm -f core*

    c语言版运行结果
    这里写图片描述
    cpp版运行结果
    这里写图片描述

    展开全文
  • Linux进程间通信——使用匿名管道

    万次阅读 多人点赞 2013-08-23 01:49:33
    这里将介绍另一种进程间通信的方式——匿名管道,通过它进程间可以交换更多有用的数据。 一、什么是管道 如果你使用过Linux的命令,那么对于管道这个名词你一定不会感觉到陌生,因为我们通常通过符号“|"来使用...
    在前面,介绍了一种进程间的通信方式:使用信号,我们创建通知事件,并通过它引起响应,但传递的信息只是一个信号值。这里将介绍另一种进程间通信的方式——匿名管道,通过它进程间可以交换更多有用的数据。

    一、什么是管道
    如果你使用过Linux的命令,那么对于管道这个名词你一定不会感觉到陌生,因为我们通常通过符号“|"来使用管道,但是管理的真正定义是什么呢?管道是一个进程连接数据流到另一个进程的通道,它通常是用作把一个进程的输出通过管道连接到另一个进程的输入。

    举个例子,在shell中输入命令:ls -l | grep string,我们知道ls命令(其实也是一个进程)会把当前目录中的文件都列出来,但是它不会直接输出,而是把本来要输出到屏幕上的数据通过管道输出到grep这个进程中,作为grep这个进程的输入,然后这个进程对输入的信息进行筛选,把存在string的信息的字符串(以行为单位)打印在屏幕上。

    二、使用popen函数
    1、popen函数和pclose函数介绍
    有静就有动,有开就有关,与此相同,与popen函数相对应的函数是pclose函数,它们的原型如下:
    #include <stdio.h>
    FILE* popen (const char *command, const char *open_mode);
    int pclose(FILE *stream_to_close);
    poen函数允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或者通过它接收数据。command是要运行的程序名和相应的参数。open_mode只能是"r(只读)"和"w(只写)"的其中之一。注意,popen函数的返回值是一个FILE类型的指针,而Linux把一切都视为文件,也就是说我们可以使用stdio I/O库中的文件处理函数来对其进行操作。

    如果open_mode是"r",主调用程序就可以使用被调用程序的输出,通过函数返回的FILE指针,就可以能过stdio函数(如fread)来读取程序的输出;如果open_mode是"w",主调用程序就可以向被调用程序发送数据,即通过stdio函数(如fwrite)向被调用程序写数据,而被调用程序就可以在自己的标准输入中读取这些数据。

    pclose函数用于关闭由popen创建出的关联文件流。pclose只在popen启动的进程结束后才返回,如果调用pclose时被调用进程仍在运行,pclose调用将等待该进程结束。它返回关闭的文件流所在进程的退出码。

    2、例子
    很多时候,我们根本就不知道输出数据的长度,为了避免定义一个非常大的数组作为缓冲区,我们可以以块的方式来发送数据,一次读取一个块的数据并发送一个块的数据,直到把所有的数据都发送完。下面的例子就是采用这种方式的数据读取和发送方式。源文件名为popen.c,代码如下:
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
    	FILE *read_fp = NULL;
    	FILE *write_fp = NULL;
    	char buffer[BUFSIZ + 1];
    	int chars_read = 0;
    	
    	//初始化缓冲区
    	memset(buffer, '\0', sizeof(buffer));
    	//打开ls和grep进程
    	read_fp = popen("ls -l", "r");
    	write_fp = popen("grep rwxrwxr-x", "w");
    	//两个进程都打开成功
    	if(read_fp && write_fp)
    	{
    		//读取一个数据块
    		chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
    		while(chars_read > 0)
    		{
    			buffer[chars_read] = '\0';
    			//把数据写入grep进程
    			fwrite(buffer, sizeof(char), chars_read, write_fp);
    			//还有数据可读,循环读取数据,直到读完所有数据
    			chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
    		}
    		//关闭文件流
    		pclose(read_fp);
    		pclose(write_fp);
    		exit(EXIT_SUCCESS);
    	}
    	exit(EXIT_FAILURE);
    }
    运行结果如下:

    从运行结果来看,达到了信息筛选的目的。程序在进程ls中读取数据,再把数据发送到进程grep中进行筛选处理,相当于在shell中直接输入命令:ls -l | grep rwxrwxr-x。

    3、popen的实现方式及优缺点
    当请求popen调用运行一个程序时,它首先启动shell,即系统中的sh命令,然后将command字符串作为一个参数传递给它。

    这样就带来了一个优点和一个缺点。优点是:在Linux中所有的参数扩展都是由shell来完成的。所以在启动程序(command中的命令程序)之前先启动shell来分析命令字符串,也就可以使各种shell扩展(如通配符)在程序启动之前就全部完成,这样我们就可以通过popen启动非常复杂的shell命令。

    而它的缺点就是:对于每个popen调用,不仅要启动一个被请求的程序,还要启动一个shell,即每一个popen调用将启动两个进程,从效率和资源的角度看,popen函数的调用比正常方式要慢一些。

    三、pipe调用
    如果说popen是一个高级的函数,pipe则是一个底层的调用。与popen函数不同的是,它在两个进程之间传递数据不需要启动一个shell来解释请求命令,同时它还提供对读写数据的更多的控制。

    pipe函数的原型如下:
    #include <unistd.h>
    int pipe(int file_descriptor[2]);
    我们可以看到pipe函数的定义非常特别,该函数在数组中墙上两个新的文件描述符后返回0,如果返回返回-1,并设置errno来说明失败原因。

    数组中的两个文件描述符以一种特殊的方式连接起来,数据基于先进先出的原则,写到file_descriptor[1]的所有数据都可以从file_descriptor[0]读回来。由于数据基于先进先出的原则,所以读取的数据和写入的数据是一致的。

    特别提醒:
    1、从函数的原型我们可以看到,它跟popen函数的一个重大区别是,popen函数是基于文件流(FILE)工作的,而pipe是基于文件描述符工作的,所以在使用pipe后,数据必须要用底层的read和write调用来读取和发送。

    2、不要用file_descriptor[0]写数据,也不要用file_descriptor[1]读数据,其行为未定义的,但在有些系统上可能会返回-1表示调用失败。数据只能从file_descriptor[0]中读取,数据也只能写入到file_descriptor[1],不能倒过来。

    例子:
    首先,我们在原先的进程中创建一个管道,然后再调用fork创建一个新的进程,最后通过管道在两个进程之间传递数据。源文件名为pipe.c,代码如下:
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
    	int data_processed = 0;
    	int filedes[2];
    	const char data[] = "Hello pipe!";
    	char buffer[BUFSIZ + 1];
    	pid_t pid;
    	//清空缓冲区
    	memset(buffer, '\0', sizeof(buffer));
    
    	if(pipe(filedes) == 0)
    	{
    		//创建管道成功
    		//通过调用fork创建子进程
    		pid = fork();
    		if(pid == -1)
    		{
    			fprintf(stderr, "Fork failure");
    			exit(EXIT_FAILURE);
    		}
    		if(pid == 0)
    		{
    			//子进程中
    			//读取数据
    			data_processed = read(filedes[0], buffer, BUFSIZ);
    			printf("Read %d bytes: %s\n", data_processed, buffer);
    			exit(EXIT_SUCCESS);
    		}
    		else
    		{
    			//父进程中
    			//写数据
    			data_processed = write(filedes[1], data, strlen(data));
    			printf("Wrote %d bytes: %s\n", data_processed, data);
    			//休眠2秒,主要是为了等子进程先结束,这样做也只是纯粹为了输出好看而已
    			//父进程其实没有必要等等子进程结束
    			sleep(2);
    			exit(EXIT_SUCCESS);
    		}
    	}
    	exit(EXIT_FAILURE);
    }
    运行结果为:


    可见,子进程读取了父进程写到filedes[1]中的数据,如果在父进程中没有sleep语句,父进程可能在子进程结束前结束,这样你可能将看到两个输入之间有一个命令提示符分隔。

    四、把管道用作标准输入和标准输出
    下面来介绍一种用管道来连接两个进程的更简洁方法,我们可以把文件描述符设置为一个已知值,一般是标准输入0或标准输出1。这样做最大的好处是可以调用标准程序,即那些不需要以文件描述符为参数的程序。

    为了完成这个工作,我们还需要两个函数的辅助,它们分别是dup函数或dup2函数,它们的原型如下
    #include <unistd.h>
    int dup(int file_descriptor);
    int dup2(int file_descriptor_one, int file_descriptor_two);
    dup调用创建一个新的文件描述符与作为它的参数的那个已有文件描述符指向同一个文件或管道。对于dup函数而言,新的文件描述总是取最小的可用值。而dup2所创建的新文件描述符或者与int file_descriptor_two相同,或者是第一个大于该参数的可用值。所以当我们首先关闭文件描述符0后调用dup,那么新的文件描述符将是数字0.

    例子
    在下面的例子中,首先打开管道,然后fork一个子进程,然后在子进程中,使标准输入指向读管道,然后关闭子进程中的读管道和写管道,只留下标准输入,最后调用execlp函数来启动一个新的进程od,但是od并不知道它的数据来源是管道还是终端。父进程则相对简单,它首先关闭读管道,然后在写管道中写入数据,再关闭写管道就完成了它的任务。源文件为pipe2.c,代码如下:
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
    	int data_processed = 0;
    	int pipes[2];
    	const char data[] = "123";
    	pid_t pid;
    
    	if(pipe(pipes) == 0)
    	{
    		pid = fork();
    		if(pid == -1)
    		{
    			fprintf(stderr, "Fork failure!\n");
    			exit(EXIT_FAILURE);
    		}
    		if(pid == 0)
    		{
    			//子进程中
    			//使标准输入指向fildes[0]
    			close(0);
    			dup(pipes[0]);
    			//关闭pipes[0]和pipes[1],只剩下标准输入
    			close(pipes[0]);
    			close(pipes[1]);
    			//启动新进程od
    			execlp("od", "od", "-c", 0);
    			exit(EXIT_FAILURE);
    		}
    		else
    		{
    			//关闭pipes[0],因为父进程不用读取数据
    			close(pipes[0]);
    			data_processed = write(pipes[1], data, strlen(data));
    			//写完数据后,关闭pipes[1]
    			close(pipes[1]);
    			printf("%d - Wrote %d bytes\n", getpid(), data_processed);
    		}
    	}
    	exit(EXIT_SUCCESS);
    }
    运行结果为:


    从运行结果中可以看出od进程正确地完成了它的任务,与在shell中直接输入od -c和123的效果一样。

    五、关于管道关闭后的读操作的讨论
    现在有这样一个问题,假如父进程向管道file_pipe[1]写数据,而子进程在管道file_pipe[0]中读取数据,当父进程没有向file_pipe[1]写数据时,子进程则没有数据可读,则子进程会发生什么呢?再者父进程把file_pipe[1]关闭了,子进程又会有什么反应呢?

    当写数据的管道没有关闭,而又没有数据可读时,read调用通常会阻塞,但是当写数据的管道关闭时,read调用将会返回0而不是阻塞。注意,这与读取一个无效的文件描述符不同,read一个无效的文件描述符返回-1。

    六、匿名管道的缺陷
    看了这么多相信大家也知道它的一个缺点,就是通信的进程,它们的关系一定是父子进程的关系,这就使得它的使用受到了一点的限制,但是我们可以使用命名管道来解决这个问题。命名管道将在下一篇文章:Linux进程间通信——使用命名管道中介绍。

    展开全文
  • Linux进程间通信编程

    2016-10-04 12:26:49
    3.消息队列 消息队列就是一个消息的链表.可以把消息看作一个记录,具有特定的格式.进程可以向中按照一定的规则添加新消息...另一些进程则可以从消息队列中读走消息. 优点:比信号传送的信息量多 能传送有格式的字节流

    3.消息队列

    消息队列就是一个消息的链表.可以把消息看作一个记录,具有特定的格式.进程可以向中按照一定的规则添加新消息;另一些进程则可以从消息队列中读走消息.

     

    优点:比信号传送的信息量多 能传送有格式的字节流

     

    目前主要有两种类型的消息队列:

    1.系统V消息队列是随内核持续的,只有在内核重起或者人工删除时,该消息队列才会被删除 (系统V消息队列目前被大量使用)

    2.消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述字,必须提供该消息队列的键值

     

       消息队列的创建

    IPC_CREAT

          创建新的消息队列

    IPC_EXCL

          IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。

    IPC_NOWAIT

           读写消息队列要求无法得到满足时,不阻塞

     

    4.共享内存

            .共享内存被多个进程共享的一部分物理内存.共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容.

     

    共享内存实现分为两个步骤:

             一、创建共享内存,使用shmget函数

             二、映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数

    共享内存的创建

    int shmget ( key_t key, int size, int shmflg )

       key标识共享内存的键值: 0/IPC_PRIVATE。 当key的取值为IPC_PRIVATE,则函数shmget()将创建一块新的共享内存;如果key的取值为0,而参数shmflg中又设置IPC_PRIVATE这个标志,则同样会创建一块新的共享内存。

         返回值:如果成功,返回共享内存标识符;如果失败,返回-1

     

    char * shmat ( int shmid, char *shmaddr, int flag)

    参数:

    shmidshmget函数返回的共享存储标识符

    flag:决定以什么方式来确定映射的地址(通常为0

    返回值:

       如果成功,则返回共享内存映射到进程中的地址;如果失败,则返回- 1当一个进程不再需要共享内存时,需要把它从进程地址空间中脱离。

         

    5.信号量

    除了用于访问控制外,还可用于进程同步

     

    展开全文
  • Linux下的进程间通信-详解

    万次阅读 多人点赞 2008-11-19 17:28:00
    详细的讲述进程间通信在这里绝对是不可能的事情,而且笔者很难有信心说自己对这一部分内容的认识达到了什么样的地步,所以在这一节的开头首先向大家推荐著 名作者Richard Stevens的著名作品:《Advanced Programming...

       详细的讲述进程间通信在这里绝对是不可能的事情,而且笔者很难有信心说自己对这一部分内容的认识达到了什么样的地步,所以在这一节的开头首先向大家推荐著 名作者Richard Stevens的著名作品:《Advanced Programming in the UNIX Environment》,它的中文译本《UNIX环境高级编程》已有机械工业出版社出版,原文精彩,译文同样地道,如果你的确对在Linux下编程有浓 厚的兴趣,那么赶紧将这本书摆到你的书桌上或计算机旁边来。说这么多实在是难抑心中的景仰之情,言归正传,在这一节里,我们将介绍进程间通信最最初步和最 最简单的一些知识和概念。
       首先,进程间通信至少可以通过传送打开文件来实现,不同的进程通过一个或多个文件来传递信息,事实上,在很多应用系统里,都使用了这种方法。但一般说来, 进程间通信(IPC:InterProcess Communication)不包括这种似乎比较低级的通信方法。Unix系统中实现进程间通信的方法很多,而且不幸的是,极少方法能在所有的Unix系 统中进行移植(唯一一种是半双工的管道,这也是最原始的一种通信方式)。而Linux作为一种新兴的操作系统,几乎支持所有的Unix下常用的进程间通信 方法:管道、消息队列、共享内存、信号量、套接口等等。下面我们将逐一介绍。

       2.3.1 管道
       管道是进程间通信中最古老的方式,它包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信
       无名管道由pipe()函数创建:
       #include <unistd.h>
       int pipe(int filedis[2]);
       参数filedis返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。下面的例子示范了如何在父进程和子进程间实现通信。

    #define INPUT 0
    #define OUTPUT 1

    void main() {
    int file_descriptors[2];
    /*定义子进程号 */
    pid_t pid;
    char buf[256];
    int returned_count;
    /*创建无名管道*/
    pipe(file_descriptors);
    /*创建子进程*/
    if((pid = fork()) == -1) {
    printf("Error in fork/n");
    exit(1);
    }
    /*执行子进程*/
    if(pid == 0) {
    printf("in the spawned (child) process.../n");
    /*子进程向父进程写数据,关闭管道的读端*/
    close(file_descriptors[INPUT]);
    write(file_descriptors[OUTPUT], "test data", strlen("test data"));
    exit(0);
    } else {
    /*执行父进程*/
    printf("in the spawning (parent) process.../n");
    /*父进程从管道读取子进程写的数据,关闭管道的写端*/
    close(file_descriptors[OUTPUT]);
    returned_count = read(file_descriptors[INPUT], buf, sizeof(buf));
    printf("%d bytes of data received from spawned process: %s/n",
    returned_count, buf);
    }
    }
       在Linux系统下,有名管道可由两种方式创建:命令行方式mknod系统调用和函数mkfifo。下面的两种途径都在当前目录下生成了一个名为myfifo的有名管道:
         方式一:mkfifo("myfifo","rw");
         方式二:mknod myfifo p
       生成了有名管道后,就可以使用一般的文件I/O函数如open、close、read、write等来对它进行操作。下面即是一个简单的例子,假设我们已经创建了一个名为myfifo的有名管道。
      /* 进程一:读有名管道*/
    #include <stdio.h>
    #include <unistd.h>
    void main() {
    FILE * in_file;
    int count = 1;
    char buf[80];
    in_file = fopen("mypipe", "r");
    if (in_file == NULL) {
    printf("Error in fdopen./n");
    exit(1);
    }
    while ((count = fread(buf, 1, 80, in_file)) > 0)
    printf("received from pipe: %s/n", buf);
    fclose(in_file);
    }
      /* 进程二:写有名管道*/
    #include <stdio.h>
    #include <unistd.h>
    void main() {
    FILE * out_file;
    int count = 1;
    char buf[80];
    out_file = fopen("mypipe", "w");
    if (out_file == NULL) {
    printf("Error opening pipe.");
    exit(1);
    }
    sprintf(buf,"this is test data for the named pipe example/n");
    fwrite(buf, 1, 80, out_file);
    fclose(out_file);
    }

       2.3.2 消息队列
       消息队列用于运行于同一台机器上的进程间通信,它和管道很相似,是一个在系统内核中用来保存消息的队列,它在系统内核中是以消息链表的形式出现。消息链表中节点的结构用msg声明。
    事实上,它是一种正逐渐被淘汰的通信方式,我们可以用流管道或者套接口的方式来取代它,所以,我们对此方式也不再解释,也建议读者忽略这种方式。

       2.3.3 共享内存
        共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据不需要在不同的进程间复制。通常由一个进程创建一块共享内存区,其余进程对这块内存区进行 读写。得到共享内存有两种方式:映射/dev/mem设备内存映像文件前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的将是 实际的物理内存,在Linux系统下,这只有通过限制Linux系统存取的内存才可以做到,这当然不太实际。常用的方式是通过shmXXX函数族来实现利 用共享内存进行存储的。
       首先要用的函数是shmget,它获得一个共享存储标识符。

         #include <sys/types.h>
         #include <sys/ipc.h>
         #include <sys/shm.h>

          int shmget(key_t key, int size, int flag);
        这个函数有点类似大家熟悉的malloc函数,系统按照请求分配size大小的内存用作共享内存。Linux系统内核中每个IPC结构都有的一个非负整数 的标识符,这样对一个消息队列发送消息时只要引用标识符就可以了。这个标识符是内核由IPC结构的关键字得到的,这个关键字,就是上面第一个函数的 key。数据类型key_t是在头文件sys/types.h中定义的,它是一个长整形的数据。在我们后面的章节中,还会碰到这个关键字。
      
         当共享内存创建后,其余进程可以调用shmat()将其连接到自身的地址空间中
       void *shmat(int shmid, void *addr, int flag);
       shmid为shmget函数返回的共享存储标识符,addr和flag参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址,进程可以对此进程进行读写操作。
        使用共享存储来实现进程间通信的注意点是对数据存取的同步,必须确保当一个进程去读取数据时,它所想要的数据已经写好了。通常,信号量被要来实现对共享存 储数据存取的同步,另外,可以通过使用shmctl函数设置共享存储内存的某些标志位如SHM_LOCK、SHM_UNLOCK等来实现

       2.3.4 信号量
       信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是前一节的共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:
       (1) 测试控制该资源的信号量。
       (2) 若此信号量的值为正,则允许进行使用该资源。进程将信号量减1
       (3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。
       (4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。
        维护信号量状态的是Linux内核操作系统而不是用户进程。我们可以从头文件/usr/src/linux/include /linux /sem.h 中看到内核用来维护信号量状态的各个结构的定义。信号量是一个数据集合,用户可以单独使用这一集合的每个元素。要调用的第一个函数是semget,用以获 得一个信号量ID。

    struct sem {
      short sempid;/* pid of last operaton */
      ushort semval;/* current value */
      ushort semncnt;/* num procs awaiting increase in semval */
      ushort semzcnt;/* num procs awaiting semval = 0 */
    }

       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>
       int semget(key_t key, int nsems, int flag);

       key是前面讲过的IPC结构的关键字,flag将来决定是创建新的信号量集合,还是引用一个现有的信号量集合。nsems是该集合中的信号量数。如果是创建新 集合(一般在服务器中),则必须指定nsems;如果是引用一个现有的信号量集合(一般在客户机中)则将nsems指定为0。

       semctl函数用来对信号量进行操作。
       int semctl(int semid, int semnum, int cmd, union semun arg);
       不同的操作是通过cmd参数来实现的,在头文件sem.h中定义了7种不同的操作,实际编程时可以参照使用。
      
         semop函数自动执行信号量集合上的操作数组
       int semop(int semid, struct sembuf semoparray[], size_t nops);
       semoparray是一个指针,它指向一个信号量操作数组。nops规定该数组中操作的数量。

       下面,我们看一个具体的例子,它创建一个特定的IPC结构的关键字和一个信号量,建立此信号量的索引,修改索引指向的信号量的值,最后我们清除信号量。在下面的代码中,函数ftok生成我们上文所说的唯一的IPC关键字。

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/sem.h>
    #include <sys/ipc.h>
    void main() {
    key_t unique_key; /* 定义一个IPC关键字*/
    int id;
    struct sembuf lock_it;
    union semun options;
    int i;

    unique_key = ftok(".", 'a'); /* 生成关键字,字符'a'是一个随机种子*/
    /* 创建一个新的信号量集合*/
    id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666);
    printf("semaphore id=%d/n", id);
    options.val = 1; /*设置变量值*/
    semctl(id, 0, SETVAL, options); /*设置索引0的信号量*/

    /*打印出信号量的值*/
    i = semctl(id, 0, GETVAL, 0);
    printf("value of semaphore at index 0 is %d/n", i);

    /*下面重新设置信号量*/
    lock_it.sem_num = 0; /*设置哪个信号量*/
    lock_it.sem_op = -1; /*定义操作*/
    lock_it.sem_flg = IPC_NOWAIT; /*操作方式*/
    if (semop(id, &lock_it, 1) == -1) {
    printf("can not lock semaphore./n");
    exit(1);
    }

    i = semctl(id, 0, GETVAL, 0);
    printf("value of semaphore at index 0 is %d/n", i);

    /*清除信号量*/
    semctl(id, 0, IPC_RMID, 0);
    }
    semget()


         可以使用系统调用semget()创建一个新的信号量集,或者存取一个已经存在的信号量集:

    系统调用:semget();
    原型:intsemget(key_t key,int nsems,int semflg);
    返回值:如果成功,则返回信号量集的IPC标识符。如果失败,则返回-1:errno=EACCESS(没有权限)
    EEXIST(信号量集已经存在,无法创建)
    EIDRM(信号量集已经删除)
    ENOENT(信号量集不存在,同时没有使用IPC_CREAT)
    ENOMEM(没有足够的内存创建新的信号量集)
    ENOSPC(超出限制)
        系统调用semget()的第一个参数是关键字值(一般是由系统调用ftok()返回的)。系统内核将此值和系统中存在的其他的信号量集的关键字值进行比较。打开和存取操作与参数semflg中的内容相关。IPC_CREAT如果信号量集在系统内核中不存在,则创建信号量集。IPC_EXCL当和 IPC_CREAT一同使用时,如果信号量集已经存在,则调用失败。如果单独使用IPC_CREAT,则semget()要么返回新创建的信号量集的标识符,要么返回系统中已经存在的同样的关键字值的信号量的标识符。如果IPC_EXCL和IPC_CREAT一同使用,则要么返回新创建的信号量集的标识符,要么返回-1。IPC_EXCL单独使用没有意义。参数nsems指出了一个新的信号量集中应该创建的信号量的个数。信号量集中最多的信号量的个数是在linux/sem.h中定义的:
    #defineSEMMSL32/*<=512maxnumofsemaphoresperid*/
    下面是一个打开和创建信号量集的程序:
    intopen_semaphore_set(key_t keyval,int numsems)
    {
    intsid;
    if(!numsems)
    return(-1);
    if((sid=semget(mykey,numsems,IPC_CREAT|0660))==-1)
    {
    return(-1);
    }
    return(sid);
    }
    };
    ==============================================================
    semop()


    系统调用:semop();
    调用原型:int semop(int semid,struct sembuf*sops,unsign ednsops);
    返回值:0,如果成功。-1,如果失败:errno=E2BIG(nsops大于最大的ops数目)
    EACCESS(权限不够)
    EAGAIN(使用了IPC_NOWAIT,但操作不能继续进行)
    EFAULT(sops指向的地址无效)
    EIDRM(信号量集已经删除)
    EINTR(当睡眠时接收到其他信号)
    EINVAL(信号量集不存在,或者semid无效)
    ENOMEM(使用了SEM_UNDO,但无足够的内存创建所需的数据结构)
    ERANGE(信号量值超出范围)

        第一个参数是关键字值。第二个参数是指向将要操作的数组的指针。第三个参数是数组中的操作的个数。参数sops指向由sembuf组成的数组。此数组是在linux/sem.h中定义的:
    /*semop systemcall takes an array of these*/
    structsembuf{
    ushortsem_num;/*semaphore index in array*/
    shortsem_op;/*semaphore operation*/
    shortsem_flg;/*operation flags*/
    sem_num将要处理的信号量的个数。
    sem_op要执行的操作。
    sem_flg操作标志。
        如果sem_op是负数,那么信号量将减去它的值。这和信号量控制的资源有关。如果没有使用IPC_NOWAIT,那么调用进程将进入睡眠状态,直到信号量控制的资源可以使用为止。如果sem_op是正数,则信号量加上它的值。这也就是进程释放信号量控制的资源。最后,如果sem_op是0,那么调用进程将调用sleep(),直到信号量的值为0。这在一个进程等待完全空闲的资源时使用。
    ===============================================================
    semctl()


    系统调用:semctl();
    原型:int semctl(int semid,int semnum,int cmd,union semunarg);
    返回值:如果成功,则为一个正数。
    如果失败,则为-1:errno=EACCESS(权限不够)
    EFAULT(arg指向的地址无效)
    EIDRM(信号量集已经删除)
    EINVAL(信号量集不存在,或者semid无效)
    EPERM(EUID没有cmd的权利)
    ERANGE(信号量值超出范围)

        系统调用semctl用来执行在信号量集上的控制操作。这和在消息队列中的系统调用msgctl是十分相似的。但这两个系统调用的参数略有不同。因为信号量一般是作为一个信号量集使用的,而不是一个单独的信号量。所以在信号量集的操作中,不但要知道IPC关键字值,也要知道信号量集中的具体的信号量。这两个系统调用都使用了参数cmd,它用来指出要操作的具体命令。两个系统调用中的最后一个参数也不一样。在系统调用msgctl中,最后一个参数是指向内核中使用的数据结构的指针。我们使用此数据结构来取得有关消息队列的一些信息,以及设置或者改变队列的存取权限和使用者。但在信号量中支持额外的可选的命令,这样就要求有一个更为复杂的数据结构。
    系统调用semctl()的第一个参数是关键字值。第二个参数是信号量数目。
        参数cmd中可以使用的命令如下:
        ·IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
        ·IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
        ·IPC_RMID将信号量集从内存中删除。
        ·GETALL用于读取信号量集中的所有信号量的值。
        ·GETNCNT返回正在等待资源的进程数目。
        ·GETPID返回最后一个执行semop操作的进程的PID。
        ·GETVAL返回信号量集中的一个单个的信号量的值。
        ·GETZCNT返回这在等待完全空闲的资源的进程数目。
        ·SETALL设置信号量集中的所有的信号量的值。
        ·SETVAL设置信号量集中的一个单独的信号量的值。
        参数arg代表一个semun的实例。semun是在linux/sem.h中定义的:
    /*arg for semctl systemcalls.*/
    unionsemun{
    intval;/*value for SETVAL*/
    structsemid_ds*buf;/*buffer for IPC_STAT&IPC_SET*/
    ushort*array;/*array for GETALL&SETALL*/
    structseminfo*__buf;/*buffer for IPC_INFO*/
    void*__pad;
        val当执行SETVAL命令时使用。buf在IPC_STAT/IPC_SET命令中使用。代表了内核中使用的信号量的数据结构。array在使用GETALL/SETALL命令时使用的指针。
        下面的程序返回信号量的值。当使用GETVAL命令时,调用中的最后一个参数被忽略:
    intget_sem_val(intsid,intsemnum)
    {
    return(semctl(sid,semnum,GETVAL,0));
    }
        下面是一个实际应用的例子:
    #defineMAX_PRINTERS5
    printer_usage()
    {
    int x;
    for(x=0;x<MAX_PRINTERS;x++)
    printf("Printer%d:%d/n/r",x,get_sem_val(sid,x));
    }
        下面的程序可以用来初始化一个新的信号量值:
    void init_semaphore(int sid,int semnum,int initval)
    {
    union semunsemopts;
    semopts.val=initval;
    semctl(sid,semnum,SETVAL,semopts);
    }
        注意系统调用semctl中的最后一个参数是一个联合类型的副本,而不是一个指向联合类型的指针。

     


       2.3.5 套接口
        套接口(socket)编程是实现Linux系统和其他大多数操作系统中进程间通信的主要方式之一。我们熟知的WWW服务、FTP服务、TELNET服务 等都是基于套接口编程来实现的。除了在异地的计算机进程间以外,套接口同样适用于本地同一台计算机内部的进程间通信。关于套接口的经典教材同样是 Richard Stevens编著的《Unix网络编程:联网的API和套接字》,清华大学出版社出版了该书的影印版。它同样是Linux程序员的必备书籍之一。
        关于这一部分的内容,可以参照本文作者的另一篇文章《设计自己的网络蚂蚁》,那里由常用的几个套接口函数的介绍和示例程序。这一部分或许是Linux进程 间通信编程中最须关注和最吸引人的一部分,毕竟,Internet 正在我们身边以不可思议的速度发展着,如果一个程序员在设计编写他下一个程序的时候,根本没有考虑到网络,考虑到Internet,那么,可以说,他的设 计很难成功。

    3 Linux的进程和Win32的进程/线程比较
       熟悉WIN32编程的人一定知道,WIN32的进程管理方式与Linux上有着很大区别,在UNIX里,只有进程的概念,但在WIN32里却还有一个"线程"的概念,那么Linux和WIN32在这里究竟有着什么区别呢?
        WIN32里的进程/线程是继承自OS/2的。在WIN32里,"进程"是指一个程序,而"线程"是一个"进程"里的一个执行"线索"。从核心上讲, WIN32的多进程与Linux并无多大的区别,在WIN32里的线程才相当于Linux的进程,是一个实际正在执行的代码。但是,WIN32里同一个进 程里各个线程之间是共享数据段的。这才是与Linux的进程最大的不同。
       下面这段程序显示了WIN32下一个进程如何启动一个线程。

    int g;
    DWORD WINAPI ChildProcess( LPVOID lpParameter ){
    int i;
    for ( i = 1; i <1000; i ++) {
    g ++;
    printf( "This is Child Thread: %d/n", g );
    }
    ExitThread( 0 );
    };

    void main()
    {
    int threadID;
    int i;
    g = 0;
    CreateThread( NULL, 0, ChildProcess, NULL, 0, &threadID );
    for ( i = 1; i <1000; i ++) {
    g ++;
    printf( "This is Parent Thread: %d/n", g );
    }
    }

        在WIN32下,使用CreateThread函数创建线程,与Linux下创建进程不同,WIN32线程不是从创建处开始运行的,而是由 CreateThread指定一个函数,线程就从那个函数处开始运行。此程序同前面的UNIX程序一样,由两个线程各打印1000条信息。 threadID是子线程的线程号,另外,全局变量g是子线程与父线程共享的,这就是与Linux最大的不同之处。大家可以看出,WIN32的进程/线程 要比Linux复杂,在Linux要实现类似WIN32的线程并不难,只要fork以后,让子进程调用ThreadProc函数,并且为全局变量开设共享 数据区就行了,但在WIN32下就无法实现类似fork的功能了。所以现在WIN32下的C语言编译器所提供的库函数虽然已经能兼容大多数 Linux/UNIX的库函数,但却仍无法实现fork。
       对于多任务系统,共享数据区是必要的,但也是一个容易引起混乱的问题,在WIN32下,一个程序员很容易忘记线程之间的数据是共享的这一情况,一个线程修 改过一个变量后,另一个线程却又修改了它,结果引起程序出问题。但在Linux下,由于变量本来并不共享,而由程序员来显式地指定要共享的数据,使程序变 得更清晰与安全。
    至于WIN32的"进程"概念,其含义则是"应用程序",也就是相当于UNIX下的exec了。
       Linux也有自己的多线程函数pthread,它既不同于Linux的进程,也不同于WIN32下的进程,关于pthread的介绍和如何在Linux环境下编写多线程程序我们将在另一篇文章《Linux下的多线程编程》中讲述。  

    展开全文
  • 一个自用的进程间通信库(一)

    千次阅读 2017-11-29 17:19:00
    用过一些进程间通信的方法,总觉得用起来不是特别方便,要么步骤太多,要么太大。于是决定给自己写个,主要是想让自己在遇到进程间通信的时候能以更简便的方式使用,记录下来也是整理一下自己的思路。  理想中...
  • 平时看的书很多,了解的也很多,但不喜欢总结,这不昨天面试的时候被问到了进程间通信的方式,因为没有认真总结过,所以昨天...现在将linux和windows的进程间通信方式好好总结一下。  windows的进程间的通信方式有
  • linux进程间通信-----管道总结实例

    千次阅读 2016-04-22 22:17:21
    linux进程间通信-----管道总结实例
  • Linux进程间通信之信号量篇

    千次阅读 2018-04-19 19:28:52
    Linux中支持System V 进程通信的手段有三种:消息队列(Message queue)、信号量(Semaphore)、共享内存(Shared memory)。消息队列点击打开链接、共享内存点击打开链接,今天我们主要来看信号量。。。。在看...
  • linux C语言编程----进程间通信

    千次阅读 2016-06-25 10:46:22
    进程间通信 一个大型的应用软件往往需要众多进程协作,进程间通信(IPC)的重要性显而易见。Linux系统下的进程通信机制基本上是从UNIX平台上的进程通信机制移植而来的。主要的进程间通信机制有以下几种。 无名管道...
  • Linux进程间通信-文件和文件锁

    千次阅读 2016-08-03 08:46:16
    前言使用文件进行进程间通信应该是最先学会的一种IPC方式。任何编程语言中,文件IO都是很重要的知识,所以使用文件进行进程间通信就成了很自然被学会的一种手段。考虑到系统对文件本身存在缓存机制,使用文件进行IPC...
  • posix消息队列与system v消息队列的差别: (1)对posix消息队列的读总是返回最高优先级的最早消息,对system v消息队列的读则可以返回任意指定优先级的消息。 (2)当往一个空队列放置一个消息时,posix消息队列...
  •  是一种异步通信方式。 2、应用:  信号可以直接进行用户空间和内核进程之间的交互,内核进程可以利用signal来通知用户空间发生的系统事件,  反之亦然。 3、信号产生,传递及其响应过
  • linux 进程间通信二 信号量以及实例

    千次阅读 2012-12-12 07:15:11
    当我们在多用户系统,多进程系统,或是两者混合的系统中使用线程操作编写程序时,我们经常会发现我们有段临界代码,在此处我们需要保证一个进程(或是一个线程的执行)需要排他的访问一个资源。
  • 进程间通信有如下一些目的:  数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间。  共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该...
  • 关于linux使用动态进行进程间通讯

    千次阅读 2015-08-27 09:36:20
    因为之前是用共享内存来进行进程间通信,这几天在验证一个思路:使用动态进行进程间通信。 关于动态与静态的区别以及动态的一些特征,这里就不说了,网上很多,百度即可。这里只说说本人的整个研究过程...
  • Linux进程通信

    千次阅读 2019-08-18 16:04:50
    信号是Linux进程间异步通信的唯一机制,用于通知进程一个特定的事件并强迫进程执行对应的处理程序,如用户在键盘上按crtl+c,内核会给当前控制台上正在运行的进程发送一个SIGINT的信号,进程收到该信号执行默认处理...
  • windows进程间通信

    万次阅读 2012-02-22 09:47:17
    摘 要: 随着人们对应用程序的...Microsoft Win32 API提供了多种进程间通信的方法,全面地阐述了这些方法的特点,并加以比较和分析,希望能给读者选择通信方法提供参考。 关键词 进程 进程通信 IPC Win32 API
  • 进程间通信(IPC,InterProcess Communication):是指在不同进程之间传播或交换信息。 一、简单的进程间通信: 命令行:父进程通过exec函数创建子进程时可以附加一些数据。 环境变量:父进程通过exec函数创建...
1 2 3 4 5 ... 20
收藏数 48,506
精华内容 19,402
关键字:

linux 进程间通信库