码迷,mamicode.com
首页 > 系统相关 > 详细

linux学习之进程篇(三)

时间:2017-11-11 00:38:09      阅读:270      评论:0      收藏:0      [点我收藏+]

标签:9.png   display   get   虚拟内存   pre   wap   err   操作   char   

进程之间的通信

  每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进行之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷贝到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程之间通信(IPC)

技术分享

进程间通信

1.pipe管道

可以用环形队列实现。队列满的话会阻塞。管道是一种最基本的IPC机制,由pipe函数创建

#include<unistd.h>

int pipe(int filedes[2]);

管道作用于有血缘关系的进程之间,通过fork来传递。

技术分享

调用pipe后,父进程创建管道,fd[1]管道写端,fd[0]管道读端,都是文件描述符,描述符分配是未被使用的最小单元,若最小未被使用的文件描述符是3,则3记录管道的读端,4记录管道的写端,总的来说读端的文件描述符较小,写端的文件描述符较大。父进程fork出子进程,上图中的左边是父进程,右边是子进程。子进程会进程父进程的文件描述表,3仍然指向管道的读端,4指向写端。创建好管道后,要确定好通信方向,有父写子读(关闭父读,关闭子写)和子写父读(关闭子读,关闭父写)两种选择,是单工方式工作。若需要双向通信,需要创建管道,仍是先创建管道,后fork。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
int main(void)
{
    int fd[2];
    char str[1024] = "hello itcast";
    char buf[1024];
    pid_t pid;
    //fd[0] 读端
    //fd[1] 写端
    if (pipe(fd) < 0) {
        perror("pipe");
        exit(1);
    }
    pid = fork();
    //父写子读
    if (pid > 0) {
        //父进程里,关闭父读
        close(fd[0]);
        sleep(5);
        write(fd[1], str, strlen(str));
        close(fd[1]);
        wait(NULL);
    }
    else if (pid == 0) {
        int len, flags;
        //子进程里,关闭子写
        close(fd[1]);

        flags = fcntl(fd[0], F_GETFL);
        flags |= O_NONBLOCK;
        fcntl(fd[0], F_SETFL, flags);
tryagain:
        len = read(fd[0], buf, sizeof(buf));
        if (len == -1) {
            if (errno == EAGAIN) {
                write(STDOUT_FILENO, "try again\n", 10);
                sleep(1);
                goto tryagain;
            }
            else {
                perror("read");
                exit(1);
            }
        }
        write(STDOUT_FILENO, buf, len);
        close(fd[0]);
    }
    else {
        perror("fork");
        exit(1);
    }
    return 0;
}

运行结果:

技术分享

使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志): 
(1) 如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。 
(2)如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。 
(3) 如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止
(4) 如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。

简而言之:写关闭,读端读取管道里内容时,再次读,返回0,相当于读到EOF;写端未关闭,写端暂时无数据,读端读完管道里数据时,再次读会阻塞;读端关闭,写端写管道,产生SIGPIPE信号,写进程默认情况下会终止进程;读端未读管道数据,当写端写满管道后,再次写,阻塞。

管道的这四种特殊情况具有普遍意义。

非阻塞管道,fcntl函数设置O_NONBLOCK标志

fpathconf(int fd,int name)测试管道缓冲区大小,_PC_PIPE_BUF。

2.fifo有名管道

创建一个有名管道,解决无血缘关系的进程通信,fifo:

技术分享

fifo是一个索引节点,不会再磁盘下留下任何大小,所以没有myfifo的大小为0;

技术分享

//写管道
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <fcntl.h> #include <sys/stat.h> #include <string.h> void sys_err(char *str, int exitno) { perror(str); exit(exitno); } int main(int argc, char *argv[]) { int fd; char buf[1024] = "hello xwp\n"; if (argc < 2) { printf("./a.out fifoname\n"); exit(1); } //fd = open(argv[1], O_RDONLY); fd = open(argv[1], O_WRONLY); if (fd < 0) sys_err("open", 1); write(fd, buf, strlen(buf)); close(fd); return 0; }
//读管道
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
void sys_err(char *str, int exitno)
{
    perror(str);
    exit(exitno);
}

int main(int argc, char *argv[])
{
    int fd, len;
    char buf[1024];
    if (argc < 2) {
        printf("./a.out fifoname\n");
        exit(1);
    }

    fd = open(argv[1], O_RDONLY);
    if (fd < 0) 
        sys_err("open", 1);

    len = read(fd, buf, sizeof(buf));
    write(STDOUT_FILENO, buf, len);

    close(fd);

    return 0;
}

gcc fifo_w.c -o fifo_w

gcc fifo_r.c  -o fifo_r

./fifo_w myfifo

./fifo_r myfifo

//函数形式,在编程中使用
#include<sys/types.h> #include<sys/stat.h> int mkfifo(const char *pathname,mode_t mode);

3.内存共享映射

mmap/munmap

 mmap可以把磁盘文件的一部分直接映射到内存,这样文件中的位置直接就对应内存地址,对文件的读写可以直接用指针而不需要read/write函数。

技术分享

                                                                                                    mmap

#include<sys/mman.h>

void *mmap(void *addr,size_t length,int prot,int flags,int fd,off_t offsize); 
具体参数含义
addr :  指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。
length:  代表将文件中多大的部分映射到内存。
prot  :  映射区域(内存)的保护方式。可以为以下几种方式的组合:
                    PROT_EXEC 映射区域可被执行
                    PROT_READ 映射区域可被读取
                    PROT_WRITE 映射区域可被写入
                    PROT_NONE 映射区域不能存取
flags :  影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。
                    MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。
                    MAP_SHARED 对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
                    MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
                    MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
                    MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
                    MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。
fd    :  要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,
          然后对该文件进行映射,可以同样达到匿名内存映射的效果。
offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是PAGE_SIZE(页面大小)的整数倍。

返回值:
      若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。

错误代码:
            EBADF  参数fd 不是有效的文件描述词
            EACCES 存取权限有误。如果是MAP_PRIVATE 情况下文件必须可读,使用MAP_SHARED则要有PROT_WRITE以及该文件要能写入。
            EINVAL 参数start、length 或offset有一个不合法。
            EAGAIN 文件被锁住,或是有太多内存被锁住。
            ENOMEM 内存不足。
用户层的调用很简单,其具体功能就是直接将物理内存直接映射到用户虚拟内存,使用户空间可以直接对物理空间操作。但是对于内核层而言,其具体实现比较复杂。

#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdlib.h>
int main(void)
{
    int fd, len;
    int *p;
    fd = open("hello", O_RDWR);
    if (fd < 0) {
        perror("open");
        exit(1);
    }
    len = lseek(fd, 0, SEEK_END);

    p = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (p == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }

    close(fd);
//映射并没有解除
p[0] = 0x30313233; munmap(p, len); return 0; }

修改磁盘文件时,对应映射的内存也会改变。

技术分享

mmap的实现原理

linux学习之进程篇(三)

标签:9.png   display   get   虚拟内存   pre   wap   err   操作   char   

原文地址:http://www.cnblogs.com/rainbow1122/p/7816773.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!