linux 环境编程学习笔记 第21天 基于普通文件IPC,管道文件,匿名管道


寒假学习 第21天 (linux 高级编程) 笔记总结


一、基于普通文件IPC


IPC(Inter-Process Communication,进程间通信)

例子:

main1.c  向tmp写入数据

 

#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, const char *argv[])
{
    int fd=open("tmp",O_RDWR|O_CREAT|O_TRUNC,0666);
    ftruncate(fd,4);
    int *p=mmap(0,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    int i=0;
    while(1)
    {
        sleep(1);
        *p=i;
        i++;
    }
    close(fd);
    return 0;
}

 


main2.c  读取tmp中的数据

#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>

int main(int argc, const char *argv[])
{
    int fd=open("tmp",O_RDWR);
    int *p=mmap(0,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    while(1)
    {
        sleep(1);
        printf("%d\n",*p);
    }
    close(fd);
    return 0;
}

 

IPC提出的应用背景

进程之间需要同步处理

同步需要通信。

普通文件就是最基本的通信手段

 

信号:系统跟应用程序之间的通信,父子进程之间通信,但两个独立的程序用信号通信是不可行的,因为不知道pid

 

普通文件IPC技术的问题:

          一个进程改变文件,另外一个文件无法感知

解决方法:

         一个特殊的文件,管道文件

 

二、管道文件


1.创建管道文件mkfifo

命令行 mkfifo命令

mkfifo函数

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


2.管道文件的特点

      无法使用映射,只能只用


例子:

 

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

void end(int s)
{
    unlink("my.pipe"); //关闭管道文件
    exit(1);
}

int main(int argc, const char *argv[])
{
    signal(SIGINT,end);
    int fd;
    int i=0;
    mkfifo("my.pipe",0666); //建立管道
    fd=open("my.pipe",O_RDWR); //打开管道
    while(1)
    {
        sleep(1);
        write(fd,&i,4);
        ++i;
    }
    return 0;
}

 

 

 

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, const char *argv[])
{
    int fd;
    int i;
    fd=open("my.pipe",O_RDWR); //打开管道
    while(1)
    {
        read(fd,&i,4);
        printf("%d\n",i);
    }
    return 0;
}


运行看看

 

1.read没有数据read阻塞,而且read后数据删除

2.数据有序

3.打开的描述符既可以读也可以写(two-way双工)

int shutdown(int sockfd, int how);// how: SHUT_WR,SHUT_RD,SHUT_RDWR

关闭读或者写, 实现单向通信,防止混乱

4.管道文件关闭后,数据不持久

5.那为什么产生管道文件这个东西?

管道的数据存储在内核的缓冲中,内核没有对管道文件进行映射,管道文件仅仅是在创建文件描述符的时候,用内核来识别创建文件描述符的内存结构的内存而已。内核创建缓冲,如果是普通文件,就直接与缓冲建立映射关系,如果是管道文件,者建立fifo。


三、匿名管道


  有名的管道的名字仅仅是内核识别是否返回同一个fd的标示

   所以当管道名失去标示作用的时候,实际是可以不要的

   在父子进程之间:打开文件描述符后创建进程。(创建子进程,会拷贝父进程的所有东西)父子进程都有描述符,管道文件就没有价值了

   

匿名管道只能使用在父子进程中。


创建匿名管道

int pipe(int pipefd[2]); //输出2个文件描述符,pipfd[0] 只读,pipfd[1]只写(这样做为了避免混乱)

pipe函数:创建管道-->打开管道-->拷贝管道-->改变管道的读写权限

例子:

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
    int fd[2];
    int r;
    r=pipe(fd);
    printf("%d\n",getpid());
    while(1);
    return 0;
}


ls -l /proc/11519/fd

 

就可以看到匿名管道,一个只读,一个只写

rwx------ 1 root root 64 Feb 10 22:52 0 -> /dev/pts/5
lrwx------ 1 root root 64 Feb 10 22:52 1 -> /dev/pts/5
lrwx------ 1 root root 64 Feb 10 22:52 2 -> /dev/pts/5
lr-x------ 1 root root 64 Feb 10 22:52 3 -> pipe:[4479252]
l-wx------ 1 root root 64 Feb 10 22:52 4 -> pipe:[4479252]


例子:

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
    printf("%d\n",getpid());
    int fd[2];
    int r;
    char buf[20];
    pipe(fd);
    write(fd[1],"hello",5);
    write(fd[1],"word",5);

    r=read(fd[0],buf,sizeof(buf));
    buf[r]=0;
    printf("%s\n",buf);

    write(fd[1],"AAAA",4);
    r=read(fd[0],buf,100);
    buf[r]=0;
    printf("%s\n",buf);

    return 0;
}


输出:22661
helloword
AAAA

 


例子2:父子进程匿名管道

 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, const char *argv[])
{
    int fd[2];
    pipe(fd);
    if(fork()){ //父进程
        close(fd[0]); //父进程只负债写
        while(1)
        {
            write(fd[1],"hello",5);
            sleep(1);
        }

    }else{ //子进程
        close(fd[1]); //子进程只负责读
        char buf[100];
        int r;
        while(1)
        {
            r=read(fd[0],buf,100);
            buf[r]=0;
            printf("%s\n",buf);
        }

    }
    return 0;
}

 

 

例子3:建立2个子进程,分别找1~5000跟5001~1000的素数,并且存入


 

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sched.h>

int idx=0;
int fddata;

void handle(int s)
{
    int status;
    if(s==SIGCHLD){//进程结束信号
        wait(&status);
        idx++;
        close(fddata);
        printf("任务完成!\n");
        if(idx==2) exit(0);
    }
}

int isprimer(int ta)//判断素数
{
    int i;
    for(i=2;i<ta;++i){
        if(ta%i==0){
            return 0;
        }
    }
    return 1;
}

int main(int argc, const char *argv[])
{
    int a,b;
    int id=1;
    int fd[2];
    pipe(fd);
    signal(SIGCHLD,handle);

    while(1)
    {
        if(id==1){  //让子进程1查找2~5000的素数
            a=2;
            b=5000;
        }else if(id==2){//让子进程2查找5000~10000的素数
            a=5001;
            b=10000;
        }

        if(fork()){//父进程:创建子进程,保存有子进程传来的数据.这里是为了创建2个子进程
            id++;
            if(id>2){
                break;
            }
            continue;
        }else{ //子进程
            close(fd[0]);
            int i;
            for(i=a;i<=b;++i){
                if(isprimer(i)){
                    write(fd[1],&i,4);
                }
                sched_yield();//告诉系统,我不执行了,把cpu调度给其他进程,把它拍到进程队列的最后
            }
            printf("%d退出\n",getpid());
            exit(0);
        }
    }
    int re;
    char buf[20];
    close(fd[1]);
    fddata=open("result.txt",O_RDWR|O_CREAT,0666);
    while(1) //父进程:这里为了保留子进程传来的数据
    {
        read(fd[0],&re,4);
        sprintf(buf,"%d\n",re);
        write(fddata,buf,strlen(buf));//是strlen不要搞错成sizeof
        printf("%s",buf);
        sched_yield();//告诉系统,我不执行了,把cpu调度给其他进程,把它拍到进程队列的最后
    }
    return 0;
}