Linux - 进程

命令:

  • ps aux/ajx
  • top
  • kill -sigkill/-9 name            kill -l 
  • GDB
    • set follow-fork-mode [parent(默认)| child]
    • set detach-on-fork [on | off]
    • info inferiors
    • inferior id
    • detach inferiors id

 函数:

  • pid_t getpid(void);

一、进程概述

1.程序和进程

(1)程序

(2)进程

 2.进程控制块PCB

 

 二、进程状态转换

1.进程的状态

(1)三态

(2)五态

2.进程相关命令

(1)ps aux/ajx

(2)STAT参数 

(3)top 实时显示进程动态

(4)kill  杀死进程 

3.进程号和相关函数

三、进程的创建

1.进程创建

父子进程之间的关系:
        区别:
            1.fork()函数的返回值不同
                父进程中: >0 返回的子进程的ID
                子进程中: =0
            2.pcb中的一些数据
                当前的进程的id pid
                当前的进程的父进程的id ppid
                信号集

        共同点:
            某些状态下:子进程刚被创建出来,还没有执行任何的写数据的操作
                - 用户区的数据
                - 文件描述符表

父子进程对变量是不是共享的?
            - 刚开始的时候,是一样的,共享的。如果修改了数据,不共享了。
            - 读时共享(子进程被创建,两个进程没有做任何的写的操作),写时拷贝。

        实际上,更准确来说,Linux 的 fork() 使用是通过写时拷贝 (copy- on-write) 实现。
        写时拷贝是一种可以推迟甚至避免拷贝数据的技术。
        内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。
        只有在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。其中的局部变量互不干扰。
        也就是说,资源的复制是在需要写入的时候才会进行,在此之前,以只读方式共享。

注意:fork之后父子进程共享文件,
fork产生的子进程与父进程相同的文件文件描述符指向相同的文件表,引用计数增加,共享文件偏移指针。

2.GDB多进程调试  

四、exec()函数族 

1.介绍

一般使用方法:

        首先fork() 创建一个子进程,子进程执行exec("a.out")命令,然后子进程从a.out的main()函数开始执行;

        进程调用exec(“a.out")函数,内核区数据不变,用户区数据被替换为a.out的用户区数据

2.exec函数族 

(1)execl()

    #include <unistd.h>
    int execl(const char *path, const char *arg, ...);
        - 参数:
            - path:需要指定的执行的文件的路径
                a.out /home/nowcoder/a.out 推荐使用绝对路径
                ./a.out hello world

            - arg:是执行可执行文件所需要的参数列表
                第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称
                从第二个参数开始往后,就是程序执行所需要的的参数列表。
                参数最后需要以NULL结束(哨兵)

        - 返回值:
            只有当调用失败,才会有返回值,返回-1,并且设置errno
            如果调用成功,没有返回值。调用成功,则代码区被替换,其中没有execl()函数了,所以没有返回值。

execl("/bin/ps", "ps", "aux", NULL);

示例:

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

int main() {

    // 创建一个子进程,在子进程中执行exec函数族中的函数
    pid_t pid = fork();

    if(pid > 0) {
        // 父进程
        printf("i am parent process, pid : %d\n",getpid());
        sleep(1);
    }else if(pid == 0) {
        // 子进程
        execl("hello","hello",NULL);

        //execl("/bin/ps", "ps", "aux", NULL);
        //perror("execl");
        printf("i am child process, pid : %d\n", getpid());

    }

    for(int i = 0; i < 3; i++) {
        printf("i = %d, pid = %d\n", i, getpid());
    }


    return 0;
}

2.execlp()

    #include <unistd.h>
    int execlp(const char *file, const char *arg, ... );
        - 会到环境变量中查找指定的可执行文件,如果找到了就执行,找不到就执行不成功。
        - 参数:
            - file:需要执行的可执行文件的文件名
                a.out
                ps

execlp("ps", "ps", "aux", NULL);

3.其它

        int execv(const char *path, char *const argv[]);
            -argv是需要的参数的一个字符串数组
        char * argv[] = {"ps", "aux", NULL};
        execv("/bin/ps", argv);


        int execve(const char *filename, char *const argv[], char *const envp[]);
        char * envp[] = {"/home/nowcoder", "/home/bbb", "/home/aaa"};

五、进程退出、孤儿进程、僵尸进程

1.进程退出

标准C库:

        #include <stdlib.h>
        void exit(int status);
调用退出处理函数,刷新IO缓冲,关闭文件描述符,然后调用_exit(),
Linux系统:
        #include <unistd.h>
        void _exit(int status);

 2.孤儿进程(orphan)

3.僵尸进程(zombie)

六、进程回收 

 1.wait()

    #include <sys/types.h>
    #include <sys/wait.h>
    pid_t wait(int *wstatus);
        功能:等待任意一个子进程结束,如果任意一个子进程结束了,wait()函数会回收子进程的资源
        参数:int *wstatus
            进程退出时的状态信息,传入的是一个int类型的地址,传出参数。
        返回值:
            - 成功:返回被回收的子进程的id
            - 失败:-1 (所有的子进程都结束,调用函数失败)

    调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出或者收到一个不能被忽略的信号时才被唤醒(相当于继续往下执行)
    如果没有子进程了,函数立刻返回,返回-1;如果子进程都已经结束了,也会立即返回,返回-1.

 示例:

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>


int main() {

    // 有一个父进程,创建5个子进程(兄弟)
    pid_t pid;

    // 创建5个子进程
    fork()在父进程中返回子进程号,在子进程中返回0
    for(int i = 0; i < 5; i++) {
        pid = fork();
        //父进程创建的子进程在fork()之后继续执行;在子进程中,fork()返回0
        if(pid == 0) {
            //pid==0说明当前是子进程,跳出循环,避免子进程继续创建子进程
            //注意:子进程中fork()返回值并不是子进程的进程号
            break;
        }
    }

    if(pid > 0) {
        // 父进程
        while(1) {
            printf("parent, pid = %d\n", getpid());

            // int ret = wait(NULL);
            int st;
            int ret = wait(&st);

            if(ret == -1) {
                break;
            }

            if(WIFEXITED(st)) {
                // 是不是正常退出
                printf("退出的状态码:%d\n", WEXITSTATUS(st));
            }
            if(WIFSIGNALED(st)) {
                // 是不是异常终止
                printf("被哪个信号干掉了:%d\n", WTERMSIG(st));
            }

            printf("child die, pid = %d\n", ret);

            sleep(1);
        }

    } else if (pid == 0){
        // 子进程
         while(1) {
            printf("child, pid = %d\n",getpid());    
            sleep(1);       
         }

        exit(0);
    }

    return 0; // exit(0)
}

 2.waitpid()

    #include <sys/types.h>

    #include <sys/wait.h>

    pid_t waitpid(pid_t pid, int *wstatus, int options);

        功能:回收指定进程号的子进程,可以设置是否阻塞

        参数:

            - pid:

                pid > 0 : 某个子进程的pid

                pid = 0 : 回收当前进程组的所有子进程    

                pid = -1 : 回收所有的子进程,相当于 wait()  (最常用)

                pid < -1 : pid绝对值为某个进程组的组id,回收指定进程组中的子进程

            - options:设置阻塞或者非阻塞

                0 : 阻塞

                WNOHANG : 非阻塞

            - 返回值:

                > 0 : 返回子进程的id

                = 0 : options=WNOHANG, 表示还有子进程活着

                = -1 :错误,或者没有子进程了

七、进程间通信 

1.概念

2.Linux进程间通信的方式

3.匿名管道

4.管道的特点

5.为什么可以使用管道进行进程间通信

6.管道的数据结构

7.匿名管道的使用

   #include <unistd.h>

    int pipe(int pipefd[2]);

        功能:创建一个匿名管道,用来进程间通信。

        参数:int pipefd[2] 文件描述符,这个数组是一个传出参数。

            pipefd[0] 对应的是管道的读端

            pipefd[1] 对应的是管道的写端

        返回值:

            成功 0

            失败 -1

    管道默认是阻塞的:如果管道中没有数据,read阻塞,如果管道满了,write阻塞

    注意:匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程

 示例1:父子进程通信(read、write)

// 子进程发送数据给父进程,父进程读取到数据输出
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(){

    //在fork()之前创建pipe
    int pipefd[2];
    int ret = pipe(pipefd);
    if(ret == -1){
        perror("pipe");
        exit(-1);
    }

    //创建子进程
    pid_t pid = fork();
    if(pid>0){
        //父进程

        //关闭写端
        close(pipefd[1]);

        char buf[1024];
        while(1){
            int len = read(pipefd[0],buf,sizeof(buf));  //需要给定缓冲区大小
            printf("parent receive: %s ,pid: %d\n",buf,getpid());

            // char * str = "i am parent";      //" "字符串为const char *
            // write(pipefd[1],str,strlen(str));
            // sleep(1);
        }
        
    }else if(pid == 0){
        //子进程

        //关闭读端
        close(pipefd[0]);

        char buf[1024];
        while(1){
            char * str = "i am child";      //" "字符串为const char *
            write(pipefd[1],str,strlen(str));
            //sleep(1);

            // int len = read(pipefd[0],buf,sizeof(buf));  //需要给定缓冲区大小
            // printf("child receive: %s ,pid: %d\n",buf,getpid());
        }
    }

    return 0;
}

示例2:实现 ps aux | grep xxx 

     实现 ps aux | grep xxx 父子进程间通信

   

    子进程: ps aux, 子进程结束后,将数据发送给父进程

    父进程:获取到数据,过滤

   

    子进程将标准输出 stdout_fileno 重定向到管道的写端。  

    //创建管道

    int pipe(int pipefd[2]);

    //在进程中调用指定可执行文件

    //execlp()执行成功不会返回;如果调用失败,返回-1,从原程序调用点接着往下执行

    int execlp(const char *file, const char *arg, ... );    

    //重定向文件描述符   new指向old

    int dup2(int old,int new);      

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wait.h>

int main() {

    // 创建一个管道
    int fd[2];
    int ret = pipe(fd);

    if(ret == -1) {
        perror("pipe");
        exit(0);
    }

    // 创建子进程
    pid_t pid = fork();

    if(pid > 0) {
        // 父进程
        // 关闭写端
        close(fd[1]);
        // 从管道中读取
        char buf[1024] = {0};

        int len = -1;
        while((len = read(fd[0], buf, sizeof(buf) - 1)) > 0) {
            // 过滤数据输出
            printf("%s", buf);
            memset(buf, 0, 1024);   //清空数据
        }
         //回收子进程资源
        wait(NULL);    

    } else if(pid == 0) {
        // 子进程
        // 关闭读端
        close(fd[0]);

        // 文件描述符的重定向 stdout_fileno -> fd[1]   
        //否则,ps命令会将结果出到终端
        dup2(fd[1], STDOUT_FILENO);
        //执行ps命令,参数为aux
        execlp("ps", "ps", "aux", NULL);   
        //execlp()执行成功不会返回;如果调用失败,返回-1,从原程序调用点接着往下执行 
        perror("execlp");
        exit(0);
    } else {
        //创建管道失败
        perror("fork");
        exit(0);
    }

    return 0;
}

8.管道的读写特点

管道的读写特点:
        使用管道时,需要注意以下几种特殊的情况(假设都是阻塞I/O操作)

1.     所有的指向管道写端的文件描述符都关闭了(管道写端引用计数为0),
        有进程从管道的读端读数据,
        那么管道中剩余的数据被读取以后,
        再次read会返回0,就像读到文件末尾一样。

2.     如果有指向管道写端的文件描述符没有关闭(管道的写端引用计数大于0),
        而持有管道写端的进程也没有往管道中写数据,这个时候有进程从管道中读取数据,
        那么管道中剩余的数据被读取后,
        再次read会阻塞,直到管道中有数据可以读了才读取数据并返回。

3.     如果所有指向管道读端的文件描述符都关闭了(管道的读端引用计数为0),
        这个时候有进程向管道中写数据,
        那么该进程会收到一个信号SIGPIPE, 通常会导致进程异常终止。

4.     如果有指向管道读端的文件描述符没有关闭(管道的读端引用计数大于0),
        而持有管道读端的进程也没有从管道中读数据,
        这时有进程向管道中写数据,
        那么在管道被写满的时候再次write会阻塞,直到管道中有空位置才能再次写入数据并返回。


总结:(假设都是阻塞I/O操作)
    读管道:
                管道中有数据,read返回实际读到的字节数。
                管道中无数据:
                    写端被全部关闭,read返回0(相当于读到文件的末尾)
                    写端没有完全关闭,read阻塞等待

    写管道:
                管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号)
                管道读端没有全部关闭:
                    管道已满,write阻塞
                    管道没有满,write将数据写入,并返回实际写入的字节数

设置管道读非阻塞:

    int flags = fcntl(fd[0], F_GETFL);          // 获取原来的flag

    flags |= O_NONBLOCK;                        // 修改flag的值   变为非阻塞

    fcntl(fd[0], F_SETFL, flags);                 // 设置新的flag

9.有名管道(FIFO) 

(1)概念

 (2)有名管道的使用

 (3)示例:两个不相关进程间通信

write.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

// 向管道中写数据
int main() {

    // 1.判断文件是否存在
    int ret = access("test", F_OK);
    if(ret == -1) {
        printf("管道不存在,创建管道\n");
        
        // 2.创建管道文件
        ret = mkfifo("test", 0664);

        if(ret == -1) {
            perror("mkfifo");
            exit(0);
        }       
    }

    // 3.以只写的方式打开管道
    int fd = open("test", O_WRONLY);
    if(fd == -1) {
        perror("open");
        exit(0);
    }

    // 写数据
    for(int i = 0; i < 100; i++) {
        char buf[1024];
        sprintf(buf, "hello, %d\n", i);    //向buf写数据
        printf("write data : %s\n", buf);
        write(fd, buf, strlen(buf));
        sleep(1);
    }

    close(fd);

    return 0;
}

read.c 

// 从管道中读取数据
int main() {

    // 1.打开管道文件
    int fd = open("test", O_RDONLY);
    if(fd == -1) {
        perror("open");
        exit(0);
    }

    // 读数据
    while(1) {
        char buf[1024] = {0};
        int len = read(fd, buf, sizeof(buf));
        if(len == 0) {
            printf("写端断开连接了...\n");
            break;
        }
        printf("recv buf : %s\n", buf);
    }

    close(fd);

    return 0;
}

(4)注意事项

有名管道的注意事项:
        1.一个为只读而打开一个管道的进程会阻塞,直到另外一个有写权限的进程打开管道
        2.一个为只写而打开一个管道的进程会阻塞,直到另外一个有读权限的进程打开管道

    读管道:
        管道中有数据,read返回实际读到的字节数
        管道中无数据:
            管道写端被全部关闭,read返回0,(相当于读到文件末尾)
            写端没有全部被关闭,read阻塞等待
    
    写管道:
        管道读端被全部关闭,进行异常终止(收到一个SIGPIPE信号)
        管道读端没有全部关闭:
            管道已经满了,write会阻塞
            管道没有满,write将数据写入,并返回实际写入的字节数。

10、有名管道——实现简单聊天功能 

(1)概要

 (2)示例1:简单版(轮流发送一条消息)

chatA.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

int main(){
    //1.判断有名管道是否存在
    int ret = access("fifo1",F_OK);
    if(ret == -1){
        printf("管道不存在,创建对应有名管道fifo1\n");
        ret = mkfifo("fifo1",0664);
        if(ret == -1){
            perror("mkfifo");
            exit(0);
        }
    }

    ret = access("fifo2",F_OK);
    if(ret == -1){
        printf("管道不存在,创建对应有名管道fifo2\n");
        ret = mkfifo("fifo2",0664);
        if(ret == -1){
            perror("mkfifo");
            exit(0);
        }
    }

    //2.以只写的方式打开fifo1
    int fdw = open("fifo1",O_WRONLY);
    if(fdw == -1){
        perror("open");
        exit(0);
    }
    printf("打开fifo1成功,等待写入...\n");

    //3.以只读的方式打开fifo2
    int fdr = open("fifo2",O_RDONLY);
    if(fdr == -1){
        perror("open");
        exit(0);
    }
    printf("打开fifo2成功,等待读取...\n");

    char buf[128];
    //4.循环写读数据
    while(1){
        //清空数据
        memset(buf,0,128);
        printf("请输入:");
        //获取标准输入的数据
        fgets(buf,128,stdin);
        //写数据
        ret = write(fdw,buf,strlen(buf));
        if(ret == -1){
            perror("write");
            exit(0);
        }

        //读数据
        memset(buf,0,128);
        ret = read(fdr,buf,128);
        if(ret <= 0){
            //ret=0,读完;ret=-1,调用失败
            perror("read");
            break;
        }
        printf("读到消息: %s\n",buf);
    }

    //5.关闭我呢见描述符
    close(fdr);
    close(fdw);
    
    return 0;
}
chatB.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

int main(){
    //1.判断有名管道是否存在
    int ret = access("fifo1",F_OK);
    if(ret == -1){
        printf("管道不存在,创建对应有名管道fifo1\n");
        ret = mkfifo("fifo1",0664);
        if(ret == -1){
            perror("mkfifo");
            exit(0);
        }
    }

    ret = access("fifo2",F_OK);
    if(ret == -1){
        printf("管道不存在,创建对应有名管道fifo2\n");
        ret = mkfifo("fifo2",0664);
        if(ret == -1){
            perror("mkfifo");
            exit(0);
        }
    }

    //2.以只读的方式打开fifo1
    int fdr = open("fifo1",O_RDONLY);
    if(fdr == -1){
        perror("open");
        exit(0);
    }
    printf("打开fifo1成功,等待读取...\n");

    //3.以只写的方式打开fifo2
    int fdw = open("fifo2",O_WRONLY);
    if(fdw == -1){
        perror("open");
        exit(0);
    }
    printf("打开fifo2成功,等待写入...\n");

    char buf[128];
    //4.循环读写数据
    while(1){
        //读数据
        memset(buf,0,128);
        ret = read(fdr,buf,128);
        if(ret <= 0){
            //ret=0,读完;ret=-1,调用失败
            perror("read");
            break;
        }
        printf("读到消息: %s\n",buf);

        //清空数据
        memset(buf,0,128);
        printf("请输入:");
        //获取标准输入的数据
        fgets(buf,128,stdin);
        //写数据
        ret = write(fdw,buf,strlen(buf));
        if(ret == -1){
            perror("write");
            exit(0);
        }
    }

    //5.关闭我呢见描述符
    close(fdr);
    close(fdw);
    
    return 0;
}

(3)示例2:升级版(读写使用不同的进程,可连续发送多条消息)

结果

 chatA.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

//fifo1  a写b读
//fifo2  a读b写

int main(){
    //1.判断有名管道是否存在
    int ret = access("fifo1",F_OK);
    if(ret == -1){
        printf("管道不存在,创建对应有名管道fifo1\n");
        ret = mkfifo("fifo1",0664);
        if(ret == -1){
            perror("mkfifo");
            exit(0);
        }
    }

    ret = access("fifo2",F_OK);
    if(ret == -1){
        printf("管道不存在,创建对应有名管道fifo2\n");
        ret = mkfifo("fifo2",0664);
        if(ret == -1){
            perror("mkfifo");
            exit(0);
        }
    }

    //创建子进程
    //父进程写,子进程读
    pid_t pid = fork();

    if(pid>0){
        //printf("chatA父进程,只写\n");
        //父进程
        //以只写的方式打开fifo1
        int fdw = open("fifo1",O_WRONLY);
        if(fdw == -1){
            perror("open");
            exit(0);
        }
        printf("chatA打开fifo1成功,等待写入...\n");

        //循环写数据
        char buf[128];
        while(1){
            //清空数据
            memset(buf,0,128);
            //printf("请输入:");

            //获取标准输入的数据
            //=========fgets()读取一行字符串;当写完一行消息,按下回车后,fgets()执行结束,执行write();所以按下回车后输入的消息需要等待下一次循环读取=========
            //使用多进程后,chatB不断读取,所以chatA的写入不会阻塞
            fgets(buf,128,stdin);

            //写数据
            ret = write(fdw,buf,strlen(buf));
            if(ret == -1){
                perror("write");
                exit(0);
            }
        }

        close(fdw);
    }
    else if(pid == 0){
        //printf("chatA子进程,只读\n");
        //子进程
        //以只读的方式打开fifo2
        int fdr = open("fifo2",O_RDONLY);
        if(fdr == -1){
            perror("open");

            exit(0);
        }
        printf("chatA打开fifo2成功,等待读取...\n");

        char buf[128];
        while(1){
            //读数据
            memset(buf,0,128);
            ret = read(fdr,buf,128);
            if(ret <= 0){
                //ret=0,读完;ret=-1,调用失败
                perror("read");
                break;
            }
            printf("                    读到消息: %s",buf);
        }

        close(fdr);
    }
    
    return 0;
}
chatB.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

int main(){
    //1.判断有名管道是否存在
    int ret = access("fifo1",F_OK);
    if(ret == -1){
        printf("管道不存在,创建对应有名管道fifo1\n");
        ret = mkfifo("fifo1",0664);
        if(ret == -1){
            perror("mkfifo");
            exit(0);
        }
    }

    ret = access("fifo2",F_OK);
    if(ret == -1){
        printf("管道不存在,创建对应有名管道fifo2\n");
        ret = mkfifo("fifo2",0664);
        if(ret == -1){
            perror("mkfifo");
            exit(0);
        }
    }


    //创建子进程
    //父进程读,子进程写
    pid_t pid = fork();

    if(pid > 0){
        //printf("chatB父进程,只读\n");
        //父进程
        //以只读的方式打开fifo1
        int fdr = open("fifo1",O_RDONLY);
        if(fdr == -1){
            perror("open");
            exit(0);
        }
        printf("chatB打开fifo1成功,等待读取...\n");

        char buf[128];
        while(1){
            //读数据
            memset(buf,0,128);
            ret = read(fdr,buf,128);
            if(ret <= 0){
                //ret=0,读完;ret=-1,调用失败
                perror("read");
                break;
            }
            printf("                    读到消息: %s",buf);
        }

        close(fdr);
    }
    else if(pid==0){
        //printf("chatB子进程,只写\n");
        //子进程
        //以只写的方式打开fifo2
        int fdw = open("fifo2",O_WRONLY);
        if(fdw == -1){
            perror("open");
            exit(0);
        }
        printf("chatB打开fifo2成功,等待写入...\n");

        //循环写数据
        char buf[128];
        while(1){
            //清空数据
            memset(buf,0,128);
            //printf("请输入:");
            //获取标准输入的数据
            fgets(buf,128,stdin);
            //写数据
            ret = write(fdw,buf,strlen(buf));
            if(ret == -1){
                perror("write");
                exit(0);
            }
        }

        close(fdw);
    }
    
    return 0;
}
 注意

        以只写方式打开文件会被阻塞,直到有读权限的进程打开文件。

        以只读方式打开文件不会被阻塞。

11.内存映射 

(1)概念

 (2)相关函数

 #include <sys/mman.h>

  void *mmp(void *addr, size_t length, int prot, int flags,int fd, off_t offset);

        - 功能:将一个文件或者设备的数据映射到内存

        - 参数:

            - void *addr: NULL, 由内核指定

            - length : 要映射的数据的长度,这个值不能为0。建议使用文件的长度。

                        如果length<内存页的大小,系统自动分配一个页面的长度.

                    获取文件的长度:stat 或 lseek

            - prot : 对申请的内存映射区的操作权限

                -PROT_EXEC :可执行的权限

                -PROT_READ :读权限

                -PROT_WRITE :写权限

                -PROT_NONE :没有权限

                要操作映射内存,必须要有读的权限。

                PROT_READ、PROT_READ|PROT_WRITE

            - flags :

                - MAP_SHARED : 映射区的数据会自动和磁盘文件进行同步,要想实现进程间通信,必须要设置这个选项

                - MAP_PRIVATE :不同步,内存映射区的数据改变了,对原来的文件不会修改,会重新创建一个新的文件。(copy on write)

            - fd: 需要映射的那个文件的文件描述符

                - 通过open得到,open的是一个磁盘文件

                - 注意:文件的大小不能为0open指定的权限不能和prot参数有冲突。(prot的权限要比open小)

                    prot: PROT_READ                open:只读/读写

                    prot: PROT_READ | PROT_WRITE   open:读写

            - offset:偏移量,一般不用。必须指定的是4k的整数倍,0表示不偏移。

        - 返回值:成功返回创建的内存的首地址

                失败返回MAP_FAILED( (void *) -1 )

    int munmap(void *addr, size_t length);

        - 功能:释放内存映射

        - 参数:

            - addr : 要释放的内存的首地址

            - length : 要释放的内存的大小,要和mmap函数中的length参数的值一样。

 (3)使用内存映射实现进程间通信

1.有关系的进程(父子进程)

        - 还没有子进程的时候

            - 通过唯一的父进程,先创建内存映射区

        - 有了内存映射区以后,创建子进程

        - 父子进程共享创建的内存映射区

   

    2.没有关系的进程间通信

        - 准备一个大小不是0的磁盘文件,进程分别对磁盘文件建立内存映射

        - 进程1 通过磁盘文件创建内存映射区

            - 得到一个操作这块内存的指针

        - 进程2 通过磁盘文件创建内存映射区

            - 得到一个操作这块内存的指针

        - 使用内存映射区通信

    注意:内存映射区通信,是非阻塞。

 示例:父子进程通信

#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <wait.h>

int main(){
    //1.打开文件
    int fd = open("test.txt",O_RDWR);
    int size = lseek(fd,0,SEEK_END);    //获取文件大小

    //2.创建内存映射区
    void * ptr = mmap(NULL,size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if(ptr == MAP_FAILED){
        perror("mmap");
        exit(0);
    }

    //3.创建子进程
    pid_t pid = fork();

    if(pid>0){
        char buf[128];
        strcpy(buf,(char *)ptr);
        printf("read data: %s\n",buf);
    }else if(pid == 0){
        strcpy((char *)ptr,"ni hao a!!!!");
    }

    //4.关闭内存映射区
    munmap(ptr,size);

    close(fd);
    
    return 0;
}

(4)内存映射注意事项

1.如果对mmap的返回值(ptr)做++操作(ptr++), munmap是否能够成功?

        void * ptr = mmap(...);
        ptr++;                      //可以对其进行++操作
        munmap(ptr, len);   // 但是,释放的地址不对,会出错

2.如果open时O_RDONLY, mmap时prot参数指定PROT_READ | PROT_WRITE会怎样?
        错误,返回MAP_FAILED
        open()函数中的权限建议和prot参数的权限保持一致。

3.如果文件偏移量为1000会怎样?
        偏移量必须是4K的整数倍;返回MAP_FAILED

4.mmap什么情况下会调用失败?
            第二个参数:length = 0
            - 第三个参数:prot
                - 只指定了写权限
                - prot 权限大于 fd

5.可以open的时候O_CREAT一个新文件来创建映射区吗?
    - 可以的,但是创建的文件的大小如果为0的话,肯定不行
    - 可以对新的文件进行扩展
        -off_t lseek(int fd,off_t offset,int whence);   //lseek(fd,100,SEEK_END);
        -int truncate(const char * path,off_t len);    //拓展文件至指定大小

6.mmap后关闭文件描述符,对mmap映射有没有影响?
            int fd = open("XXX");
            mmap(,,,,fd,0);
            close(fd); 
            映射区还存在,创建映射区的fd被关闭,没有任何影响

7.对ptr越界操作会怎样?
        void * ptr = mmap(NULL, 100,,,,,);
        内存页大小可能是4K
        越界操作操作的是非法的内存 -> 段错误

(5)使用内存映射实现文件拷贝

思路:

        1.对原始文件进行内存映射

        2.创建新文件,拓展大小

        3.对新文件进行内存映射

        4.内存拷贝

        5.释放资源

#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(){
    //1.对原始文件进行内存映射
    int fd = open("english.txt",O_RDWR);
    if(fd == -1){
        perror("open");
        exit(0);
    }

    //获取大小
    int len = lseek(fd,0,SEEK_END);
    //内存映射
    void * ptr = mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if(ptr == MAP_FAILED){
        perror("mmap");
        exit(0);
    }

    // 2.创建新文件,拓展大小
    int fd1 = open("cpy.txt",O_RDWR | O_CREAT,0664);
    if(fd == -1){
        perror("open");
        exit(0);
    }

    //拓展新文件
    truncate("cpy.txt",len);
    // lseek(fd,len,SEEK_END);
    write(fd1," ",1);   //写入,使拓展生效
  
    // 3.对新文件进行内存映射
    void * ptr1 = mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED,fd1,0);
    if(ptr1 == MAP_FAILED){
        perror("mmap");
        exit(0);
    }

    // 4.内存拷贝
    memcpy(ptr1,ptr,len);

    // 5.释放资源,释放顺序与申请顺序相反
    munmap(ptr1,len);
    munmap(ptr,len);

    return 0;
}

 (6)匿名映射

匿名映射:不需要文件实体的一个内存映射

    flag = MAP_SHARED | MAP_ANONYMOUS

    fd = -1

int len = 4096;

    void * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);

2023.7.20 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值