linux学习之进程篇(三)

进程之间的通信

  每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进行之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程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的实现原理

时间: 2024-11-05 21:50:07

linux学习之进程篇(三)的相关文章

linux学习之进程篇(一)

进程 1.PCB 每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内的进程控制块是task_struct结构体.现在我们全面了解一下其中都有哪些信息. 进程标示符(PID):描述本进程的唯一标示符,用来区别其他进程.父进程id(PPID) 进程的状态,有运行.挂起.停止.僵尸等状态. 进程切换时需要保存和恢复的一些CPU寄存器. 描述虚拟地址空间的信息. 描述控制终端的信息. 当前工作目录 umask掩码 文件描述符表,包含很多指向file结构体的指针 和信号相关的信

linux学习之进程篇(二)

进程原语 1.fork #include<unistd.h> pid_t fork(void); fork 子进程复制父进程,子进程和父进程的PID是不一样的,在克隆pcb时,pid没有复制,fork还有底层的函数,如creat(),clone(),retrun 返回.子进程执行的第一条语句是return. #include<stdio.h> #include<unistd.h> #include<stdlib.h> int main(void) { pid

Linux学习之进程管理(十九)

Linux学习之进程管理 进程查看 查看系统中所有进程,使用BSD操作系统的格式 语法:ps aux 选项: a:显示所有前台进程 x:显示所有后台进程 u:显示这个进程是由哪个用户产生的 语法:ps -le 查看系统中所有进程,使用Linux标准命令格式 选项 l:显示详细信息 e:显示所有进程 USER:该进程是由哪个用户产生的 PID:进程的ID号 %CPU:该进程占用CPU资源的百分比,占用越高,进程越消耗资源. %MEM:该进程占用物理内存的百分比,占用越高,进程越消耗资源. VSZ:

linux学习之进程,线程和程序

                                                                                  程序.进程和线程的概念 1:程序和进程的差别 进程的出现最初是在UNIX下,用于表示多用户,多任务的操作系统环境下,应用程序在内存环境中基本执行单元的概念.进程是UNIX操作系统环境最基本的概念.是系统资源分配的最小单位.UNIX操作系统下的用户管理和资源分配等工作几乎都是操作系统通过对应用程序进程的控制实现的! 当使用c c++ j

Linux学习笔记——第一篇——Ubuntu安装与操作

笔者是Windows的使用者,由于Coding的需要以及在Linux下开发的方便,所以开始使用Linux. 当然笔者还是割舍不下Windows的,毕竟很多通讯工具等软件以及游戏在Linux下是没有发行的,所以笔者使用了虚拟机啊. 下面给出简单的安装过程. 1.下载虚拟机软件,笔者比较喜欢VMPlayer,因为它比较轻便且免费,并且很好的支持了拖拽复制功能(VM TOOL),当然也可以使用如VirtualBox.VPC等 链接:https://my.vmware.com/web/vmware/fr

linux学习之centos(三):网卡配置

Linux系统版本:Centos 6.5 在linux学习之centos(二):虚拟网络三种连接方式和SecureCRT的使用中,使用远程工具SecureCRT,通过“ifconfig eth0 + 具体的ip地址”命令给linux配IP地址,但是这种配置方式存在以下问题: 只能临时生效,一旦重启,需要重新配置: 只能配置ip地址和子网掩码,可以连接内网,是无法连接外网的. 如果想要连接外网,ip地址.子网掩码.网关.DNS缺一不可. 如何真正的给CentOS配一个ip,而且设置成功后,通过使用

Linux学习之基础篇

一.Linux学习的必备条件: 1. 计算器概论不硬件相关知识: 2. 先从Linux癿安装不挃令学起: 3. Linux操作系统的基础技能:『使用者.群组癿概忛』.『权限癿观忛』,『程序的定义』等等: 4. 务必学会vi文书编辑器: 5. Shell不Shell Script癿学习: 6. 一定要会软件管理员:Tarball/RPM/DPKG 等软件管理员的安装方式,对你来说重要的不行了.(嵌入式设备,学术研究单位) 7. 网绚基础癿建立:IP概念,路由的概念等等:

Linux学习之进程管理

|-进程管理     进程常用命令        |- w查看当前系统信息        |- ps进程查看命令        |- kill终止进程        |- 一个存放内存中的特殊目录/proc        |- 进程的优先级        |- 进程的挂起与恢复        |- 通过top命令查看进程        计划任务        |- 计划任务的重要性        |- 一次性计划at和batch        |- 周期性计划crontab    进程管理的概念

马哥Linux学习之查询篇(命令查询和文件查询)

Linux运维工作一般都使用命令完成,在如此多的各种命令中,要想全部记住显然是不太可能也是不必要的,另外,文件的查找在日常操作中也是必不可少的.下面我就总结一下Linux中如何查找命令以及文件. 命令的运行文件路径查询.这个查找的方法是同样是使用命令,这个命令叫which,它能帮我们查找到命令的路径.具体它的详细介绍我们可以man一下哦. 接下来,我们使用which查看命令的路径.先看一下最常用的ls命令吧. 在图中大家可以看到执行的结果显示出来了,不但显示了ls命令的运行文件路径,而且显示了此