Linux C++服务器项目——多进程

牛客 C++高并发服务器开发
参考笔记

1 基本概念

1.1 基本概念程序

程序是包含一系列信息的文件,这些信息描述了如何在运行时创建一个进程:

  • 二进制格式标识:每个程序文件都包含用于描述可执行文件格式的元信息。内核利用此信息来解释文件中的其他信息。(ELF可执行连接格式)
  • 机器语言指令:对程序算法进行编码。
  • 程序入口地址:标识程序开始执行时的起始指令位置。
  • 数据:程序文件包含的变量初始值和程序使用的字面量值(比如字符串)
  • 符号表及重定位表:描述程序中函数和变量的位置及名称。这些表格有多重用途,其中包括调试和运行时的符号解析(动态链接)。
  • 共享库和动态链接信息∶程序文件所包含的一些字段,列出了程序运行时需要使用的共享库,以及加载共享库的动态连接器的路径名。
  • 其他信息:程序文件还包含许多其他信息,用以描述如何创建进程。

1.2 进程

  • 进程是正在运行的程序的实例。是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。
  • 它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
  • 可以用一个程序来创建多个进程,进程是由内核定义的抽象实体,并为该实体分配用以执行程序的各项系统资源。
  • 从内核的角度看,进程由用户内存空间和一系列内核数据结码组,具中用厂内什工应自日手许多与进提相兰所使用的变量,而内核数据结构则用于维护进柱认忿后态e-ut水1Lr小X9的右兰信自讲程资源使用及限制、的标识号(IDs)、虚拟内存表、打开文件的描述符表、信号传递及处理的有关信息、进程资源使用及限制、当前工作目录和大量的其他信息。
  • 对于一个单CPU系统来说,程序同时处于运行状态只是一种宏观上的概念,他们虽然都已经开始运行,但就微观而言,任意时刻,CPU上运行的程序只有一个。
  • 在多道程序设计模型中,多个进程轮流使用CPU。而当下常见CPU为纳秒级1秒可以执行大约10亿条指令。由于人眼的反应速度是毫秒级,所以看似同时在运行。

1.3 单道多道程序设计

  • 单道程序,即在计算机内存中只允许一个的程序运行。
  • 多道程序设计技术是在计算机内存中同时存放几道相互独立的程序,使它们在管理程序控制下,相互穿插运行,两个或两个以上程序在计算机系统中I问了力T到个l多道程序设计技术的根本目的是为了提高CPU的利用率。
  • 对于一个单CPU系统来说,程序同时处于运行状态只是一种宏观上的概念,他们虽然都已经开始运行,但微观而言,任意时刻,CPU上运行的程序只有一个。
  • 在多道程序设计模型中,多个进程轮流使用CPU。而当下常见CPU为纳秒级1秒可以执行大约10亿条指令。由于人眼的反应速度是毫秒级,所以看似同时在运行。

1.4 并行和并发

在这里插入图片描述
并发:比如10000个用户同时访问,某网站,服务器;
淘宝双十一,高并发做的非常好。
据说12306,高并发是阿里开发的;

并发是两个队列交替使用一台咖啡机。
并行是两个队列同时使用两台咖啡机。

在这里插入图片描述

1.5 进程控制块(PCB)

为了管理进程,内核必须对每个进程所做的事情进行清楚的描述。内核为每个进程分配一个PCB(Processing Control Block)进程控制块,维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。

在/usrlsrc/linux-headers-xxx/include/linux/sched.h文件中可以查看struct tas
k_struct结构体定义。其内部成员有很多,我们只需要掌握以下部分即可:

  • 进程id:系统中每个进程有唯一的id,用pid_t类型表示,其实就是一个非负整数
  • 进程的状态:有就绪、运行、挂起、停止等状态
  • 进程切换时需要保存和恢复的一些CPU寄存器
  • 描述虚拟地址家闯的信息
  • 描述控制终端的信息
  • 当前工作目录(Current Working Directory)
  • umask掩码
  • 文件描述符表,包含很多指向file结构体的指针
  • 和信号相关的信息
  • 用户id和组id
  • 会话(Session)和进程组
  • 进程可以使用的资源上限(Resource Limit)
luck@luck:~$ umask
0002
ulimit -a

在这里插入图片描述

2.进程的状态

进程状态反映进程执行过程的变化。这些状态随着进程的执行和外界条件的变化而转换。在三态模型中,进程状态分为三个基本状态,即就绪态,运行态,阻塞态。在五态模型中,进程分为新建态、就绪态,运行态,阻塞态,终止态。

  • 运行态:进程占有处理器正在运行;
  • 就绪态:进程具备运行条件,等待系统分配处理器以便运行。当进程已分配到除CPU以外的所有必要资源后,只要再获得CPU,便可立即执行。在一个系统中处于就绪状态的进程可能有多个,通常将它们排成一个队列,称为就绪队列;
  • 阻塞态:又称为等待(wait)态或睡眠(sleep)态,指进程不具备运行条件,正在等待某个事件的完成;
    在这里插入图片描述
    在这里插入图片描述
  • 新建态:进程刚被创建时的状态,尚未进入就绪队列
  • 终止态:进程完成任务到达正常结束点,或出现无法克服的错误而异常终止,或被操作系统及有终止伙的进样所终止时所处的状态。进入终止态的进程以后不再执行,但依然保留在操作系统中等待善后。一旦其他进程完成了对终止态进程的信息抽取之后,操作系统将删除该进程。

3 进程相关指令

查看进程
在这里插入图片描述
实时显示进程动态

top

可以在使用top命令时加上-d来指定显示信息更新的时间间隔,在top命令执行后,可以按以下按键对显示的结果进行排序:

  • M根据内存使用量排序
  • Р根据CPU占有率排序
  • T根据进程运行时间长短排序
  • U根据用户名来筛选进程
  • K输入指定的PID杀死进程

杀死进程(kill命令并不是去杀死一个进程,而是给进程发送某个信号)

kill [-signal] pid
kill -1列出所有信号
kill -SIGKILL 进程ID
kill -9 进程ID
killall name 根据进程名杀死进程

4.进程号相关函数

每个进程都由进程号来标识,其类型为pid_t(整型),进程号的范围:0~32767。进程号总是唯一的,但可以重用。当一个进程终止后,其进程号就可以再次使用。

任何进程(除init进程)都是由另一个进程创建,该进程称为被创建进程的父进程,对应的进程号称为父进程号(PPID)。

进程组是一个或多个进程的集合。他们之间相互关联,进程组可以接收同一终端的各种信号,关联的进程有一个进程组号(PGID)。默认情况下,当前的进程号会当做当前的进程组号。

进程号和进程组相关函数:

pid_t getpid(void);
pid_t getppid(void) ;
pid_t getpgid(pid_t pid);

5 进程创建

终端输入命令查看 第二章,fork()函数用法

man 2 fork

在这里插入图片描述

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

int main(){
    int num = 10;

    //创建子进程
    pid_t pid = fork();//fork - create a child process

    //判断是父进程还是子进程, pid > 0父进程, pid = 0 子进程
    if(pid > 0){
        //printf("pid : %d\n", pid);
        //如果大于0,返回的是创建的子进程的进程号,当前是父进程
        printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());

        printf("parent num : %d\n", num);
        num += 10;
        printf("parent num += 10 : %d\n",num);
    }

    else if(pid == 0){
        //当前子进程
        printf("i am child process, pid : %d, ppid : %d\n", getpid(), getppid());
        printf("chilid num : %d\n", num);
        num += 10;
        printf("child num += 10 : %d\n",num);

    }

    //for循环父进程和子进程都会去执行
    //交替抢占CPU资源
    for(int i = 0; i < 3; i++){
        printf("i : %d , pid : %d\n",i ,getpid());
        sleep(1);
    }

    return 0;
}

/*
写时拷贝,读时共享;

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

*/

//for循环父进程和子进程都会去执行,
//交替抢占CPU资源
在这里插入图片描述
父子进程虚拟地址空间
在这里插入图片描述
实际上,更准确来说,Linux的 fork()使用是通过写时拷贝(copy- on-write)实现。
写时拷贝是一种可以推迟甚至避免拷贝数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享

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

6 exeC函数族介绍 (面试很少问)

exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。

exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息仍保持原样,颇有些神似"三十六计"中的"金蝉脱壳"。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回-1,从原程序的调用点接着往下执行。
在这里插入图片描述
在这里插入图片描述

7 进程控制进程退出

7.1 退出进程

#include <stdlib.h> 
void exit(int status);
#include <unistd.h>
void _exit(int status) ;

在这里插入图片描述

/*
#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status) ;

status参数:是进程退出时的一个状态信息。父进程回收子进程资源的时候可以获取到。
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
    printf("hello\n");
    printf("world");

    //exit(0);//退出时,会刷新缓冲区,world会被打印出来
    _exit(0); //退出时,不会刷新缓冲区,world不会被打印出来

    return 0;
 }

_exit(0); //退出时,不会刷新缓冲区,world不会被打印出来

在这里插入图片描述

int main()
{
    printf("hello\n");
    printf("world");

    exit(0);//退出时,会刷新缓冲区,world会被打印出来
    //_exit(0); //退出时,不会刷新缓冲区,world不会被打印出来

    return 0;
 }

exit(0);//退出时,会刷新缓冲区,world会被打印出来
在这里插入图片描述

7.2 孤儿进程 (会被回收,没危害)

父进程运行结束,但子进程还在运行(未运行结束),这样的子进程就称为孤儿进程(Orphan Process) .

每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为init,而 init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。

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

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

    //判断是父进程还是子进程
    if(pid > 0){
        printf("i am parent process,pid : %d,ppid : %d\n", getpid(), getppid());
    }

    else if(pid == 0) {
        sleep(20); //休眠20秒,当父进程执行完毕,结束了;;;;等待20秒后,又执行了子进程
        //当前是子进程
        printf("i am child process, pid : %d,ppid : %d\n", getpid(),getppid());
    }
    // for循环
    for(int i = 0; i< 3; i++){
        printf("i: %d , pid : %d\n", i , getpid());
    }

    return 0;
}

休眠20秒,当父进程执行完毕,结束了;;;;等待20秒后,又执行了子进程
在这里插入图片描述

7.3 僵尸进程(有危害,应当避免)

每个进程结束之后,都会释放自己地址空间中的用户区数据,内核区的PCB没有办法自己释放掉,需要父进程去释放。

进程终止时,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵(Zombie)进程。

僵尸进程不能被kill -9杀死,这样就会导致一个问题,如果父进程不调用wait()或waitpid()的话,那么保留的那
段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进柱,将因为没有可用的进程号而导致系统不能产生新的进程,此即为僵尸进程的危害,应当避免;

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

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

    //判断是父进程还是子进程
    if(pid > 0){
        while(1){ //父进程,死循环,,,每隔一秒printf一次,如果不sleep,终端会被卡死
            printf("i am parent process,pid : %d,ppid : %d\n", getpid(), getppid());
            sleep(1);//每隔一秒printf一次,如果不sleep,终端会被卡死
        }   
    }

    else if(pid == 0) {
        //当前是子进程
        printf("i am child process, pid : %d,ppid : %d\n", getpid(),getppid());
    } //进程终止时,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵(Zombie)进程。
    
    // for循环
    for(int i = 0; i< 3; i++){
        printf("i: %d , pid : %d\n", i , getpid());
    }

    return 0;
}

父进程37059,子进程37060;
但是子进程只执行了一次
在这里插入图片描述

复制会话,在另一窗口查看进程情况

在这里插入图片描述

僵尸进程不能被kill -9杀死
ps ajx 查看进程,父进程号37059,子进程37060;
kill -9 37060后,ps ajx 查看进程,子进程37060还存在;
在这里插入图片描述
在这里插入图片描述

kill -9 37059杀死父进程后,子进程才被回收
此时窗口一显示,进程已被杀死;
在这里插入图片描述
在看窗口二,
ps ajx 父进程和子进程都没了

在这里插入图片描述

7.4 进程回收

在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。但是仍然为其保留一定的信息,这些信息主要主要指进程控制块PCB的信息(包括进程号、退出状态、运行时间等)。

父进程可以通过调用wait 或 waitpid 得到它的退出状态同时彻底清除掉这个进程。

wait()和waitpid()函数的功能一样,区别在于,wait()函数会阻塞,waitpid()可以设置不阻塞,waitpid()还可以指定等待哪个子进程结束。

注意:一次wait或waitpid 调用只能清理一个子进程,清理多个子进程应使用循环。

退出信息相关宏函数

WIFEXITED(status) 		非O,进程正常退出
WEXITSTATUS(status)		如果上宏为真,获取进程退出的状态(exit的参数)

WIFSIGNALED(status)0,进程异常终止
WTERMSIG(status)		如果上宏为真,获取使进程终止的信号编号


WIFSTOPPED(status)0,进程处于暂停状态
WSTOPSIG(status)		如果上宏为真,获取使进程暂停的信号的编号
WIFCONTINUED(status)0,进程暂停后已经继续运行
/*
#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 :某个进程组的组id的绝对值,回收指定进程组中的子进程
	- options:设置阻塞或者非阻塞
		0 :阻塞
		WNOHANG:非阻塞
	-返回值:
		>0 : 返回子进程的id
		=0 : options = WNOHANG,表示还有子进程或者
		= -1 :错误,或者没有子进程了
*/

8.进程间通信

8.1 进程间通讯概念

进程是一个独立的资源分配单元,不同进程(这里所说的进程通常指的是用户进程)之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源。

但是,进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信( IPC: InterProcesses Communication )。

进程间通信的目的:

  • 数据传输:一个进程需要将它的数据发送给另一个进程。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供互斥和同步机制。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

8.2 Linux进程间通信的方式

在这里插入图片描述

8.3 匿名管道(管道)

管道也叫无名(匿名)管道,它是是UNIX系统IPC(进程间通信)的最古老形式,所有的UNIX系统都支持这种通信机制。

统计一个目录中文件的数目命令: ls | wc -l,为了执行该命令,shell创建了两个进程来分别执行ls和wc。

在这里插入图片描述
在这里插入图片描述

1 管道的特点

  • 管道其实是一个在内核内存中维护的缓冲器,这个缓冲器的存储能力是有限的不同的操作系统大小不一定相
    同。
  • 管道拥有文件的特质:读操作、写操作,匿名管道没有文件实体,有名管道有文件实体,但不存储数据。可以按照操作文件的方式对管道进行操作。
  • 一个管道是一个字节流,使用管道时不存在消息或者消息边界的概念,从管道读取数据的进程可以读取任意大小的数据块,而不管写入进程写入管道的数据块的大小是多少。
  • 通过管道传递的数据是顺序的,从管道中读取出来的字节的顺序和它们被写入管道的顺序是完全一样的。
  • 在管道中的数据的传递方向是单向的,一端用于写入,一端用于读取,管道是半双工的。
  • 从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据,在管道中无法使用lseek()来随机的访问数据。
  • 匿名管道只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用。
    在这里插入图片描述

匿名管道的使用

创建匿名管道

#include<unistd.h>
int pipe(int pipefd[2]);

查看管道缓冲大小命令

ulimit -a

在这里插入图片描述

查看管道缓冲大小函数

#include <unistd.h>
long fpathconf(int fd,int name) ;

在这里插入图片描述
父进程子进程,一个写,一个读;
所以,父进程、子进程是配套使用的,(写的时候就关闭读;读的时候,就关闭写);
在这里插入图片描述

在这里插入图片描述

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

int main(){
    pid_t pid;

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

    //创建子进程
     if(pid > 0){
       //父进程
        printf("i am parent process, pid : %d\n", getpid());

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

       //从管道的读取端读取数据
       char buf[1024] = {0};
       while(1){
            int len = read(pipefd[0],buf,sizeof(buf));
            printf("parent recv : %s, pid : %d\n",buf, getpid());

            //向管道中写入数据
            //char* str = "hello, i am parent";
            //write(pipefd[1], str, strlen(str));
            //sleep(1);
       }
    }

    else if(pid == 0){
        //当前子进程
        printf("i am child process, pid : %d\n", getpid());

        //关闭读端
        close(pipefd[0]);
        char buf[1024] = {0};
        while(1){
            //向管道中写入数据
            char* str = "helloo, i am chlid";
            write(pipefd[1],str,strlen(str));
            sleep(1);

            //从管道中读数据
            int len = read(pipefd[0],buf,sizeof(buf));
            printf("child recv : %s, pid : %d\n",buf, getpid());
            bzero(buf,1024);
        }
    }
    return 0;
}

8.4 有名管道(命名管道)

介绍
1.匿名管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道(FIFO),也叫命名管道、FIFO文件。FIFO(First Input First Output),即先进先出队列。

2.有名管道(FIFO)不同于匿名管道之处在于它提供了一个路径名与之关联,以FIFO的又件形式仔仕于乂什系统中,并且其打开方式与打开一个普通文件是一样的,这样即使与FIFO的创建进程不存在亲缘关系的进程,
只要可以访问该路径,就能够彼此通过FIFO相互通信,因此,通过FIFO 不相关的进程也能交换数据。

3.一旦打开了FIFO,就能在它上面使用与操作匿名管道和其他文件的系统调用一样的I/O系统调用了(如read()、write()和close()。与管道一样,FIFO也有一个写入端和读取端,并且从管道中读取数据的顺序与与入的顺序是一样的。FIFO的名称也由此而来:先入先出。

4.有名管道(FIFO)和匿名管道(pipe)有一些特点是相同的,不一样的地方在于:
。FIFO 在文件系统中作为一个特殊文件存在,但 FIFO中的内容却存放在内存中。
。当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用。
。FIFO有名字,不相关的进程可以通过打开有名管道进行通信。

通过命名创建有名管道

mkfifo 名字

通过函数创建有名管道

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname,mode_t mode);

一旦使用mikffo创建了一个FIFO,就可以使用open打开它,常见的文件I/O凶数都可用FIFO。如close,read、write、unlink 等。

FIFO严格遵循先进先出(First in First out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。

8.5 管道的读写特点

使用管道时,需要注意以下几种特殊的情况(假设都是阻塞I/0操作)
1.所有的指向管道写端的文件描述符都关闭了(管道写端引用计数为0),有进程从管道的读端读数据,那么管道中剩余的数据被读取以后,再次read会返回0,就像读到文件末尾一样。

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

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

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

总结:

读管道:
	管道中有数据,read返回实际读到的字节数。
	管道中无数据:
		写端被全部关闭,read返回0(相当于读到文件的末尾)
		写端没有完全关闭,read阻塞等待

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

8.6 内存映射

内存映射(Memory-mapped l/O)是将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。
在这里插入图片描述

#include <sys/mman.h>
void *mmap(void *addr,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void *addr,size_t length);

8.7 共享内存

共享内存允许两个或者多个进程共享物理内存的同一块区域(通常被称为段)。由于一个共享内存段会称为一个进程用户空间的一部分,因此这种IPC机制无需内核介入。所有需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他所有共享同一个段的进程可用。

与管道等要求发送进程将数据从用户空间的缓冲区复制进内核内存和接收进程将数据从内核内存复制进用户空间的缓冲区的做法相比,这种IPC技术的速度更快。 IPC(Inter-Process Communication,进程间通信)。

使用步骤

  • 调用shmget()创建一个新共享内存段或取得一个既有共享内存段的标识符(即由其他进程创建的共享内存段)。这个调用将返回后续调用中需要用到的共享内存标识符。

  • 使用shmat()来附上共享内存段,即使该段成为调用进程的虚拟内存的一部分。

*此刻在程序中可以像对待其他可用内存那样对待这个共享内存段。为引用这块共享内存,程序需要使用由shmat()

  • 调用返回的addr值,它是一个指向进程的虚拟地址空间中该共享内存段的起点的指针。

  • 调用shmdt()来分离共享内存段。在这个调用之后,进程就无法再引用这块共享内存了。这一步是可选的,并且在进程终止时会自动完成这一步。

  • 调用shmctl()来删除共享内存段。只有当当前所有附加内存段的进程都与之分离之后内存段才会销毁。只有一个进程需要执行这一步。

相关函数

int shmget(key_t key,size_t size, int shmf1g);
void *shmat(int shmid,const void *shmaddr,int shmf1g);
int shmdt(const void *shmaddr);
int shmct1(int shmid, int cmd,struct shmid_ds *buf);
key_t ftok(const char *pathname,int proj_id);

共享内存操作命令
ipcs用法

ipcs -a 	//打印当前系统中所有的进程间通信方式的信息
ipcs -m		//打印出使用共享内存进行进程间通信的信息
ipcs -q		//打印出使用消息队列进行进程间通信的信息
ipcs -s 	//打印出使用信号进行进程间通信的信息
  • ipcrm用法
ipcrm -M shmkey		//移除用shmkey创建的共享内存段
ipcrm -m shmid		//移除用shmid标识的共享内存段
ipcrm -Q msgkey		//移除用msqkey创建的消息队列
ipcrm -q msqid		//移除用msqid标识的消息队列
ipcrm -S semkey	  	//移除用semkey创建的信号
ipcrm -s semid		//移除用semid标识的信号

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

R-G-B

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值