Linux进程间通信(IPC)与PostgreSQL共享内存管理
2018-04-22 22:12:46 阿炯

本文主要介绍了Linux下进程间通信的一些知识,PostgreSQL的使用过程中会与它们直接相关,方后提供了一个相关的解决具体问题的过程。

Linux系统中的进程间通信方式主要以下几种:

同一主机上的进程通信方式:
* UNIX 进程间通信方式:包括管道 (PIPE), 有名管道 (FIFO), 和信号 (Signal)

* System V 进程通信方式:包括信号量 (Semaphore), 消息队列 (Message Queue), 和共享内存 (Shared Memory)

网络主机间的进程通信方式:
* RPC: Remote Procedure Call 远程过程调用

* Socket: 当前最流行的网络通信方式,基于 TCP/IP 协议的通信方式.

各自的特点如下:

管道(PIPE):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系 (父子进程) 的进程间使用。另外管道传送的是无格式的字节流,并且管道缓冲区的大小是有限的(管道缓冲区存在于内存中,在管道创建时,为缓冲区分配一个页面大小)。

有名管道 (FIFO):有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

信号(Signal):信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

信号量(Semaphore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

消息队列(Message Queue):消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

共享内存(Shared Memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

套接字(Socket):套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机间的进程通信。

线程间通信方式主要以下几种:

*锁机制:包括互斥锁、条件变量、读写锁
互斥锁提供了以排他方式防止数据结构被并发修改的方法;读写锁允许多个线程同时读共享数据,而对写操作是互斥的;条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。

*信号量机制(Semaphore):包括无名线程信号量和命名线程信号量。

*信号机制(Signal):类似进程间的信号处理。

线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。


Linux下个各种进程间的通信方式:管道,FIFO,消息队列,他们的共同特点就是通过内核来进行通信(假设POSIX消息队列也是在内核中实现的,因为POSIX标准并没有限定它的实现方式)。向管道,FIFO,消息队列写入数据需要把数据从进程复制到内核,从这些IPC读取数据的时候又需要把数据从内核复制到进程。所以这种IPC方式往往需要2次在进程和内核之间进行数据的复制,即进程间的通信必须借助内核来传递:


通过内核进行通信的IPC

共享内存也是一种IPC,它是目前可用IPC中最快的,它是使用方式是将同一个内存区映射到共享它的不同进程的地址空间中,这样这些进程间的通信就不再需要通过内核,只需对该共享的内存区域进程操作就可以了,和其他IPC不同的是,共享内存的使用需要用户自己进行同步操作。下图是共享内存区IPC的通信:


共享内存IPC通信
 
共享内存是最高效的IPC机制,因为它不涉及进程之间的任何数据传输。这种高效率带来的问题是,我们必须用其他辅助手段来同步进程对共享内存的访问,否则会产生竞态条件。因此,共享内存通常和其他进程间通信方式一起使用。Linux下有三种共享内存的IPC技术:System V共享内存、共享文件映射(mmap)、POSIX共享内存。

1)、Linux和所有的UNIX操作系统都允许通过共享内存在应用程序之间共享存储空间。

2)、有两类基本的API函数用于在进程间共享内存:System v和POSIX(当然,还有mmap,属于POSIX的)。

3)、这两类函数上使用相同的原则,核心思想就是任何要被共享的内存都必须经过显示的分配。

4)、因为所有进程共享同一块内存,共享内存在各种进程间通信方式中具有最高的效率。

5)、内核没有对访问共享内存进行同步,所以必须提供自己的同步措施,比如数据在写入之前不允许其它进程对其进行读写。

POSIX共享内存

POSIX共享内存不需要自己手动挂载,只要打开成功就会自动挂载,一般挂载在 /dev/shm 目录下。POSIX共享内存能够让无关进程共享一个映射区域,而无需创建一个相应的文件映射。Linux从内核2.4起开始支持POSIX共享内存。SUSv3并没有对POSIX共享内存的实现细节进行规定,特别是没有要求使用一个(真实或虚拟)文件系统来标识共享内存对象,但是很多UNIX实现都采用了文件系统来标识共享内存对象。一些UNIX实现将共享对象名创建为标准文件系统上一个特殊位置处的文件,Linux使用挂载于/dev/shm目录下的专用tmpfs文件系统,这个文件系统具有内核持久性,即它所包含的共享内存对象会一直持久,即使当前不存在任何进程打开它,但这些对象会在系统关闭之后丢失。

注意:系统上POSIX共享内存区域占据的内存总量受限于底层的tmpfs文件系统的大小,这个文件系统通常会在启动时使用默认大小(如,256MB)进行挂载,如果有必要的话,root用户能够使用命令mount -o remount,size=<num-bytes>重新挂载这个文件系统来修改它的大小。

要使用POSIX共享内存对象需要完成下列任务:
1.使用shm_open()函数打开一个与指定的名字对应的对象。shm_open()函数与open()系统调用类似,它会创建一个新的共享对象或打开一个既有对象。作为函数结果,shm_open()会返回一个引用该对象的文件描述符。

2.将上一步中获得的文件描述符传入mmap()调用并在其flags参数中指定MAP_SHARED。这会将共享内存对象映射进进程的虚拟地址空间。与mmap()的其他用法一样,一旦映射了对象之后就能够关闭该文件描述符而不会影响到这个映射。

使用共享内存

使用步骤:
1. 调用shm_open()函数创建或打开一个共享内存对象。
2. 调用ftruncate()调整对象大小。
3. 调用mmap()函数,将内存映射到相应的进程虚拟内存中。mmap()系统调用在调用进程的虚拟地址空间中创建一个新映射。

说明:
1. POSIX共享内存上shm_open()和mmap()的关系类似于System V共享内存上shmget()和shmat()的关系。使用POSIX共享内存对象需要两步式过程(shm_open()+mmap()),而没有使用单个函数来执行两项任务是因为历史原因。在POSIX委员会增加这个特性时,mmap()调用已经存在了,实际上,这里所需要做的事情是使用shm_open()调用替换open()调用,其中差别是使用shm_open()无需在一个基于磁盘文件系统上创建一个文件。

2.由于共享内存对象的引用是通过文件描述符来完成的,因此可以直接使用UNIX系统中已经定义好的各种文件描述符系统调用,而无需增加新的系统调用。

POSIX共享内存API

1)、函数shm_open和shm_unlink非常类似于为普通文件所提供的open和unlink系统调用。

2)、如果要编写一个可移植的程序,那么shm_open和shm_unlink是最好的选择。

3)、shm_open:创建一个新的共享区域或者附加在已有的共享区域上。区域被其名字标识,函数返回各文件的描述符。

4)、shm_unlink:类似于unlink系统调用对文件进行操作,直到所有的进程不再引用该内存区后才对其进行释放。

5)、mmap:用于将一个文件映射到某一内存区中,其中也使用了shm_open函数返回的文件描述符。

6)、munmap:用于释放mmap所映射的内存区域。

7)、msync:同步存取一个映射区域并将高速缓存的数据回写到物理内存中,以便其他进程可以监听这些改变。

POSIX信号量

当多个进程同时访问系统上的某个资源的时候,就需要考虑进程的同步问题,以确保任一时刻只有一个进程可以拥有对资源的独占式访问。POSIX信号量不仅可以用于进程之间的同步,也可以用于线程之间的同步。

SUSv3规定了两种类型的POSIX信号量。

命名信号量:这种信号量拥有一个名字。通过使用相同的名字调用sem_open(),不相关的进程能够访问同一个信号量。

未命名信号量:这种信号量没有名字,相反,它位于内存中一个预先商定的位置处。未命名信号量可以在进程之间或一组线程之间共享。当在进程之间共享时,信号量必须位于一个共享内存区域中。当在线程之间共享时,信号量可以位于被这些线程共享的一块内存区域中(如在堆上或在一个全局变量中)。

POSIX信号量是一个整数,其值不能小于0。

命名信号量

要使用命名信号量必须要使用下列函数:
sem_open()函数打开或创建一个信号量并返回一个句柄以供后续调用使用,如果这个调用会创建信号量的话还会对所创建的信号量进行初始化。
sem_post()和sem_wait()函数分别递增和递减一个信号量值。
sem_getvalue()函数获取一个信号量的当前值。
sem_close()函数删除调用进程与它之前打开的一个信号量之间的关联关系。
sem_unlink()函数删除一个信号量名字并将其标记为在所有进程关闭该信号量时删除该信号量。

POSIX消息队列

消息队列可认为是一个消息链表,它允许进程之间以消息的形式交换数据。有足够写权限的进程或线程可往队列中放置消息,有足够读权限的进程或线程可从队列中取走消息。每个消息都是一个记录,它由发送者赋予一个优先级。与管道不同,管道是字节流模型,没有消息边界。

本文介绍的是POSIX消息队列。POSIX消息队列与System V消息队列的相似之处在于数据的交换单位是整个消息,但它们之间仍然存在一些显著的差异。

POSIX消息队列是引用计数的。只有当所有当前使用队列的进程都关闭了队列之后才会对队列进行标记以便删除。

每个System V消息都有一个整数类型,并且通过msgrcv()可以以各种方式类选择消息。与之形成鲜明对比的是,POSIX消息有一个关联的优先级,并且消息之间是严格按照优先级顺序排队的(以及接收)。

POSIX消息队列提供了一个特性允许在队列中的一条消息可用时异步地通知进程。

POSIX消息队列API中的主要函数如下:
mq_open()函数创建一个新消息队列或打开一个既有队列,返回后续调用中会用到的消息队列描述符。
mq_send()函数向队列写入一条消息。
mq_recevie()函数从队列中读取一条消息。
mq_close()函数关闭进程之前打开的一个消息队列。
mq_unlink()函数删除一个消息队列名并当所有进程关闭该队列时对队列进行标记以便删除。

上面的函数所完成的功能是相当明显的。此外,POSIX消息队列API还具备一些特别的特性:
每个消息队列都有一组关联的特性,其中一些特性可以在使用mq_open()创建或打开队列时进行设置。获取和修改队列特性的工作则是由两个函数来完成的:mq_getattr()和mq_setattr()。
mq_notify()函数允许一个进程向一个队列注册接收消息通知。在注册完之后,当一条消息可用时会通过发送一个信号或在一个单独的线程中调用一个函数来通知进程。

fork()、exec()以及进程终止对消息队列描述符的影响:
在fork()中子进程会接收其父进程的消息队列描述符的副本,并且这些描述符会引用同样 的打开着的消息队列描述符。子进程不会继承其父进程的任何消息通知注册。当一个进程执行了一个exec()或终止时,所有其打开的消息队列描述符会被关闭。


System V共享内存

前面已经介绍过了POSIX共享内存区,System V共享内存区在概念上类似POSIX共享内存区,POSIX共享内存区的使用是调用shm_open创建共享内存区后调用mmap进行内存区的映射,而System V共享内存区则是调用shmget创建共享内存区然后调用shmat进行内存区的映射。对每个System V共享内存区,内核会维护一个shmid_ds的数据结构。

System V共享内存 API

1)、System V API广泛应用于X windows系统及其扩展版本中,许多X应用程序也使用它.

2)、shmget:创建一个新的共享区域或者附加在已有的共享区域上(同shm_open).

3)、shmat:用于将一个文件映射到内存区域中(同mmap).

4)、shmdt:用于释放所映射的内存区域(同munmap)

5)、shmctl:对于多个用户,断开其对共享区域的连接(同shm_unlink)


System V消息队列

System V消息队列是Open Group定义的XSI,不属于POSIX标准。System V IPC的历史相对很早,在上个世70年代后期有贝尔实验室的分支机构开发,80年代加入System V的系统内核中,后来商用UNIX系统基本都加入了System V IPC的功能。

System V消息队列相对于POSIX消息队列的区别主要是:

POSIX消息队列的读操作总是返回消息队列中优先级最高的最早消息,而对于System V消息队列可以返回任意指定优先级(通过消息类型)的消息。

当向一个空消息队列中写入一个消息时,POSIX消息队列允许产生一个信号或启动一个线程,System V消息队列不提供类似的机制。

系统内核都会为每一个System V消息队列维护一个信息结构。对于System V消息队列一般内核还有一个限制:系统范围内的最大消息数,在Linux下这个限制由msgmnb*msgmni决定。

上面已经说过可以通过IPC_SET来设置使用中的消息队列的最大字节数。但是要在系统范围内对内核限制进行修改,在Linux下面可以通过修改/etc/sysctl.conf内核参数配置文件,然后配合sysctl命令来对内核参数进行设置。

System V 信号量

System V信号量是不属于POSIX标准,它属于SUS(Single UNIX Specification)单一UNIX规范中的扩展定义。它和POSIX信号量一样都提供基本的信号量功能操作。

System V信号量相对于POSIX信号量最大的区别是在信号量的操作复杂度。

在POSIX信号量中说过,根据信号量取值(代表可用资源的数目)的不同,POSIX信号量可以分为:

二值信号量:信号量的值只有0和1,这和互斥量很类型,若资源被锁住,信号量的值为0,若资源可用,则信号量的值为1;

计数信号量:信号量的值在0到一个大于1的限制值(POSIX指出系统的最大限制值至少要为32767)。该计数表示可用的资源的个数。

相对于POSIX信号量,System V信号量增加了复杂度,我们称System V信号量之为:计数信号量集。计数信号量集:至少有一个信号量构成的集合,其中集合中的每个信号量都是计数信号量。对于信号量集中的信号量数目系统内核是存在限制的,由内核参数:SEMMSL决定。

对于System V信号量,系统内核还有很多限制,譬如下面:
SEMMNS:系统中信号量的最大数目,等于SEMMNI*SEMMSL  
SEMOPM:一次semopt()操作的最大信号量数目  
SEMMNI:系统内核中信号量的最大数目  

对于系统中的每个System V信号量,即每个信号量集,内核都会维护一个semid_ds的信息结构。

更多知识

Linux进程间通信之管道

IPC是进程间通信(interprocess communication)的简称,我们在这里对有关管道的知识进行总结。

管道(pipe):管道是第一个广泛使用的IPC形式,既可在程序中使用,也可以从shell中使用。管道只能在具有共同祖先(指父子进程关系)的进程间使用。

FIFO:FIFO是管道概念的一个变体,FIFO(First In First Out,先进先出),也叫有名管道。可以用于任意进程间通信。

socketpair:socket编程接口提供的一个创建全双工管道的系统调用。也被称为流管道。

管道:

管道只能用于具有相关关系的进程之间通信,所谓的相关关系,即拥有血缘关系的进程,具有共同祖先。

管道是一个字节流,意味着在使用管道时是不存在消息或消息边界的概念的。从管道中读取数据的进程可以读取任意大小的数据块,而不管写入进程写入管道的数据块的大小是什么。此外,通过管道传递的数据是顺序的—-从管道中读取出来的字节的顺序与它们被写入管道的顺序是完全一样的。

在管道中数据的传递方向是单向的。管道的一端用于写入,另一端则用于读取。管道的容量是有限的。一旦管道被填满之后,后续向该管道的写入操作就会被阻塞直到读者从管道中移除了一些数据为止。

说明:

成功的pipe()调用会在数组filedes中返回两个打开的文件描述符:一个表示管道的读取端(filedes[0]),另一个表示管道的写入端(filedes)。

与所有文件描述符一样,可以使用read()和write()系统调用在管道上执行I/O。

管道是父进程和子进程间通信的常用手段。管道能在父,子进程间传递数据,利用的是fork调用之后两个管道文件描述符两个都保持打开。

使用pipe()创建完管道之后的情况,如图:


调用fork()之后,如图:


关闭未使用的文件描述符后:


虽然父进程和子进程都可以从管道中读取和写入数据,但这种做法并不常见。因此,在fork()调用之后,其中一个进程应该立即关闭管道的写入端的描述符,另一个则应该关闭读取端的描述符。比如,如上图所示,如果父进程需要向子进程传输数据,那么它就会关闭管道的读取端的描述符,而子进程就会关闭管道的写入端的描述符。

显然,如果要实现父,子进程之间的双向数据传输,就必须使用两个管道:创建两个管道,在两个进程之间发送数据的两个方向上各使用一个。

补充:

需要注意的是,当使用两个管道,实现父子进程双向通信时,要注意会发生死锁的情况。因为创建的管道描述符默认是阻塞的。如果两个进程都试图从空管道中读取数据或尝试向已满的管道中写入数据就可能会发生死锁。

关闭未使用的管道文件描述符不仅仅是为了确保进程不会耗尽其文件描述符的限制——-这对于正确使用管道是非常重要的。
当一个管道的所有的写入端都被关闭时,管道的读端调用recv时,recv会返回0,表示文件结束。

如果读取进程没有关闭管道的写入端,那么在其他进程关闭了写入描述符之后,读者也不会看到文件结束,即使它读完了管道中的所有数据,read()也会阻塞以等待数据,这是因为内核知道至少还存在一个管道的写入描述符打开着,即读取进程自己打开了这个描述符。

同理,如果写入进程没有关闭管道的读取端,那么即使在其他进程已经关闭了管道的读取端之后写入进程仍然能够向管道写入数据,最后写入进程会将数据充满整个管道,后续的写入请求会被永远阻塞。

FIFO:

管道没有名字,因此它们的最大劣势是只能用于有一个共同祖先进程的各个进程之间。

FIFO与管道类似,它们两者之间最大的差别在于FIFO在文件系统中拥有一个名称,并且其打开方式与打开一个普通文件是一样的。这样就能够将FIFO用于非相关进程之间的通信。它是一个单向(半双工)数据流。FIFO由mkfifo函数创建。

在创建出一个FIFO后,它必须或者打开来读,或者打开来写,所用的可以是open函数,也可以是某个标准I/O打开函数,例如fopen。FIFO不能打开来既读又写,因为它是半双工的。

FIFO与管道的例子变动如下:
创建并打开一个管道只需要调用pipe。创建并打开一个FIFO则需在调用mkfifo之后再调用open。

管道在所有进程最终都关闭它之后自动消失。FIFO的名字则只有通过调用unlink才从文件系统删除。

socketpair:

socket编程接口提供了一个创建全双工的管道的系统调用:socketpair。socketpair()系统调用只能用在UNIX domain中,即domain参数必须被指定为AF_UNIX。protocol参数必须指定为0。

将type参数指定为SOCK_STREAM,创建一个双向管道(也被成为流管道)。

sockfd数组返回了引用这两个相互连接的socket的文件描述符。

每个socket都可以用来读取和写入,并且这两个socket之间每个方向上的数据信道是分开的。这对套接字可以进行双工通信,每一个描述符既可以读也可以写。这个在同一个进程中也可以进行通信,向sockfd[0]中写入,就可以从sockfd[1]中读取(只能从sockfd[1]中读取),也可以在sockfd[1]中写入,然后从sockfd[0]中读取;但是,若没有在0端写入,而从1端读取,则1端的读取操作会阻塞。

在Linux中,完全可以把这一对socket当成pipe返回的文件描述符一样使用,唯一的区别就是这一对文件描述符中的任何一个都可读和可写。

一起修改dynamic_shared_memory_type参数导致不能启动的问题

在初始化时会报出如下的错误(操作系统为CRUX 3.2):
bin/pg_ctl start -D /var/lib/pgsql/main -o "-c config_file=/etc/pgsql/postgresql.conf"

FATAL:  could not open shared memory segment "/PostgreSQL.1035113192": Permission denied
LOG:  database system is shut down

这与在安装greenplum所报出的与内核相关的共享内存的参数不匹配错误信息不一样,对系统的shmmax,shmall参数修改后,甚至对像配置文件中内存使用及连接数减到不能再小了也依然报这个错误,那到底是什么问题导致的呢?

将配置文件中的'dynamic_shared_memory_type' 由sysv改为 posix了,却发现无法启动,该选项不外乎如下几个选项:
dynamic_shared_memory_type = ?    
# the default is the first optionsupported by the operating system:
#   posix
#   sysv
#   windows
#   mmap
# use none to disable dynamic shared memory

网上关于这个参数的资料要少一些,碰到这种问题相当多的情况要修改最为重要的内存参数:
# sysctl -a | grep -i "kernel.shm"
kernel.shmmax
kernel.shmall
kernel.sem

shmmax 表示内核所允许的最大共享内存段的大小(bytes)。
缺省设置:33554432

shmall 表示在任何给定时刻,系统上可以使用的共享内存的总数,至少为ceil(shmmax/PAGE_SIZE)。
缺省设置:2097152

shmmni 表示用于整个系统的共享内存段的最大数目(个)
缺省设置:4096

关于这些参数的设置,也找到了一些设定方法。

Increasing SHMMNI will not help, it's the second part of the hint that matters here.Get your system's page size with the shell command getconf PAGE_SIZE.

Usually it's 4096. Multiply that by SHMALL.

In your case that should be SHMALL * 4096 = SHMMAX. That's your current maximum shared memory, independently of SHMMNI.

Conclusion: increase SHMALL.

一个简单的依据实际情况的计算脚本:
#!/bin/bash
# simple shmsetup script
page_size=`getconf PAGE_SIZE`
phys_pages=`getconf _PHYS_PAGES`
shmall=`expr $phys_pages / 2`
shmmax=`expr $shmall \* $page_size`
echo kernel.shmmax = $shmmax
echo kernel.shmall = $shmall

即时设置:
sysctl -w kernel.shmmax=1581617152
sysctl -w kernel.shmall=386137

要重启后生效,还要写入/etc/sysctl.conf文件中。

查看信号量的指令:
# ipcs -l

------ Messages Limits --------
max queues system wide = 32000
max size of message (bytes) = 8192
default max size of queue (bytes) = 16384

------ Shared Memory Limits --------
max number of segments = 4096
max seg size (kbytes) = 18014398509465599
max total shared memory (kbytes) = 18014398442373116
min seg size (bytes) = 1

------ Semaphore Limits --------
max number of arrays = 32000
max semaphores per array = 32000
max semaphores system wide = 1024000000
max ops per semop call = 500
semaphore max value = 32767

kernel.shm 设置不当导致 FATAL: could not create shared memory segment.

但发现按之前和他人的经验操作后,问题依然,后来发现这个参数确实不能随便改,尤其是在已经库初始化完成后,因为这个是在初始时指定的,要修改这个参数就要在库初始时指定:
...
选择默认最大联接数 (max_connections) ... 100
选择默认共享缓冲区大小 (shared_buffers) ... 128MB
选择动态共享内存实现 ......sysv
创建配置文件 ... 成功
...

但看了initdb的手册,没有发现对初始库时对这个参数进行指定(initdb --help),在'-s, --show 显示内部设置'中有线索:
postgres@pcrux:~$ initdb -s
属于此数据库系统的文件宿主为用户 "postgres".
此用户也必须为服务器进程的宿主.
VERSION=9.6.8
PGDATA=/var/pgsql/data
share_path=/usr/share/postgresql
PGPATH=/usr/bin
POSTGRES_SUPERUSERNAME=postgres
POSTGRES_BKI=/usr/share/postgresql/postgres.bki
POSTGRES_DESCR=/usr/share/postgresql/postgres.description
POSTGRES_SHDESCR=/usr/share/postgresql/postgres.shdescription
POSTGRESQL_CONF_SAMPLE=/usr/share/postgresql/postgresql.conf.sample
PG_HBA_SAMPLE=/usr/share/postgresql/pg_hba.conf.sample
PG_IDENT_SAMPLE=/usr/share/postgresql/pg_ident.conf.sample

看能否修改POSTGRESQL_CONF_SAMPLE来实现,发现还是不能:
postgres@pcrux:~$ initdb -D /tmp/pgs

创建目录 /tmp/pgs ... 成功
正在创建子目录 ... 成功
选择默认最大联接数 (max_connections) ... 100
选择默认共享缓冲区大小 (shared_buffers) ... 128MB
选择动态共享内存实现 ......sysv
创建配置文件 ... 成功
正在运行自举脚本 ...致命错误:  无法打开共享内存段 "/PostgreSQL.1804289383": Permission denied
子进程已退出, 退出码为 1
initdb: 删除数据目录 "/tmp/pgs"

难道是我在编译内核时没有将对应的方法编译进去,所以操作系统不支持?

再次进入内核编译界面,发现真的没有将'POSIX Message Queues'编译进内核,只有一个'System V IPC'被选中。这两项都在'General setup'页下,再经过编译安装后,依然没有成功,仍报同样的错误。

如果从错误本身认识的话,应该是权限不具备导致的,通过的诸多的文档的查阅,发现确实是因为内存盘(shm)设备的权限导致的:
# ls -lh /dev/|grep shm
drwxr-xr-x 2 root root       40 11月 17  2015 shm
# chmod o+w /dev/shm


改过之后,只要直接进行初始化就能完成且'动态共享内存实现'为'posix',无需编译内核或更改postgresql.conf.sample模板中的配置。发现像centos 6.x中该设备文件默认的权限就是777。关于对它的调整请称步:tmpfs(shm)解决方案

关于PostgreSQL的共享内存管理,早期的 PostgreSQL 版本共享内存分配只支持sysv的方式,数据库启动时,需要分配一块共享内存段,这个需求主要与配置多大的shared_buffers 有关。

社区在9.3的版本做了一个变动,使用mmap分配共享内存,大幅降低了系统对System V共享内存的需求量。

但是mmap并不好,会带来额外的IO开销,所以PostgreSQL 9.4开始,又做了一个变动,支持动态分配共享内存,主要是为多核并行计算做的铺垫,而且默认的共享内存分配的方法变成了posix(如果环境支持),同样不需要启动时分配大的共享内存段 。

从9.4开始,共享内存分配方法通过参数 dynamic_shared_memory_type 控制。

不同的值,创建共享内存段的方法也不一样,例如posix使用shm_open,sysv使用shmget,mmap使用mmap。正是由于创建共享内存段的方法不一样,所以需要配置的操作系统内核参数也不一样。

参考来源

walker沃克的CSDN博客

如何度量Kernel Resources for PostgreSQL

Setting up shared memory for PostgreSQL

Postgres与OS内核相关的几个参数设置

postgresql v9.x 18.4.管理内核资源

postgresql v8.x 16.4.管理内核资源


Postgresql 启动报错 FATAL: could not map anonymous shared memory