进程间的通信包括管道,共享内存,信号量通信,消息队列,套借口(socket)和全双工管道通信

首先来看下管道的用法:管道顾名思义,就如同下水道管道一样,当从管道一端流水到另一端的时候,水流的方向是单方向的。某一时刻只能从单方向传递数据,不能双向传递。这种就叫单双工模式。半双工模式只能是一端写数据,一端读数据。来看一个半双工的例子:

(1)在父进程中通过pipe()函数创建一个管道。产生一个描述符,fd[0]指向管道的读端,fd[1]指向管道的写端

(2) 在父进程中调用fork函数产生一个子进程。子进程和父进程都分别指向fd的读端和写端

(3) 在子进程中,关闭fd[0]也就是读的端口,往fd[1]写端口写入数据,在父进程中关闭写端口fd[1],从读端口fd[0]中读取数据并显示在终端上。在父进程中调用了sleep(2)等待2秒的函数,目的是等待子进程写数据

int pipe_function()
{
    int fd[2],pid,line;
    char message[100];
    if (pipe(fd) == -1)
    {
        perror("not create a new process!");
        return 1;
    }
    else if((pid=fork())<0)
    {
        perror("create new process fail!");
        return 1;
    }
    else if(pid==0)
    {
        close(fd[0]);
        printf("Child process send message\n");
        write(fd[1],"Welcome to microsoft!",19);
    }
    else
    {
        close(fd[1]);
        sleep(2);
        printf("Parent process receive message is:\n");
        line=read(fd[0],message,100);
        write(STDOUT_FILENO,message,line);
        printf("\n");
        wait(NULL);
        exit(0);
    }
    return 0;
}

运行结果:

root@zhf-linux:/home/zhf/zhf/c_prj# ./test3

Child process send message

Parent process receive message is:

Welcome to microsof

上面的例子演示了单向通信,如果我们想得到双向通信,父进程在读的同时也给子进程写。要实现这样的功能,我们就必须建立2个管道,一个管道分别是从父进程流向子进程,一个管道是从子进程流向父进程。代码实现如下:定义了两个管道fd和fd1。

int pipe_function_multiy()
{
    int fd[2],pid,line,fd1[2],line1;
    char message[100],message1[100];
    if (pipe(fd) == -1 || pipe(fd1) == -1)
    {
        perror("not create a new process!");
        return 1;
    }
    else if((pid=fork())<0)
    {
        perror("create new process fail!");
        return 1;
    }
    else if(pid==0)
    {
        close(fd[0]);
        printf("Child process send message\n");
        write(fd[1],"Welcome to microsoft!",19);
        sleep(2);
        close(fd1[1]);
        printf("Child process receive message is:\n");
        line1=read(fd1[0],message1,100);
        write(STDOUT_FILENO,message1,line1);
        printf("\n");
        exit(0);
    }
    else
    {
        close(fd[1]);
        sleep(2);
        printf("Parent process receive message is:\n");
        line=read(fd[0],message,100);
        write(STDOUT_FILENO,message,line);
        printf("\n");
        close(fd1[0]);
        printf("parent process send message is:\n");
        write(fd1[1],"hello how are you!",20);
        exit(0);
    }
    return 0;
}

执行结果:

root@zhf-linux:/home/zhf/zhf/c_prj# ./test3

Parent process receive message is:

Child process send message

Welcome to microsof

parent process send message is:

Child process receive message is:

hello how are you!

在上面的例子中,管道只能在有关联的进程中进行,也就是父子进程。那如果不相关的两个进程也需要进行通信该如何解决呢。这里就要到命令管道。通常称为FIFO。通过这个名称可以知道命令管道遵循先进先出的原则。创建一个命令管道有两种方法一种是通过函数创建命名管道,一种是通过shell命令来创建。

首先来看下通过shell命令创建:

(一)首先通过mkfifo创建一个管道文件test

root@zhf-linux:/home/zhf/zhf# mkfifo test

(二)在一个终端中通过cat命令来查看这个命令管道中的数据。由于没有任何写入的数据,因此一直处于等待。这个时候cat命令将一直挂起,直到终端或者有数据发送到FIFO中。

root@zhf-linux:/home/zhf/zhf# cat ./test

(三)然后打开另外一个终端,向FIFO中写入数据

root@zhf-linux:/home/zhf/zhf# echo "hello fifo" > ./test

(四)这个时候cat命令将会输出内容

root@zhf-linux:/home/zhf/zhf# cat ./test

hello fifo

那接下来我们看下通过C代码的方式来实现命令管道的方法:

首先创建2个文件。代表2个进程,一个读,一个写。还是用刚才创建的test这个命令管道文件

test3.c

int mkfifo_function_a()
{
    int fd;
    int pid;
    fd=open(FIFO,O_RDWR);
    printf("write the message:\n");
    write(fd,"hello world",20);
    close(fd);
    
}

test4.c

int mkfifo_function_b()
{
    char msg[100];
    int fd;
    int pid;
    int line;
    fd=open(FIFO,O_RDWR);
    printf("read the message:\n");
    line=read(fd,msg,100);
    close(fd);
    write(STDOUT_FILENO,msg,line);
}

flags=O_RDONLY:open将会调用阻塞,除非有另外一个进程以写的方式打开同一个FIFO,否则一直等待。

flags=O_WRONLY:open将会调用阻塞,除非有另外一个进程以读的方式打开同一个FIFO,否则一直等待。

flags=O_RDONLY|O_NONBLOCK:如果此时没有其他进程以写的方式打开FIFO,此时open也会成功返回,此时FIFO被读打开,而不会返回错误。

flags=O_WRONLY|O_NONBLOCK:立即返回,如果此时没有其他进程以读的方式打开,open会失败打开,此时FIFO没有被打开,返回-1

(一)在一个终端中运行test4.c。显示读取数据。由于没有输入数据,因此一直挂起

root@zhf-linux:/home/zhf/zhf/c_prj#
./test4

read
the message:

(二)
在另外一个终端中运行test3.c。写入数据

root@zhf-linux:/home/zhf/zhf/c_prj#
./test3

write
the message:

(三)此时显示出读取信息:

root@zhf-linux:/home/zhf/zhf/c_prj#
./test4

read
the message:

hello
world

共享内存:

前面介绍到父子进程分别是访问不同的内存,因为子进程拷贝了另外一份内存地址。那么如果想两个进程访问同一块内存地址的数据,就需要用到共享内存了。先来看几个共享内存的函数:

shmget: 创建一块共享内存区域。如果已存在这一块的共享内存。该函数可以打开这个已经存在的共享内存。原型为:

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

第一个参数,与信号量的semget函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget()函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1.

不相关的进程可以通过该函数的返回值访问同一共享内存,它代表程序可能要使用的某个资源,程序对所有共享内存的访问都是间接的,程序先通过调用shmget()函数并提供一个键,再由系统生成一个相应的共享内存标识符(shmget()函数的返回值),只有shmget()函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。

第二个参数,size以字节为单位指定需要共享的内存容量

第三个参数,shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。共享内存的权限标志与文件的读写权限一样,举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。

shmat函数:

shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下:

void *shmat(int shm_id, const void *shm_addr, int shmflg);

第一个参数,shm_id是由shmget()函数返回的共享内存标识。

第二个参数,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。

第三个参数,shm_flg是一组标志位,通常为0。

调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.

shmdt函数:

该函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。它的原型如下:

int shmdt(const void *shmaddr);

参数shmaddr是shmat()函数返回的地址指针,调用成功时返回0,失败时返回-1.

shmctl函数:用来控制共享内存

int shmctl(int shm_id, int command, struct shmid_ds *buf);

第一个参数,shm_id是shmget()函数返回的共享内存标识符。

第二个参数,command是要采取的操作,它可以取下面的三个值 :

  • IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。

  • IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值

  • IPC_RMID:删除共享内存段

第三个参数,buf是一个结构指针,它指向共享内存模式和访问权限的结构。

下面来看下一个简单的父子进程访问共享内存的方法:

int share_memory_function()
{
    int shmid;
    int proj_id;
    key_t key;
    int sizz;
    char *addr;
    pid_t pid;
    key=IPC_PRIVATE;
    shmid=shmget(key,1024,IPC_CREAT|0660);
    if (shmid == -1)
    {
        perror("create share memory failed!");
        return 1;
    }
    addr=(char *)shmat(shmid,NULL,0);
    if(addr == (char *)(-1))
    {
        perror("can not attach");
        return 1;
    }
    strcpy(addr,"welcome to ubuntun");
    pid=fork();
    if (pid == -1)
    {
        perror("error!");
        return 1;
    }
    if (pid == 0)
    {
        printf("Child process string is %s\n",addr);
        exit(0);
    }
    else
    {
        wait(NULL);
        printf("Parent process string is %s\n",addr);
        if (shmdt(addr) == 1)
        {
            perror("release failed");
            return 1;
        }
        if (shmctl(shmid,IPC_RMID,NULL)==-1)
        {
            perror("failed");
            return 1;
        }
    }
    return 0;

}

运行结果:

root@zhf-linux:/home/zhf/zhf/c_prj# ./test4

Child process string is welcome to ubuntun

Parent process string is welcome to ubuntun

在共享内存中,只要获取到了共享内存的地址,如何进程都可以操作内存,这样就会导致一个问题,多个进程对内存中的变量进行读写。从而使得变量的值变得不可控。信号量就可以解决这个问题。当有一个进程要求使用某一个共享内存中的资源时,系统会首先判断该资源的信号量,如果大于0,则可以使用该资源,并且信号量要减一,当不再使用该资源的时候,信号量再加一。如果信号量等于0,就进入了休眠状态。资源不可访问。

来看下信号量的使用:

1、创建信号量

semget函数创建一个信号量集或访问一个已存在的信号量集。

#include <sys/sem.h>

int semget (key_t key, int nsem, int oflag) ;

返回值是一个称为信号量标识符的整数,semop和semctl函数将使用它。

参数nsem指定集合中的信号量数。(若用于访问一个已存在的集合,那就可以把该参数指定为0)

参数oflag可以是SEM_R(read)和SEM_A(alter)常值的组合。(打开时用到),也可以是IPC_CREAT或IPC_EXCL ;

2、打开信号量

使用semget打开一个信号量集后,对其中一个或多个信号量的操作就使用semop(op--operate)函数来执行。

#include <sys/sem.h>

int semop (int semid, struct sembuf * opsptr, size_t nops) ;

参数opsptr是一个指针,它指向一个信号量操作数组,信号量操作由sembuf结构表示:

struct sembuf{

short sem_num; // 除非使用一组信号量,否则它为0

short sem_op; // 信号量在一次操作中需要改变的数据,通常是两个数,

// 一个是-1,即P(等待)操作,一个是+1,即V(发送信号)操作

short sem_flg; // 通常为SEM_UNDO,使操作系统跟踪信号,并在进程没有释放该信号量而终止时,

// 操作系统释放信号量

};

◆参数nops规定opsptr数组中元素个数。

sem_op值:

(1)若sem_op为正,这对应于进程释放占用的资源数。sem_op值加到信号量的值上。(V操作)

(2)若sem_op为负,这表示要获取该信号量控制的资源数。信号量值减去sem_op的绝对值。(P操作)

(3)若sem_op为0,这表示调用进程希望等待到该信号量值变成0

◆如果信号量值小于sem_op的绝对值(资源不能满足要求),则:

(1)若指定了IPC_NOWAIT,则semop()出错返回EAGAIN。

(2)若未指定IPC_NOWAIT,则信号量的semncnt值加1(因为调用进程将进 入休眠状态),然后调用进程被挂起直至:①此信号量变成大于或等于sem_op的绝对值;②从系统中删除了此信号量,返回EIDRM;③进程捕捉到一个信 号,并从信号处理程序返回,返回EINTR。(与消息队列的阻塞处理方式 很相似)

3、信号量操作

semctl函数对一个信号量执行各种控制操作。

#include <sys/sem.h>

int semctl (int semid, int semnum, int cmd, /*可选参数*/ ) ;

第四个参数是可选的,取决于第三个参数cmd。

参数semnum指定信号集中的哪个信号(操作对象)

参数cmd指定以下10种命令中的一种,在semid指定的信号量集合上执行此命令。

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      设置信号量集中的一个单独的信号量的值。

下面来看个具体的应用。首先在/home/zhf/zhf/c_prj/test1.c上创建一个信号量,在模拟系统分配资源。没隔3秒钟就有一个资源被占用。

Test6.c中的代码:

#define RESOURCE 4

int sem_function_set()
{
    key_t key;
    int semid;
    struct sembuf sbuf={0,-1,IPC_NOWAIT};
    union semun arg;
    if ((key=ftok("/home/zhf/zhf/c_prj/test1.c",'c'))==-1)
    {
        perror("ftok error!\n");
        exit(1);
    }
    if ((semid=semget(key,1,IPC_CREAT|0666))==-1)
    {
        perror("segmet error!\n");
        exit(1);
    }
    arg.val=RESOURCE;
    if(semctl(semid,0,SETVAL,arg)==-1)
    {
        perror("semctl errror!\n");
        exit(1);
    }
    while(1)
    {
        if (semop(semid,&sbuf,1)==-1)
        {
            perror("semop error!\n");
            exit(1);
        }
        sleep(3);
    }
    semctl(semid,0,IPC_RMID,0);
    exit(0);
}

test5.c中的代码:

int sem_get()
{
    key_t key;
    int semid,semval;
    union semun arg;
    if((key=ftok("/home/zhf/zhf/c_prj/test1.c",'c'))==-1)
    {
        perror("key errror1\n");
        return 1;
    }
    if((semid=semget(key,1,IPC_CREAT|0666))==-1)
    {
        perror("semget error!\n");
        return 1;
    }
    while(1)
    {
        if((semval=semctl(semid,0,GETVAL,0))==-1)
        {
            perror("semctl error!\n");
            exit(1);
        }
        if(semval > 0)
        {
            printf("%d resource could be used\n",semval);
        }
        else
        {
            printf("no resource could be used\n");
            break;
        }
        sleep(3);
    }
    exit(0);
}

分别在2个终端执行:结果如下

linux c编程:进程间通信的更多相关文章

  1. Linux网络编程--进程间通信(一)

    进程间通信简介(摘自<Linux网络编程>p85) AT&T 在 UNIX System V 中引入了几种新的进程通讯方式,即消息队列( MessageQueues),信号量( s ...

  2. linux系统编程--进程间通信

    IPC方法 Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间.任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问, 要交换数据必须通过内核,在内核中开 ...

  3. Linux环境编程进程间通信机制理解

    一.Linux系统调用主要函数 二.创建进程 1.创建子进程系统调用fork() 2.验证fork()创建子进程效果 3.系统调用fork()与挂起系统调用wait() 三.模拟进程管道通信 四.pi ...

  4. 〖Linux〗Linux高级编程 - 进程间通信(Interprocess Communication)

    [转自: http://blog.csdn.net/Paradise_for_why/article/details/5550619] 这一章就是著名的IPC,这个东西实际的作用和它的名字一样普及.例 ...

  5. Linux系统编程——进程间通信(一)

    基本操作命令: ps -ajx/-aux/-ef 查看进程间状态/的相互关系 top 动态显示系统中的进程 nice 按照指定的优先级运行 /renice 改变正在运行的进程的优先级 kill -9杀 ...

  6. Linux系统编程——进程间通信:管道(pipe)

    管道的概述 管道也叫无名管道,它是是 UNIX 系统 IPC(进程间通信) 的最古老形式,全部的 UNIX 系统都支持这样的通信机制. 无名管道有例如以下特点: 1.半双工,数据在同一时刻仅仅能在一个 ...

  7. Linux系统编程——进程间通信:信号中断处理

    什么是信号? 信号是 Linux 进程间通信的最古老的方式.信号是url=474nN303T2Oe2ehYZjkrggeXCaJPDSrmM5Unoh4TTuty4wSgS0nl4-vl43AGMFb ...

  8. Linux系统编程——进程间通信:命名管道(FIFO)

    命名管道的概述 无名管道,因为没有名字,仅仅能用于亲缘关系的进程间通信(很多其它详情.请看<无名管道>).为了克服这个缺点.提出了命名管道(FIFO).也叫有名管道.FIFO 文件. 命名 ...

  9. Linux系统编程——进程间通信(System V IPC 对象)

    基本查看命令 ipcs  -m查看共享内存        ipcs -s查看信号量        ipcs -q查看消息队列 ipcrm  -m  id 删除共享内存   -M+key值 ipcrm ...

  10. Linux系统编程——进程间通信:共享内存

    概述 url=MdyPihmS_tWLwgWL5CMzaTrwDFHu6euAJJUAjKvlzbJmRw7RfhmkBWwAloo7Y65hLY-kQdHsbqWYP2wc2fk8yq"& ...

随机推荐

  1. fauxbar.bak

    {"options": { "almostdone":"0", "backup_searchEngines":" ...

  2. Oracle 检查表空间使用情况

    --检查表空间使用情况  SELECT f.tablespace_name       , a.total "total (M)"       , f.free "fre ...

  3. dedecms 留言板中引用模板文件方法

    最近在做一个用dedecms搭建的网站,客户提出要有留言板,dedecms带了一个留言板的模块,安装倒是十分简便,但装完后发现界面十分粗糙.装修比较简单,但是发现遇到一个问题:网站通用的导航栏无法显示 ...

  4. 浅谈XXE攻击

    一.XXE,即XML External Entity,XML外部实体.ENTITY 实体,在一个甚至多个XML文档中频繁使用某一条数据,我们可以预先定义一个这条数据的“别名”,即一个ENTITY,然后 ...

  5. CSS 属性选择器的深入挖掘

    CSS 属性选择器,可以通过已经存在的属性名或属性值匹配元素. 属性选择器是在 CSS2 中引入的并且在 CSS3 中得到了很好拓展.本文将会比较全面的介绍属性选择器,尽可能的去挖掘这个选择器在不同场 ...

  6. 用户空间和内核空间通讯之【Netlink 中】

    原文地址:用户空间和内核空间通讯之[Netlink 中] 作者:wjlkoorey258 今天我们来动手演练一下Netlink的用法,看看它到底是如何实现用户-内核空间的数据通信的.我们依旧是在2.6 ...

  7. 利用js实现table增加一行

    简单的方法: 用jquery插件,比如设置该table的id为mytable <table id="mytable"> <tr> <td> 第一 ...

  8. ArrayList和HashSet的比较

    ArrayList是数组存储的方式 HashSet存储会先进行HashCode值得比较(hashcode和equals方法),若相同就不会再存储 HashCode和HashSet类 Hashset就是 ...

  9. 18. 使用模板【从零开始学Spring Boot】

    转:http://blog.csdn.net/linxingliang/article/details/52017098 18.1 使用thymeleaf 整体步骤: (1)       在pom.x ...

  10. Android设计模式之中的一个个样例让你彻底明确装饰者模式(Decorator Pattern)

    导读 这篇文章中我不会使用概念性文字来说明装饰者模式.由于通常概念性的问题都非常抽象.非常难懂.使得读者非常难明确究竟为什么要使用这样的设计模式.我们设计模式的诞生,肯定是前辈们在设计程序的时候遇到了 ...