温故Linux后端编程(二):进程·全家桶

举报
看,未来 发表于 2021/02/05 00:42:10 2021/02/05
【摘要】 文章目录 前言进程概念问答录什么是进程进程为何而生程序与进程的区别与联系进程的三种基本状态进程状态间的装换进程的挂起进程控制块(PCB) 进程调度算法非剥夺方式剥夺方式 先进先出(FIFO)最短处理机运行期优先调度算法最高响应比优先调度算法优先级调度算法动态优先级时间片轮转调度算法前后台调度算法多级反馈队列轮转算法进程依次执行时可能发生的三种情况进程调度的时...

在这里插入图片描述

前言

不知不觉,就到大三了。
不知不觉,就要开始找暑期实习了。
温故而知新嘛。(数据结构复习两天发现不对,我还是更喜欢这个。)
所以就来了。


进程概念问答录

Linux的进程操作方式主要有:产生进程、终止进程、进程间通信。
以下内容为简述。

什么是进程

一段程序的执行过程。

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

一堆官方话,就·一段程序的执行过程。

进程为何而生

为了使程序在多道程序环境下能够并发执行,并对并发执行的程序加以控制和描述,引入进程的概念。
程序段、数据段及进程控制块三部分构成了一个进程的实体。

程序与进程的区别与联系

(1)进程是程序的一次执行,是一个动态的概念,程序是完成某个特定功能的指令的有序序列,是一个静态的概念
(2)一个进程可以执行一个或几个程序,同一程序也可能由多个进程同时执行
(3)进程是系统进行资源分配和调度的一个独立单位,程序则不是
(4)程序可以作为一种软件资源长期保存,而进程是程序的一次执行过程,它是临时的,有生命期的
进程是具有结构的

总结一句话:进程是程序的运行

进程的三种基本状态

  1. 就绪状态
    当进程已分配到除CPU以外的所有必要的资源后,只要能再获得处理机便可立即执行,这时的状态称为就绪状态

  2. 执行状态
    指进程已获得处理机,其程序正在执行

  3. 阻塞状态
    进程因发生某种事件(如I/O请求、申请缓冲空间等)而暂停执行时的状态,亦即进程的执行受到阻塞,故称这种状态为阻塞状态,有时也称为“等待”状态或“睡眠”状态。

进程状态间的装换

在这里插入图片描述

进程的挂起

在进程中,CTRL+C。

终端用户的需要
当终端用户在自己的程序运行期间发现有可疑问题时,往往希望暂时使自己的进程静止下来。

父进程的需要
父进程常常希望考察和修改子进程或者当要协调各子进程间的活动

操作系统的需要
操作系统有时需要挂起某些进程,检查运行中资源的使用情况及进行记账,以便改善系统运行的性能。

对换的需要
为了缓解内存紧张的情况,即将内存中处于阻塞状态的进程换至辅存上,使进程又处于一种有别于阻塞状态的新状态。

负荷调节的需要

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述

进程控制块(PCB)

-进程控制块记录进程信息
-操作系统是根据进程控制块PCB来对并发执行的进程进行控制和管理的 
-PCB是进程存在的唯一标志

  
 
  • 1
  • 2
  • 3

-进程标识符信息
进程标识符用于唯一地标识一个进程,通常有外部标识符和内部标识符
①外部标识符。由创建者提供,通常由字母、数字所组成,往往是由用户(进程)在访问该进程时使用。
②内部标识符。这是为了方便系统使用而设置的。在所有操作系统中都为每一个进程赋予一个惟一的整数作为内部标识符,它通常就是一个进程的序号。


进程调度算法

进程调度就是系统按照某种算法把CPU动态地分配给某一就绪进程。进程调度工作是通过进程调度程序来完成的。
进程调度程序的主要功能:
-选择进程占有CPU
-进行进程上下文的切换

非剥夺方式

分派程序一旦把处理机分配给某进程后便让它一直运行下去,直到进程完成或发生某事件(如提出I/O请求)而阻塞时才把处理机分配给另一进程
-优点:简单,系统开销小,
-缺点:貌似公正,可能导致系统性能的恶化

剥夺方式

该方式规定,当一个进程正在运行时,系统可以基于某种原则,剥夺已分配给它的处理机,将之分配给其它进程
剥夺原则:优先权原则、短进程优先原则、时间片原则

先进先出(FIFO)

	-算法:把处理机分配给最先进入就绪队列的进程 -优点:易于实现 -缺点:表面上公平,服务质量不佳 、对短进程不利

  
 
  • 1
  • 2
  • 3

最短处理机运行期优先调度算法

-算法:从就绪队列中选出“下一个CPU执行期”最短的进程,为之分配处理机使之执行
-优点:可获得较好的调度性能
-缺点: 进程的CPU执行期难以准确得到、对长进程不利

最高响应比优先调度算法

   -算法:响应比=(等待时间+要求的服务时间)/要求的服务时间 ,每次选取响应比最高的进程调度 -优点:所以对短进程有利,并且考虑了等待时间 -缺点:计算响应比有一定的系统开销

  
 
  • 1
  • 2
  • 3

优先级调度算法

-算法:将CPU分配给就绪队列中优先级最高的进程
-静态优先级
在进程创建时确立,确定后运行期间保持不变。确立依据有:进程的类型、进程对资源的需求、用户申请的优先级
优点:简单
缺点:不能动态反映进程特点,系统调度性能差

动态优先级

进程在开始创建时,根据某种原则确定一个优先级后,随着进程执行时间的变化,其优先级不断地进行动态调整 
确定依据:根据进程占有的CPU时间的长短来决定,占有时间越长优先级越低;根据进程等待CPU的时间来决定,时间越长优先级越高

  
 
  • 1
  • 2

时间片轮转调度算法

算法:通常用在分时系统,它轮流地调度系统中所有就绪进程,使就绪进程依次获得一个时间片的运行时间

  • 时间片长短确定遵循原则
    既要保证系统各个用户进程及时地得到响应,又不要由于时间片太短而增加调度的开销,降低系统的效率

前后台调度算法

-算法:该方法用在批处理和分时相结合的系统中。将分时用户作业放在前台,把批处理作业放在后台。系统对前台作业按照时间片轮转法进行调度,仅当前台无作业时,才把处理机分配给后台作业的进程。后台进程通常按先来先服务方式运行
-优点:使分时用户进程得到及时响应,又提高了系统资源的利用率

多级反馈队列轮转算法

-系统设置多个不同优先级的就绪队列,每次调度总是先调度优先级高的队列,仅当该队列空时,才调度次高优先级队列。
-通常刚创建的进程和因请求I/O未用完时间片的进程排在最高优先级队列,在这个队列中运行2-3个时间片未完成的进程排列下一个较低优先级队列。
-不论什么时候,只要较高优先级队列有进程进入,立即转进程调度,及时调度较高优先级队列进程。
-优点:能较好地满足各类作业的用户要求,既能使分时用户作业得到满意的响应,又能使批处理用户的作业获得较合理的周转时间

进程依次执行时可能发生的三种情况

   -进程未用完一个时间片便结束,这时系统应提前进行调度 -进程在执行过程中提出I/O请求而阻塞,系统应将它放入相应的阻塞队列并引起调度 -进程用完一个时间片后尚未完成。系统应将它重新放到就绪队列的末尾,等待下次执行 

  
 
  • 1
  • 2
  • 3

进程调度的时机和过程

进程调度的时机

   -正在执行的进程运行完毕 -正在执行的进程调用阻塞原语将自己阻塞起来进入等待状态 -在采用抢占式优先级调度时,有优先级高于正在运行进程的进程进入就绪队列 -在分时系统中时间片已经用完 -CPU方式是可剥夺时,就绪队列中的某个进程 优先级变得高于当前运行进程的优先级 

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

进程调度的过程

-进程调度所依赖的数据结构通常是调度队列,由于调度的原因不同,在单处理器系统中设置了多种等待队列
-只有就绪队列中的进程能够获得处理器而最终运行,其他队列中的进程从队列中出来后,必须进入就绪队列才能分配处理器
-队列数据结构的建立结构与调度算法密切相关
-进程调度算法只是决定哪一个进程将获得处理机,而将处理机分配给该进程的具体操作是由分派程序完成的


进程原语

fork

#include <unistd.h>

pid_t fork(void);

  
 
  • 1
  • 2
  • 3

功能:子进程复制父进程中的0~3g空间和PCB,但ID号不同。

fork调用一次返回两次
父进程中返回子进程id (就是大于0的意思)
子进程返回0
读时共享写时复制,可保高效

与之相关函数:

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

pid_t getpid(void); //获取进程ID
pid_t getppid(void); //获取父进程ID

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

进程的产生方式:

进程的产生有多种方式,但是追本溯源是相通的。

(1)复制父进程的系统环境(放心,只要是你开的进程,肯定有父进程)
(2)在内核中建立进程结构
(3)将结构插入到进程列表,便于维护
(4)分配资源给该进程
(5)复制父进程的内存映射消息
(6)管理文件描述符和链接点
(7)通知父进程

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

下面是一张进程列表的图,命令:pstree。
在这里插入图片描述

可以看到init是所有进程的父进程,其他进程都是由init进程直接或间接fork出来的。

exec族

fork子进程是为了执行新程序(fork创建了子进程后,子进程和父进程同时被OS调度执行,因此子进程可以单独的执行一个程序,这个程序宏观上将会和父进程程序同时进行)

使用exec族函数运行新的可执行程序。exec族函数可以直接把一个编译好的可执行程序直接加载运行。

有了exec族函数后,典型的父子进程程序是这样的:子进程需要运行的程序被单独编写、单独编译链接成一个可执行程序(hello)。主进程为父进程,fork创建了子进程后在子进程中exec来执行hello,达到父子进程分别做不同程序同时(宏观上)运行的效果。

#include<unistd.h>

int execve(const char *path,char *const argv[],char *const envp[]);//这个是真正的系统调用
//以下的函数最后都是调用这个函数

int execl(const char *path,char *const argv,···);
int execlp(const char *file,char *const argv,···);
int execle(const char *path,char *const argv,···· ,char *const envp[]);
int execv(const char *path,char *const argv[]);
int execvp(const char *file,char *const argv,);

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

exec函数族装入并运行程序path/file,并将参数arg0(arg1, arg2, argv[], envp[])传递给子程序,出错返回-1.

看一下后缀:

后缀 功能
l 希望接收以逗号分隔的参数列表,列表以NULL指针作为结束标志
v 希望接收到一个以NULL结尾的字符串数组的指针
p 是一个以NULL结尾的字符串数组指针,函数可以DOS的PATH变量查找子程序文件
e 函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境

下面我找到一些通俗易懂的栗子,算是让我明白了一点:

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

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

int main(int argc, char *argv[])
{
  //以NULL结尾的字符串数组的指针,适合包含v的exec函数参数
  char *arg[] = {"ls", "-a", NULL}; /** * 创建子进程并调用函数execl * execl 中希望接收以逗号分隔的参数列表,并以NULL指针为结束标志 */
  if( fork() == 0 )
  { // in clild  printf( "1------------execl------------\n" ); if( execl( "/bin/ls", "ls","-a", NULL ) == -1 ) { perror( "execl error " ); exit(1); }
  } /** *创建子进程并调用函数execv *execv中希望接收一个以NULL结尾的字符串数组的指针 */
  if( fork() == 0 )
  { // in child  printf("2------------execv------------\n"); if( execv( "/bin/ls",arg) < 0) { perror("execv error "); exit(1); }
  } /** *创建子进程并调用 execlp *execlp中 *l希望接收以逗号分隔的参数列表,列表以NULL指针作为结束标志 *p是一个以NULL结尾的字符串数组指针,函数可以DOS的PATH变量查找子程序文件 */
  if( fork() == 0 )
  { // in clhild  printf("3------------execlp------------\n"); if( execlp( "ls", "ls", "-a", NULL ) < 0 ) { perror( "execlp error " ); exit(1); }
  } /** *创建子里程并调用execvp *v 望接收到一个以NULL结尾的字符串数组的指针 *p 是一个以NULL结尾的字符串数组指针,函数可以DOS的PATH变量查找子程序文件 */
  if( fork() == 0 )
  { printf("4------------execvp------------\n"); if( execvp( "ls", arg ) < 0 ) { perror( "execvp error " ); exit( 1 ); }
  } /** *创建子进程并调用execle *l 希望接收以逗号分隔的参数列表,列表以NULL指针作为结束标志 *e 函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境 */
  if( fork() == 0 )
  { printf("5------------execle------------\n"); if( execle("/bin/ls", "ls", "-a", NULL, NULL) == -1 ) { perror("execle error "); exit(1); }
  } /** *创建子进程并调用execve * v 希望接收到一个以NULL结尾的字符串数组的指针 * e 函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境 */
  if( fork() == 0 )
  { printf("6------------execve-----------\n"); if( execve( "/bin/ls", arg, NULL ) == 0) { perror("execve error "); exit(1); }
  }
  return EXIT_SUCCESS;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108

wait/waitpid

这里几个概念:

僵尸进程:子进程退出,父进程没有及时回收,子进程成为僵尸进程
孤儿进程:父进程退出,而子进程没有退出,子进程成为孤儿进程
init进程:1号进程,负责收留孤儿进程,成为他们的父进程

有几种方式终止进程:
(1)main返回
(2)调用exit
(3)调用_exit
(4)调用abort(给自己发送异常终止信号)
(5)由一个信号终止

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
#include<sys/types.h>
#include<sys/wait.h>

pid_t wait(int *status);
//这里的status为一个整形指针,是该子进程的返回状态。若该指针不为空,则可以通过该指针获取子进程退出时的状态。

pid_t waitpid(pid_t pid,int *status,int options);
// pid是进程号
/*
	<-1 回收指定进程组内的任意子进程
	-1 回收任意子进程
	0 回收和当前waitpid调用一个组的所有子进程
	>0 回收指定ID的子进程
*/
//options:
/*
WNOHANG:强制回收,不阻塞。
WUNTRANCED:一般用上面那个
*/

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

进程间间相互关系

-资源共享关系
-相互合作关系

同步机制应遵循的准则

 	- 空闲让进 - 忙则等待 - 有限等待 - 让权等待

  
 
  • 1
  • 2
  • 3
  • 4

读者-写者问题

只要求读的进程称为“reader进程”,其他进程称为“writer进程”。
允许多个reader进程同时读一个共享对象,但决不允许一个writer进程和其他reader进程或writer进程同时访问共享对象
所谓读者-写者问题(The Reader-Writer Problem)是只保证一个writer进程必须与其他进程互斥地访问共享对象的同步问题

哲学家进餐问题

有五个哲学家,他们的生活方式是交替地进行思考和进餐。哲学家们共用一张圆桌,分别坐在周围的五张椅子上。在圆桌上有五个碗和五支筷子,平时一个哲学家进行思考,饥饿时便试图取用其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐。进餐毕,放下筷子继续思考。

解决办法:

①至多只允许四个哲学家同时进餐,以保证至少有一个哲学家能够进餐,最终总会释放出他所使用过的两支筷子,从而可使更多的哲学家进餐。
②仅当哲学家的左、右两支筷子均可用时才允许他拿起筷子进餐。
③规定奇数号哲学家先拿他左边的筷子,然后再去拿他右边的筷子;而偶数号哲学家则相反。

死锁

在多道程序系统中,若对资源的管理、分配和使用不当,也会产生一种危险,即在一定条件下会导致系统发生一种随机性错误——死锁(参考上面两个问题)。

产生死锁的原因

多个进程所共享的资源不足,引起它们对资源的竞争而产生死锁
-竞争可剥夺和非剥夺性资源
-竞争非剥夺性资源

进程运行过程中,请求和释放资源的顺序不当,而导致进程死锁
-进程推进顺序合法
-进程推进顺序非法

说白了,就是竞态。

产生死锁的四个必要条件

  • 互斥条件
    进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占有

  • 请求和保持条件
    当进程因请求资源而阻塞时,对已获得的资源保持不放

  • 不剥夺条件
    进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放

  • 环路等待条件
    在发生死锁时必然存在一个进程—资源的环形链

解决死锁的基本办法

  • 预防死锁
    通过设置某些限制条件,以破坏产生死锁的四个必要条件中的一个或几个,来防止发生死锁。

  • 避免死锁
    在资源的动态分配过程中,使用某种方法去防止系统进入不安全状态,从而避免了死锁的发生。

  • 检测死锁
    检测死锁方法允许系统运行过程中发生死锁。但通过系统所设置的检测机构,可以及时检测出死锁的发生,并精确地确定与死锁有关的进程和资源,然后采取适当措施,从系统中消除所发生的死锁

  • 解除死锁
    解除死锁是与检测死锁相配套的一种设施,用于将进程从死锁状态下解脱出来

预防死锁

系统要求所有进程一次性地申请其所需的全部资源,若系统有足够的资源分配给一进程时,便一次把所有其所需的资源分配给该进程,摒弃“请求”条件;在分配时只要有一种资源要求不能满足,则已有的其它资源也全部不分配给该进程,摒弃“保持”条件(静态资源分配法).

避免死锁

所谓安全状态,是指系统能按某种进程顺序如(P1,P2,…,Pn)(称<P1,P2,…,Pn>序列为安全序列)来为每个进程分配其所需资源,直至最大需求,使每个进程都可顺利完成

若系统不存在这样一个安全序列,则称系统处于不安全状态

如果不按照安全顺序分配资源,则系统可能由安全状态进入不安全状态 ,可能发生死锁

银行家算法

可利用资源向量Available:是一个具有m个元素的数组,其中每一个元素代表一类 可利用的资源数目 ,如果Available[j]=k,表示系统中现有Rj类资源有k个

最大需求矩阵Max:是一个n×m的矩阵,它定义了系统中n个进程中的每一个进程,对m类资源的最大需求,如果Max(i,j)=k,表示进程i需要Rj类资源的最大数目为k

分配矩阵Allocation:一个n×m的矩阵,它定义了系统中每一类资源当前已分配给每一个进程的资源数,如果Allocation(i,j)=k,表示进程i当前已分得Rj类资源的数目为k

需求矩阵Need:是一个n×m的矩阵,用以表示每一个进程尚需的各类资源数,如果Need[i,j]=k,表示进程i还需要Rj类资源k个,方能完成其任务
Need(i,j)=Max(i,j)-Allocation(i,j)

示例

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

检测死锁

为了检测死锁,系统必须①保存有关资源的请求和分配信息,②提供一种算法,利用这些信息来检测系统是否已进入死锁状态

资源分配图

资源分配图是由一组结点N和 一组边E所组成的一对偶G =(N,E)
结点分为两种结点:进程结点和资源结点;边表示进程和资源的请求分配关系

在这里插入图片描述

死锁的解除

-剥夺资源
-撤销进程


进程间通信

之前写了很多篇,直到···

在这里插入图片描述

管道

在shell中管道用“|”表示。管道的历史很悠久了。

可以理解为内存中的一个缓冲区,用于将某个进程的数据流导入,由某一个进程导出,实现通信。

这种管道是没有名字,所以「|」表示的管道称为匿名管道,用完了就销毁

注意,这个匿名管道是特殊的文件,只存在于内存,不存于文件系统中。

匿名管道用于有血缘关系的进程间通信。

在 shell 里面执行 A | B命令的时候,A 进程和 B 进程都是 shell 创建出来的子进程,A 和 B 之间不存在父子关系,它俩的父进程都是 shell。

在这里插入图片描述
如果要用管道进行无血缘关系之间的进程通信,用FIFO有名管道。

局势到这里已经很清楚了,管道具有:“召之即来,挥之即去,且不占文件系统位置”的特性,适合用于shell中的“一次性博弈”。

如果是“长期博弈”,它不行,得往后看。

消息队列

相对于管道的种种限制,消息队列就显得明智多了,因为它存着“尾款”啊,所以在系统中存留的必要性就大了。

1、消息队列是内核地址空间中的内部链表,通过Linux内核在不同的进程间传递消息。
2、消息顺序的发送到消息队列中,并以几种不同的方式从队列中获取。
3、内核中的消息队列是通过IPC标识符来进行区别的,不同消息队列之间是互相独立的。
4、每个消息队列中的消息又构成一个独立的链表。

我把它看作一个“丰巢”。

但是吧,消息队列固然有它的局限性在:
消息队列不适合比较大数据的传输,因为在内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限。在 Linux 内核中,会有两个宏定义 MSGMAX 和 MSGMNB,它们以字节为单位,分别定义了一条消息的最大长度和一个队列的最大长度。

消息队列还是很赞的,单看上面这些特性,比管道占地方,比后面要讲的SHM慢,传输消息大小也不如MMP,还异步,靠。
就是这么个“一无是处”的通信方式,硬生生把自己的劣势转化为了优势,不然它都不知道被压到哪个箱底去了。

对,就是异步

随着“异步业务”的出现(如双十一流量削峰、秒杀系统等),消息队列突然就成了香饽饽,它能承载的消息比管道多,它也不追求速度,占用的内存还比MMP小,简直就是用来做流量削峰、解耦、异步的神器。

RobbieMQ、RocketMQ、Kafka。消息队列:解耦、异步、流量削峰。当下流行的几款MQ以及新手上路该如何选择MQ?

消息队列火了,命运也真是神奇啊。

内存共享映射(SHM)

1、共享内存是在多个进程之间共享内存区域的一种进程间的通信方式。
2、它是在多个进程间通过对指定内存段进行映射实现内存共享的。
3、这是IPC最快捷的方式,因为它没有中间商赚差价。
4、多个进程间共享的是同一块物理空间,仅仅是挂载地址不同而已,因此不需要进行复制,可以直接使用这段空间。
在这里插入图片描述

优点很明显了,快,承载数据量也够,一般用来进行进程间数据包通信(数据包不会太大,而且量多)。

但是如果要进行大文件操作,那这个就有点吃不消了,不是说不行,是扬短避长了哈哈哈,千里马非要它去拉磨。

文件内存映射(MMP)

1、mmap()函数用来将文件或者设备映射到内存中。
2、mmap的特点是按需调页。最开始只申请vma,并不调真正的页。当对某些页进行引用的时候,会引起一个缺页中断,再将页面调入到内存当中,这样避免了对内存的浪费。

mmap的优势: 操作文件就像操作内存一样,适合于对较大文件的读写。

mmap的缺点:
1、文件如果很小,比如60bytes,由于在内存当中的组织都是按页组织的,将文件调入到内存当中是一个页4k,这样其他的4096-60=4036 bytes的内存空间就会浪费掉了。

网络流通信(Socket)

Socket在进程间通信的优势

首先,就是分布式系统,这点当看到Socket做进程间通信的时候我就想到了。Socket进程间通信可以跨主机,具有伸缩性,把进程分布到不同的服务器上,改改端口就能用了。相反,其他IPC都不能跨机器。

其次,我兄弟天天给我吹他学的服务器到后面可以跨平台,Windows和Linux上交互,我问他他说还没学到,那现在呢?就让我先剧透吧哈哈哈哈。

在编程上,TCP sockets和pipe都是操作文件描述符,用来收发字节流,都可以read/write/fcntl/select/poll等,不同的是,TCP是全双工的,pipe是半双工的,不方便。

就拿最快的IPC,shm共享内存来说,就那么好用?对于技术不过硬的朋友来说,那可真的是坑坑洼洼,反正我是鼻青脸肿了。
或许有人会说,具体问题具体分析,单机就用shm,分布式就用TCP,我想问问,有意思吗?有意思吗?就那么喜欢为一个功能写两份代码啊。
在比对一下shm与TCP,TCP是字节流协议,只能顺序读取,有写缓冲;shm是消息协议,一个进程把内容写入虚拟地址,由另一个进程来读走,基本上可以说是阻塞。

其实我是很喜欢shm通信的,甚至都封装了动态库,在我的“动态库”专栏下就能找到,但是吧,叛变就是这么的快哈哈哈。

再者,IPC通信崩溃会怎样?段错误;TCP呢?网络掉线,哪个好找?一目了然。
TCP一断掉,哪里断了哪里重连就好,IPC呢?那就得全部重头来过。

使用TCP长连接通信

使用TCP长连接通信的好处有两点:

  1. 容易定位分布式系统中的服务之间的依赖关系。
    只要在机器上运行netstat -tpna | grep : port 就能立刻列出用到某服务的客户端地址,然后在客户端上用netstatlsof命令找出是哪个进程发起的连接。这样在迁移服务的时候可以有效的防止出现outage。
    TCP短连接和UDP连接则不具备这一特性。

  2. 通过收发队列的长度也比较容易定位网络中或程序故障。在正常运行时,netstat打印的Recv-Q和Send-Q都接近于0,或者在0附近波动。如果Recv-Q保持不变或持续增加,一般是服务进程的处理速度变慢,可能是死锁或阻塞了。如果Send-Q保持不变或持续增长,那可能是对方服务器太忙,没空理你,也有可能是网络中某个路由器或者交换机挂了,甚至对方掉线了。

下面是个示例:
在这里插入图片描述

最后的惊喜

它还能跨语言!!!!!


就到这里啦,点赞评论收藏,一键三连哦(如果你真的看到这里的话)

在这里插入图片描述

文章来源: lion-wu.blog.csdn.net,作者:看,未来,版权归原作者所有,如需转载,请联系作者。

原文链接:lion-wu.blog.csdn.net/article/details/113655829

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。