Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Приветствую
Продолжаем изучение работы PostgreSQL. Мы остановились на моменте общей инициализации процесса. Сегодня мы рассмотрим процесс инициализации Postgres - входную точку до основного цикла.
PostgresMain - точка входа для процесса бэкэнда. Расположена в src/backend/tcop/postgres.c
Бэкэнд может работать как автономно (Standalone), так и в обычном (многопользовательском) режиме (из-под Postmaster)
Описание работы ведется с учетом работы в многопользовательском режиме (IsUnderPostmaster равен true
)
Работа в автономном режиме
Если работа идет автономно, то многие опции еще не инициализированы. Например, настройки GUC. Поэтому, перед самим циклом нужно проинициализировать все значения, но только если мы запустились не из-под Postmaster.
Для этого во время инициализации из разных мест вызываются многие функции инициализации встречавшиеся ранее. Например, InitializeGUCOptions, но с проверкой, что не запустились из Postmaster. Для проверки этого есть переменная IsUnderPostmaster.
В коде есть много мест с паттерном
if (!IsUnderPostmaster) {
someOperation();
}
Его можно интерпретировать как "выполнение функции someOperation
должно происходить при запуске в автономном режиме"
Например, в самом начале точки входа есть такая проверка
void
PostgresMain(int argc, char *argv[],
const char *dbname,
const char *username)
{
int firstchar;
StringInfoData input_message;
sigjmp_buf local_sigjmp_buf;
volatile bool send_ready_for_query = true;
bool idle_in_transaction_timeout_enabled = false;
bool idle_session_timeout_enabled = false;
/* Initialize startup process environment if necessary. */
if (!IsUnderPostmaster)
InitStandaloneProcess(argv[0]);
// ...
}
Установка состояния работы
Как только начинаем работу в качестве бэкэнда, то входим в состояние InitProcessing.
Состояние нормальной работы (NormalProcessing) выставляется после инициализации процесса бэкэнда (InitPostgres)
ProcessingMode
Не только Postmaster имеет машину состояний, но и бэкэнд. Его машина состояний описывает перечислением ProcessingMode
typedef enum ProcessingMode
{
BootstrapProcessing, /* bootstrap creation of template database */
InitProcessing, /* initializing system */
NormalProcessing /* normal processing */
} ProcessingMode;
Всего есть 3 состояния:
BootstrapProcessing - создание БД из шаблона
InitProcessing - старт и инициализация бэкэнда
NormalProcessing - обычная работа
Для них также определены свои макросы:
extern ProcessingMode Mode;
#define IsBootstrapProcessingMode() (Mode == BootstrapProcessing)
#define IsInitProcessingMode() (Mode == InitProcessing)
#define IsNormalProcessingMode() (Mode == NormalProcessing)
#define GetProcessingMode() Mode
#define SetProcessingMode(mode) \
do { \
AssertArg((mode) == BootstrapProcessing || \
(mode) == InitProcessing || \
(mode) == NormalProcessing); \
Mode = (mode); \
} while(0)
Перечисление и макросы определены в src/include/miscadmin.h
Сигналы
После настраиваем свои обработчики сигналов:
SIGHUP - перезагрузка конфигурации
SIGINT - отмена текущего запроса (Fast shutdown)
SIGTERM - отмена текущего запроса и выход (Smart shutdown)
SIGQUIT - быстрый выход (Immediate shutdown)
SIGPIPE - игнорируется, т.к. это безопасней чем прерывать ‘who-knows-what operation’, а заметим мы это на следующей итерации цикла
SIGUSR1 - имеет множественные значения, например, восстановление БД
SIGUSR2 - игнорируется
SIGFPE - ошибка операций с плавающей точкой
Множественные значения на 1 сигнал
SIGUSR1 - сигнал со множеством различных значений. Но как определяется значение?
В Postgres для этого используется общая память.
Структура ProcSignalSlot предоставляет возможность передачи дополнительной информации для сигналов
typedef struct
{
volatile pid_t pss_pid;
volatile sig_atomic_t pss_signalFlags[NUM_PROCSIGNALS];
pg_atomic_uint64 pss_barrierGeneration;
pg_atomic_uint32 pss_barrierCheckMask;
ConditionVariable pss_barrierCV;
} ProcSignalSlot;
Поле pss_signalFlags - массив булевских значений, представляющий причины для сигналов.
Сами причины определяются перечислением ProcSignalReason
typedef enum
{
PROCSIG_CATCHUP_INTERRUPT, /* sinval catchup interrupt */
PROCSIG_NOTIFY_INTERRUPT, /* listen/notify interrupt */
PROCSIG_PARALLEL_MESSAGE, /* message from cooperating parallel backend */
PROCSIG_WALSND_INIT_STOPPING, /* ask walsenders to prepare for shutdown */
PROCSIG_BARRIER, /* global barrier interrupt */
PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */
/* Recovery conflict reasons */
PROCSIG_RECOVERY_CONFLICT_DATABASE,
PROCSIG_RECOVERY_CONFLICT_TABLESPACE,
PROCSIG_RECOVERY_CONFLICT_LOCK,
PROCSIG_RECOVERY_CONFLICT_SNAPSHOT,
PROCSIG_RECOVERY_CONFLICT_BUFFERPIN,
PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK,
NUM_PROCSIGNALS /* Must be last! */
} ProcSignalReason;
Для обработки SIGUSR1 используется следующим образом
// Обработчик SIGUSR1 срабатывает
void
procsignal_sigusr1_handler(SIGNAL_ARGS)
{
int save_errno = errno;
if (CheckProcSignal(PROCSIG_CATCHUP_INTERRUPT))
HandleCatchupInterrupt();
if (CheckProcSignal(PROCSIG_NOTIFY_INTERRUPT))
HandleNotifyInterrupt();
if (CheckProcSignal(PROCSIG_PARALLEL_MESSAGE))
HandleParallelMessageInterrupt();
if (CheckProcSignal(PROCSIG_WALSND_INIT_STOPPING))
HandleWalSndInitStopping();
if (CheckProcSignal(PROCSIG_BARRIER))
HandleProcSignalBarrierInterrupt();
if (CheckProcSignal(PROCSIG_LOG_MEMORY_CONTEXT))
HandleLogMemoryContextInterrupt();
if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_DATABASE))
RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_DATABASE);
if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_TABLESPACE))
RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_TABLESPACE);
if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_LOCK))
RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_LOCK);
if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_SNAPSHOT))
RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_SNAPSHOT);
if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK))
RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN))
RecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
SetLatch(MyLatch);
errno = save_errno;
}
// В обработчике последовательно проходятся все SigProcReason
// и для проверки вызывается эта функция
static bool
CheckProcSignal(ProcSignalReason reason)
{
volatile ProcSignalSlot *slot = MyProcSignalSlot;
if (slot != NULL)
{
/* Careful here --- don't clear flag if we haven't seen it set */
if (slot->pss_signalFlags[reason])
{
slot->pss_signalFlags[reason] = false;
return true;
}
}
return false;
}
Все функции и определения находятся в файле src/backend/storage/ipc/procsignal.c
Postgres также использует SIGALRM. За его обработку отвечает модуль timeout src/backend/utils/misc/timeout.c
Модуль (ре)инициализируется: все таймауты сбрасываются и заново регистрируется функция обработки сигнала.
BaseInit
Функция BaseInit (src/backend/utils/init/postinit.c) вызывается каждым процессом в начале своей работы. Ее главная задача - инициализация общих модулей:
Работа с файлами
Модуль fd
Модуль для работы с файлами именуется fd
Типы и функции для работы с файлами объявляются в src/include/storage/fd.h
Для работы с файлами вместо "сырых" дескрипторов используется свой тип File, который на самом деле и есть int
typedef int File;
Также объявляются функции для работы с файлами. Например, объявляются функции для создания/открытия фалов
File PathNameOpenFile(const char *fileName, int fileFlags);
File PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
File OpenTemporaryFile(bool interXact);
Причина добавления отдельного модуля проста - максимальное количество открытых файлов. Во время работы открываются не только файлы таблиц, но и для сортировки, например.
Реализация содержится в src/backend/storage/file/fd.c
Для хранения используется LRU список внутренней структуры vfd
typedef struct vfd
{
int fd; /* current FD, or VFD_CLOSED if none */
unsigned short fdstate; /* bitflags for VFD's state */
ResourceOwner resowner; /* owner, for automatic cleanup */
File nextFree; /* link to next free VFD, if in freelist */
File lruMoreRecently; /* doubly linked recency-of-use list */
File lruLessRecently;
off_t fileSize; /* current size of file (0 if not temporary) */
char *fileName; /* name of file, or NULL for unused VFD */
/* NB: fileName is malloc'd, and must be free'd when closing the VFD */
int fileFlags; /* open(2) flags for (re)opening the file */
mode_t fileMode; /* mode to pass to open(2) */
} Vfd;
/*
* Virtual File Descriptor array pointer and size. This grows as
* needed. 'File' values are indexes into this array.
* Note that VfdCache[0] is not a usable VFD, just a list header.
*/
static Vfd *VfdCache;
static Size SizeVfdCache = 0;
Сам массив представляет собой двусвязный список. Причем элемент с 0 индексом - особый: это всегда начало/конец списка и его дескриптор равен VFD_CLOSED.
При инициализации модуля вызывается функция InitFileAccess
/*
* InitFileAccess --- initialize this module during backend startup
*
* This is called during either normal or standalone backend start.
* It is *not* called in the postmaster.
*/
void
InitFileAccess(void)
{
Assert(SizeVfdCache == 0); /* call me only once */
/* initialize cache header entry */
VfdCache = (Vfd *) malloc(sizeof(Vfd));
if (VfdCache == NULL)
ereport(FATAL,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
MemSet((char *) &(VfdCache[0]), 0, sizeof(Vfd));
VfdCache->fd = VFD_CLOSED;
SizeVfdCache = 1;
/* register proc-exit hook to ensure temp files are dropped at exit */
on_proc_exit(AtProcExit_Files, 0);
}
Функция для открытия файла и записи его в LRU
File
PathNameOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode)
{
char *fnamecopy;
File file;
Vfd *vfdP;
/*
* We need a malloc'd copy of the file name; fail cleanly if no room.
*/
fnamecopy = strdup(fileName);
if (fnamecopy == NULL)
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
file = AllocateVfd();
vfdP = &VfdCache[file];
/* Close excess kernel FDs. */
ReleaseLruFiles();
vfdP->fd = BasicOpenFilePerm(fileName, fileFlags, fileMode);
if (vfdP->fd < 0)
{
int save_errno = errno;
FreeVfd(file);
free(fnamecopy);
errno = save_errno;
return -1;
}
++nfile;
vfdP->fileName = fnamecopy;
/* Saved flags are adjusted to be OK for re-opening file */
vfdP->fileFlags = fileFlags & ~(O_CREAT | O_TRUNC | O_EXCL);
vfdP->fileMode = fileMode;
vfdP->fileSize = 0;
vfdP->fdstate = 0x0;
vfdP->resowner = NULL;
Insert(file);
return file;
}
В случае, если мы не можем создать виртуальный файл, то присутствует возможность открытия файлов напрямую, но нужно уведомить модуль об этом
/* If you've really really gotta have a plain kernel FD, use this */
int BasicOpenFile(const char *fileName, int fileFlags);
int BasicOpenFilePerm(const char *fileName, int fileFlags, mode_t fileMode);
/* Use these for other cases, and also for long-lived BasicOpenFile FDs */
extern bool AcquireExternalFD(void);
extern void ReserveExternalFD(void);
extern void ReleaseExternalFD(void);
Синхронизация файлов
Модуль sync
Некоторые файлы общие для нескольких процессов. Если кто-то модифицирует файл, то надо уведомить других. Этим занимается модуль sync (src/backend/storage/sync/sync.c)
Опопвещения работают следующим образом:
Регистрируются изменения в файлах
bool RegisterSyncRequest(const FileTag *ftag,
SyncRequestType type,
bool retryOnError);
В нее передаются структуры FileTag и SyncRequestType
// Тип запроса синхронизации
typedef enum SyncRequestType
{
SYNC_REQUEST, /* schedule a call of sync function */
SYNC_UNLINK_REQUEST, /* schedule a call of unlink function */
SYNC_FORGET_REQUEST, /* forget all calls for a tag */
SYNC_FILTER_REQUEST /* forget all calls satisfying match fn */
} SyncRequestType;
// Идентификация файла и места для синхронизации
typedef struct FileTag
{
int16 handler; /* SyncRequestHandler value, saving space */
int16 forknum; /* ForkNumber, saving space */
RelFileNode rnode;
uint32 segno;
} FileTag;
// src/include/storage/relfilenode.h
typedef struct RelFileNode
{
Oid spcNode; /* tablespace */
Oid dbNode; /* database */
Oid relNode; /* relation */
} RelFileNode;
Изменения регистрируются для Checkpointer процесса
Во время чекпоинта вызывается функция синхронизации
void ProcessSyncRequests(void);
Работа с файловой системой
Модуль smgr
Модуль smgr (src/backend/storage/smgr/smgr.c) отвечает за все операции с файловой системой.
Работа с файловой системой ведется посредством структуры SMgrRelationData (src/include/storage/smgr.h)
typedef struct SMgrRelationData
{
/* rnode is the hashtable lookup key, so it must be first! */
RelFileNodeBackend smgr_rnode; /* relation physical identifier */
/* pointer to owning pointer, or NULL if none */
struct SMgrRelationData **smgr_owner;
/*
* The following fields are reset to InvalidBlockNumber upon a cache flush
* event, and hold the last known size for each fork. This information is
* currently only reliable during recovery, since there is no cache
* invalidation for fork extension.
*/
BlockNumber smgr_targblock; /* current insertion target block */
BlockNumber smgr_cached_nblocks[MAX_FORKNUM + 1]; /* last known size */
/* additional public fields may someday exist here */
/*
* Fields below here are intended to be private to smgr.c and its
* submodules. Do not touch them from elsewhere.
*/
int smgr_which; /* storage manager selector */
/*
* for md.c; per-fork arrays of the number of open segments
* (md_num_open_segs) and the segments themselves (md_seg_fds).
*/
int md_num_open_segs[MAX_FORKNUM + 1];
struct _MdfdVec *md_seg_fds[MAX_FORKNUM + 1];
/* if unowned, list link in list of all unowned SMgrRelations */
dlist_node node;
} SMgrRelationData;
typedef SMgrRelationData *SMgrRelation;
Работа осуществляется посредством функций, использующих эту структуру. Например, для чтения из файла используется функция smgrread
/*
* smgrread() -- read a particular block from a relation into the supplied
* buffer.
*
* This routine is called from the buffer manager in order to
* instantiate pages in the shared buffer cache. All storage managers
* return pages in the format that POSTGRES expects.
*/
void
smgrread(SMgrRelation reln,
ForkNumber forknum,
BlockNumber blocknum,
char *buffer);
Под капотом, используется абстракция хранилища и для каждого хранилища есть свой "провайдер". Провайдер описывается структурой f_smgr, являющийся таблицей функций.
typedef struct f_smgr
{
void (*smgr_init) (void); /* may be NULL */
void (*smgr_shutdown) (void); /* may be NULL */
void (*smgr_open) (SMgrRelation reln);
void (*smgr_close) (SMgrRelation reln, ForkNumber forknum);
void (*smgr_create) (SMgrRelation reln, ForkNumber forknum,
bool isRedo);
bool (*smgr_exists) (SMgrRelation reln, ForkNumber forknum);
void (*smgr_unlink) (RelFileNodeBackend rnode, ForkNumber forknum,
bool isRedo);
void (*smgr_extend) (SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, char *buffer, bool skipFsync);
bool (*smgr_prefetch) (SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum);
void (*smgr_read) (SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, char *buffer);
void (*smgr_write) (SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, char *buffer, bool skipFsync);
void (*smgr_writeback) (SMgrRelation reln, ForkNumber forknum,
BlockNumber blocknum, BlockNumber nblocks);
BlockNumber (*smgr_nblocks) (SMgrRelation reln, ForkNumber forknum);
void (*smgr_truncate) (SMgrRelation reln, ForkNumber forknum,
BlockNumber nblocks);
void (*smgr_immedsync) (SMgrRelation reln, ForkNumber forknum);
} f_smgr;
Все известные провайдеры хранятся в массиве smgrsw
static const f_smgr smgrsw[] = {
/* magnetic disk */
// Реализация по умолчанию
// Использует модуль fd
// src/backend/storage/smgr/md.c
{
.smgr_init = mdinit,
.smgr_shutdown = NULL,
.smgr_open = mdopen,
.smgr_close = mdclose,
.smgr_create = mdcreate,
.smgr_exists = mdexists,
.smgr_unlink = mdunlink,
.smgr_extend = mdextend,
.smgr_prefetch = mdprefetch,
.smgr_read = mdread,
.smgr_write = mdwrite,
.smgr_writeback = mdwriteback,
.smgr_nblocks = mdnblocks,
.smgr_truncate = mdtruncate,
.smgr_immedsync = mdimmedsync,
}
};
Все операции строятся следующим образом:
По ID провайдера находится его таблица функций
Вызывается функция для совершения необходимой операции
Чтение из файла реализуется таким образом
void
smgrread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
char *buffer)
{
smgrsw[reln->smgr_which].smgr_read(reln, forknum, blocknum, buffer);
}
Общие страницы
Модуль bufmgr
Постоянное чтение с файловой системы будет довольно дорогим занятием. Для оптимизации используется буферизация - модуль bufmgr управляет страницами, считанными из файлов, и при необходимости сбрасывает их на диск.
Тип буфера определяется в src/include/storage/buf.h
/*
* Buffer identifiers.
*
* Zero is invalid, positive is the index of a shared buffer (1..NBuffers),
* negative is the index of a local buffer (-1 .. -NLocBuffer).
*/
typedef int Buffer;
#define InvalidBuffer 0
Сами операции с буфером объявляются в src/include/storage/bufmgr.h. Например, для чтения буфера объявляются функции
Buffer ReadBuffer(Relation reln, BlockNumber blockNum);
Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum,
BlockNumber blockNum, ReadBufferMode mode,
BufferAccessStrategy strategy);
Buffer ReadBufferWithoutRelcache(RelFileNode rnode,
ForkNumber forkNum, BlockNumber blockNum,
ReadBufferMode mode, BufferAccessStrategy strategy);
Реализация располагается в src/backend/storage/buffer/bufmgr.c
Так как одна и та же страница может быть использована в нескольких процессах, то для отслеживания этого используется структура PrivateRefCountEntry
typedef struct PrivateRefCountEntry
{
Buffer buffer;
int32 refcount;
} PrivateRefCountEntry;
Для хранения страниц используется массив PrivateRefCountArray
/* 64 bytes, about the size of a cache line on common systems */
#define REFCOUNT_ARRAY_ENTRIES 8
static struct PrivateRefCountEntry
PrivateRefCountArray[REFCOUNT_ARRAY_ENTRIES];
InitProcess
Теперь вызывается функция InitProcess. (src/backend/storage/lmgr/proc.c)
Она инициализирует специфичные для типа процесса (AutoVacuum, BgWorker, WalSender и т.д.) переменные и структуры.
Для отслеживания состояния приложения существует структура PROC_HDR
typedef struct PROC_HDR
{
// ...
/* Length of allProcs array */
uint32 allProcCount;
/* Head of list of free PGPROC structures */
PGPROC *freeProcs;
/* Head of list of autovacuum's free PGPROC structures */
PGPROC *autovacFreeProcs;
/* Head of list of bgworker free PGPROC structures */
PGPROC *bgworkerFreeProcs;
/* Head of list of walsender free PGPROC structures */
PGPROC *walsenderFreeProcs;
// ...
} PROC_HDR;
Указанные поля представляют списки свободных структур для каждого типа процесса. Структура PGPROC представляет состояние процесса.
При старте каждый процесс получает свою структуру из списка свободных и иницилизирует ее значениями по умолчанию (для ID - невалидные, для bool - false и т.д.)
Так как мы бэкэнд, то получаем свою структуру из списка freeProcs
. Операция получения структуры - конкурентная, поэтому она исполняется с помощью блокировки.
SpinLockAcquire(ProcStructLock);
MyProc = *procgloballist;
if (MyProc != NULL)
{
*procgloballist = (PGPROC *) MyProc->links.next;
SpinLockRelease(ProcStructLock);
}
else
{
/*
* If we reach here, all the PGPROCs are in use. This is one of the
* possible places to detect "too many backends", so give the standard
* error message. XXX do we need to give a different failure message
* in the autovacuum case?
*/
SpinLockRelease(ProcStructLock);
// ...
}
InitPostgres
После общей инициализации процесса, нужно провести инициализацию конкретную. Для этого вызывается функция InitPostgres
(src/backend/utils/init/postinit.c)
Получение Id
На предыдущем шаге мы получили структуру нашего бэкэнда. Теперь необходимо присвоить ему собственный ID. Идентификаторы бэкэндов растут линейно и определяются индексом в массиве shmInvalBuffer
ProcState *stateP = NULL;
SISeg *segP = shmInvalBuffer;
// ...
/* Look for a free entry in the procState array */
for (int index = 0; index < segP->lastBackend; index++)
{
if (segP->procState[index].procPid == 0) /* inactive slot? */
{
stateP = &segP->procState[index];
break;
}
}
// ...
MyBackendId = (stateP - &segP->procState[0]) + 1;
Сам этот массив используется, чтобы бэкэнды обменивались между собой информацией для инвалидации общего кеша
Дополнительно все бэкэнды регистрируются в списке для получения сигналов при закрытии приложения.
Регистрация таймаутов
Дальше происходит регистрация обработчиков таймаутов:
DEADLOCK_TIMEOUT - таймаут при получении блокировки, после которого проверяется дедлок
STATEMENT_TIMEOUT - таймаут на каждое выражение
LOCK_TIMEOUT - таймаут ожидания при получении блокировки (таблицы, строки или любого другого объекта БД)
IDLE_SESSION_TIMEOUT - таймаут неактивного сеанса
IDLE_IN_TRANSACTION_SESSION_TIMEOUT - таймаут неактивного сеанса для транзакции
CLIENT_CONNECTION_TIMEOUT - таймаут проверки подключения клиента
Таймауты определяются структурой TimeoutId
/*
* Identifiers for timeout reasons. Note that in case multiple timeouts
* trigger at the same time, they are serviced in the order of this enum.
*/
typedef enum TimeoutId
{
/* Predefined timeout reasons */
STARTUP_PACKET_TIMEOUT,
DEADLOCK_TIMEOUT,
LOCK_TIMEOUT,
STATEMENT_TIMEOUT,
STANDBY_DEADLOCK_TIMEOUT,
STANDBY_TIMEOUT,
STANDBY_LOCK_TIMEOUT,
IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
IDLE_SESSION_TIMEOUT,
CLIENT_CONNECTION_CHECK_TIMEOUT,
/* First user-definable timeout reason */
USER_TIMEOUT,
/* Maximum number of timeout reasons */
MAX_TIMEOUTS = USER_TIMEOUT + 10
} TimeoutId;
Регистрируются таймауты функцией RegisterTimeout из модуля timeout
/* callback function signature */
typedef void (*timeout_handler_proc) (void);
TimeoutId RegisterTimeout(TimeoutId id, timeout_handler_proc handler);
Инициализация кеша
Для производительности Postgres использует кеш. Всего есть 3 типа кешей:
Кеш отношений (RelationCache) - кеш для быстрого доступа к таблицам. Использует хеш-таблицу.
Реализация хэш-таблицы
В Postgres существует свой тип данных для быстрого доступа к данным по ключу - хэш-таблица.
Структура хэш-таблицы определяется в src/backend/utils/hash/dynahash.c
struct HTAB
{
HASHHDR *hctl; /* => shared control information */
HASHSEGMENT *dir; /* directory of segment starts */
HashValueFunc hash; /* hash function */
HashCompareFunc match; /* key comparison function */
HashCopyFunc keycopy; /* key copying function */
HashAllocFunc alloc; /* memory allocator */
MemoryContext hcxt; /* memory context if default allocator used */
char *tabname; /* table name (for error messages) */
bool isshared; /* true if table is in shared memory */
bool isfixed; /* if true, don't enlarge */
/* freezing a shared table isn't allowed, so we can keep state here */
bool frozen; /* true = no more inserts allowed */
/* We keep local copies of these fixed values to reduce contention */
Size keysize; /* hash key length in bytes */
long ssize; /* segment size --- must be power of 2 */
int sshift; /* segment shift = log2(ssize) */
};
Функции для работы с хэш-таблицами объявляются в src/include/utils/hsearch.h. Например, функция для создания хэш-таблицы:
HTAB *hash_create(const char *tabname, long nelem,
const HASHCTL *info, int flags);
Также существует и другая реализация - simplehash
Она определена в src/include/lib/simplehash.h
Это не просто структура данных. Это набор макросов, который генерирует шаблонную хэш-таблицу и все вспомогательные функции. Например, функция для создния таблицы
#define SH_MAKE_PREFIX(a) CppConcat(a,_)
#define SH_MAKE_NAME(name) SH_MAKE_NAME_(SH_MAKE_PREFIX(SH_PREFIX),name)
#define SH_MAKE_NAME_(a,b) CppConcat(a,b)
#define SH_CREATE SH_MAKE_NAME(create)
#define SH_SCOPE extern
typedef struct SH_TYPE
{
/*
* Size of data / bucket array, 64 bits to handle UINT32_MAX sized hash
* tables. Note that the maximum number of elements is lower
* (SH_MAX_FILLFACTOR)
*/
uint64 size;
/* how many elements have valid contents */
uint32 members;
/* mask for bucket and size calculations, based on size */
uint32 sizemask;
/* boundary after which to grow hashtable */
uint32 grow_threshold;
/* hash buckets */
SH_ELEMENT_TYPE *data;
#ifndef SH_RAW_ALLOCATOR
/* memory context to use for allocations */
MemoryContext ctx;
#endif
/* user defined data, useful for callbacks */
void *private_data;
} SH_TYPE;
#ifdef SH_RAW_ALLOCATOR
/* <prefix>_hash <prefix>_create(uint32 nelements, void *private_data) */
SH_SCOPE SH_TYPE *SH_CREATE(uint32 nelements, void *private_data);
#else
/*
* <prefix>_hash <prefix>_create(MemoryContext ctx, uint32 nelements,
* void *private_data)
*/
SH_SCOPE SH_TYPE *SH_CREATE(MemoryContext ctx, uint32 nelements,
void *private_data);
#endif
* В большинстве случаев используется dynahash
Системный кеш (CatalogCache) - кеш файловой системы. В ней хранится информация о системных таблицах: конвертеры, классы операторов, метод доступа и другие
Кеш планов (PlanCache) - кеш планировщика
Стоит упомянуть, что инициализация кеша отношений включает 3 этапа:
Аллокация памяти (контекст кеша, хеш-таблицы)
Инициализация кеша под важные общие таблицы (pg_database, pg_authid, pg_auth_members, pg_shseclabel, pg_subscription
Загрузка остальных таблиц в кеш.
Фазы инициализации кеша отношений “разбросаны” по всей функции InitPostres, т.к. для каждой фазы есть свои ограничения. Например, для аутентификации пользователя нам требуются только некоторые таблицы. Поэтому, 2 фаза прогревает кеш для аутенификации, а 3 фаза выполняется после нее и загружает все остальные таблицы в кеш.
Инициализация порталов
Теперь, инициализируется менеджер порталов
Выделяется собственный контекст памяти - TopPortalContext
Создается хэш-таблица для поиска курсора по его имени. Хэш-таблица
Что такое портал
Портал - объект, представляющий исполняющийся запрос. Этот тип используется в расширенном протоколе, на этапе привязки, после парсинга.
Порталы могут быть именованными (имя курсора) или безымянными (одиночный SELECT).
Структура портала определяется в src/include/utils/portal.h
typedef struct PortalData *Portal;
typedef struct PortalData
{
/* Bookkeeping data */
const char *name; /* portal's name */
const char *prepStmtName; /* source prepared statement (NULL if none) */
MemoryContext portalContext; /* subsidiary memory for portal */
ResourceOwner resowner; /* resources owned by portal */
void (*cleanup) (Portal portal); /* cleanup hook */
/*
* State data for remembering which subtransaction(s) the portal was
* created or used in. If the portal is held over from a previous
* transaction, both subxids are InvalidSubTransactionId. Otherwise,
* createSubid is the creating subxact and activeSubid is the last subxact
* in which we ran the portal.
*/
SubTransactionId createSubid; /* the creating subxact */
SubTransactionId activeSubid; /* the last subxact with activity */
/* The query or queries the portal will execute */
const char *sourceText; /* text of query (as of 8.4, never NULL) */
CommandTag commandTag; /* command tag for original query */
QueryCompletion qc; /* command completion data for executed query */
List *stmts; /* list of PlannedStmts */
CachedPlan *cplan; /* CachedPlan, if stmts are from one */
ParamListInfo portalParams; /* params to pass to query */
QueryEnvironment *queryEnv; /* environment for query */
/* Features/options */
PortalStrategy strategy; /* see above */
int cursorOptions; /* DECLARE CURSOR option bits */
bool run_once; /* portal will only be run once */
/* Status data */
PortalStatus status; /* see above */
bool portalPinned; /* a pinned portal can't be dropped */
bool autoHeld; /* was automatically converted from pinned to
* held (see HoldPinnedPortals()) */
/* If not NULL, Executor is active; call ExecutorEnd eventually: */
QueryDesc *queryDesc; /* info needed for executor invocation */
/* If portal returns tuples, this is their tupdesc: */
TupleDesc tupDesc; /* descriptor for result tuples */
/* and these are the format codes to use for the columns: */
int16 *formats; /* a format code for each column */
/*
* Outermost ActiveSnapshot for execution of the portal's queries. For
* all but a few utility commands, we require such a snapshot to exist.
* This ensures that TOAST references in query results can be detoasted,
* and helps to reduce thrashing of the process's exposed xmin.
*/
Snapshot portalSnapshot; /* active snapshot, or NULL if none */
/*
* Where we store tuples for a held cursor or a PORTAL_ONE_RETURNING or
* PORTAL_UTIL_SELECT query. (A cursor held past the end of its
* transaction no longer has any active executor state.)
*/
Tuplestorestate *holdStore; /* store for holdable cursors */
MemoryContext holdContext; /* memory containing holdStore */
/*
* Snapshot under which tuples in the holdStore were read. We must keep a
* reference to this snapshot if there is any possibility that the tuples
* contain TOAST references, because releasing the snapshot could allow
* recently-dead rows to be vacuumed away, along with any toast data
* belonging to them. In the case of a held cursor, we avoid needing to
* keep such a snapshot by forcibly detoasting the data.
*/
Snapshot holdSnapshot; /* registered snapshot, or NULL if none */
/*
* atStart, atEnd and portalPos indicate the current cursor position.
* portalPos is zero before the first row, N after fetching N'th row of
* query. After we run off the end, portalPos = # of rows in query, and
* atEnd is true. Note that atStart implies portalPos == 0, but not the
* reverse: we might have backed up only as far as the first row, not to
* the start. Also note that various code inspects atStart and atEnd, but
* only the portal movement routines should touch portalPos.
*/
bool atStart;
bool atEnd;
uint64 portalPos;
/* Presentation data, primarily used by the pg_cursors system view */
TimestampTz creation_time; /* time at which this portal was defined */
bool visible; /* include this portal in pg_cursors? */
/* Stuff added at the end to avoid ABI break in stable branches: */
int createLevel; /* creating subxact's nesting level */
} PortalData;
Порталы могут быть 2 видов: SQL (SELECT, CURSOR) и портал уровня протокола. Основная разница - возможность прокрутки:
SCROLL - есть возможность прокрутки запроса вперед и назад
NO SCROLL - запрос исполняется только "вперед"
Для SQL возможны оба варианта, а порталы уровня протокола допускают только NO SCROLL.
Дополнительно в этом же файле объявляются функции для работы с порталами. Определяются они в src/backend/utils/mmgr/portalmem.c
Инициализация для сборщика статистики
Сборщик статистики был инициализирован в Postmaster. Но взаимодействовать с ним мы пока не можем.
Для отслеживания статуса бэкэнда есть структура PgBackendStatus
/* ----------
* PgBackendStatus
*
* Each live backend maintains a PgBackendStatus struct in shared memory
* showing its current activity. (The structs are allocated according to
* BackendId, but that is not critical.) Note that the collector process
* has no involvement in, or even access to, these structs.
*
* Each auxiliary process also maintains a PgBackendStatus struct in shared
* memory.
* ----------
*/
typedef struct PgBackendStatus
{
/*
* To avoid locking overhead, we use the following protocol: a backend
* increments st_changecount before modifying its entry, and again after
* finishing a modification. A would-be reader should note the value of
* st_changecount, copy the entry into private memory, then check
* st_changecount again. If the value hasn't changed, and if it's even,
* the copy is valid; otherwise start over. This makes updates cheap
* while reads are potentially expensive, but that's the tradeoff we want.
*
* The above protocol needs memory barriers to ensure that the apparent
* order of execution is as it desires. Otherwise, for example, the CPU
* might rearrange the code so that st_changecount is incremented twice
* before the modification on a machine with weak memory ordering. Hence,
* use the macros defined below for manipulating st_changecount, rather
* than touching it directly.
*/
int st_changecount;
/* The entry is valid iff st_procpid > 0, unused if st_procpid == 0 */
int st_procpid;
/* Type of backends */
BackendType st_backendType;
/* Times when current backend, transaction, and activity started */
TimestampTz st_proc_start_timestamp;
TimestampTz st_xact_start_timestamp;
TimestampTz st_activity_start_timestamp;
TimestampTz st_state_start_timestamp;
/* Database OID, owning user's OID, connection client address */
Oid st_databaseid;
Oid st_userid;
SockAddr st_clientaddr;
char *st_clienthostname; /* MUST be null-terminated */
/* Information about SSL connection */
bool st_ssl;
PgBackendSSLStatus *st_sslstatus;
/* Information about GSSAPI connection */
bool st_gss;
PgBackendGSSStatus *st_gssstatus;
/* current state */
BackendState st_state;
/* application name; MUST be null-terminated */
char *st_appname;
/*
* Current command string; MUST be null-terminated. Note that this string
* possibly is truncated in the middle of a multi-byte character. As
* activity strings are stored more frequently than read, that allows to
* move the cost of correct truncation to the display side. Use
* pgstat_clip_activity() to truncate correctly.
*/
char *st_activity_raw;
/*
* Command progress reporting. Any command which wishes can advertise
* that it is running by setting st_progress_command,
* st_progress_command_target, and st_progress_param[].
* st_progress_command_target should be the OID of the relation which the
* command targets (we assume there's just one, as this is meant for
* utility commands), but the meaning of each element in the
* st_progress_param array is command-specific.
*/
ProgressCommandType st_progress_command;
Oid st_progress_command_target;
int64 st_progress_param[PGSTAT_NUM_PROGRESS_PARAM];
/* query identifier, optionally computed using post_parse_analyze_hook */
uint64 st_query_id;
} PgBackendStatus;
На данном этапе мы получаем свою из массива всех структур, для отправки статистики. Так как Id бэкэндов возрастают линейно (от 1 до MAX_BACKENDS), то наша структура имеет индекс MyBackendId - 1
в этом массиве.
MyBEEntry = &BackendStatusArray[MyBackendId - 1];
Аутентификация
Дальше происходит аутентификация пользователя. Вызывается функция ClientAuthentication (src/backend/libpq/auth.c)
/*
* Client authentication starts here. If there is an error, this
* function does not return and the backend process is terminated.
*/
void
ClientAuthentication(Port *port);
Эта функция сначала проверяет валидность сертификатов пользователя (если есть) и затем вызывает функцию аутентификации для выбранного метода аутентификации.
Представление pg_hba.conf в коде
Структуры для работы с pg_hba.conf определяются в src/include/libpq/hba.h
Файл представляется в виде множества строк, которые в свою очередь представляются структурой HbaLine
typedef struct HbaLine
{
int linenumber;
char *rawline;
ConnType conntype;
List *databases;
List *roles;
struct sockaddr_storage addr;
int addrlen; /* zero if we don't have a valid addr */
struct sockaddr_storage mask;
int masklen; /* zero if we don't have a valid mask */
IPCompareMethod ip_cmp_method;
char *hostname;
UserAuth auth_method;
char *usermap;
char *pamservice;
bool pam_use_hostname;
bool ldaptls;
char *ldapscheme;
char *ldapserver;
int ldapport;
char *ldapbinddn;
char *ldapbindpasswd;
char *ldapsearchattribute;
char *ldapsearchfilter;
char *ldapbasedn;
int ldapscope;
char *ldapprefix;
char *ldapsuffix;
ClientCertMode clientcert;
ClientCertName clientcertname;
char *krb_realm;
bool include_realm;
bool compat_realm;
bool upn_username;
List *radiusservers;
char *radiusservers_s;
List *radiussecrets;
char *radiussecrets_s;
List *radiusidentifiers;
char *radiusidentifiers_s;
List *radiusports;
char *radiusports_s;
} HbaLine;
Также имеются методы доступа. Они представляются структурой UserAuth
/*
* The following enum represents the authentication methods that
* are supported by PostgreSQL.
*
* Note: keep this in sync with the UserAuthName array in hba.c.
*/
typedef enum UserAuth
{
uaReject,
uaImplicitReject, /* Not a user-visible option */
uaTrust,
uaIdent,
uaPassword,
uaMD5,
uaSCRAM,
uaGSS,
uaSSPI,
uaPAM,
uaBSD,
uaLDAP,
uaCert,
uaRADIUS,
uaPeer
#define USER_AUTH_LAST uaPeer /* Must be last value of this enum */
} UserAuth;
Все методы аутентификации представлены в документации, кроме ImplicitReject. Он представляет собой невалидный/неизвестный метод доступа (SpecialCase). Ведет себя так же как и uaReject.
// src/backend/libpq/hba.c
/*
* Scan the pre-parsed hba file, looking for a match to the port's connection
* request.
*/
static void
check_hba(hbaPort *port)
{
// ...
/* If no matching entry was found, then implicitly reject. */
hba = palloc0(sizeof(HbaLine));
hba->auth_method = uaImplicitReject;
port->hba = hba;
}
Успешная аутентификация не означает полный доступ к системе. Осталось пройти несколько фильтров:
Пользователю может быть запрещен вход
Количество подключений достигло максимума
При завершении работы БД или обновлении (pg_upgrade), доступ имеет только суперпользователь
Аутентификация могла занять некоторое время. Особенно при использовании логина/пароля. За это время БД могла исчезнуть/переименоваться. Не лишним было бы проверить это.
Первым делом получается блокировка объекта БД. Если в момент нашей аутентификации, кто-то начал DROP DATABASE
, то после получения блокировки мы об этом узнаем.
После происходит проверка прав доступа к БД. Для работой с различными правами доступа используется ACL
ACL
Для поддержки прав доступа Postgres определяет свой фреймворк ACL - Access Control List.
Каждый объект БД может иметь свой список доступа. Элемент списка представляется структурой AclItem (src/include/utils/acl.h)
typedef struct AclItem
{
Oid ai_grantee; /* ID that this item grants privs to */
Oid ai_grantor; /* grantor of privs */
AclMode ai_privs; /* privilege bits */
} AclItem;
Тип AclMode, используемый в структуре, определяется в src/include/nodes/parsenodes.h
typedef uint32 AclMode; /* a bitmask of privilege bits */
Права хранятся в виде битовой маски. Для хранения используется 32 битный int. Верхние 16 бит - для определения GRANT опций (выполнения SQL запросов), а нижние - для реального доступа.
Биты прав для SQL запросов определяются там же
#define ACL_INSERT (1<<0) /* for relations */
#define ACL_SELECT (1<<1)
#define ACL_UPDATE (1<<2)
#define ACL_DELETE (1<<3)
#define ACL_TRUNCATE (1<<4)
#define ACL_REFERENCES (1<<5)
#define ACL_TRIGGER (1<<6)
#define ACL_EXECUTE (1<<7) /* for functions */
#define ACL_USAGE (1<<8) /* for languages, namespaces, FDWs, and
* servers */
#define ACL_CREATE (1<<9) /* for namespaces and databases */
#define ACL_CREATE_TEMP (1<<10) /* for databases */
#define ACL_CONNECT (1<<11) /* for databases */
#define N_ACL_RIGHTS 12 /* 1 plus the last 1<<x */
#define ACL_NO_RIGHTS 0
/* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */
#define ACL_SELECT_FOR_UPDATE ACL_UPDATE
Например, сейчас происходит проверка возможности подключения пользователя к БД. Проверка доступа осуществляется вызовом функции
// src/include/utils/acl.h
/* result codes for pg_*_aclcheck */
typedef enum
{
ACLCHECK_OK = 0,
ACLCHECK_NO_PRIV,
ACLCHECK_NOT_OWNER
} AclResult;
// src/backend/catalog/aclchk.c
AclResult
pg_database_aclcheck(Oid db_oid, Oid roleid, AclMode mode)
{
if (pg_database_aclmask(db_oid, roleid, mode, ACLMASK_ANY) != 0)
return ACLCHECK_OK;
else
return ACLCHECK_NO_PRIV;
}
Большая часть бизнес-логики содержится в функции aclmask (src/backend/utils/adt/acl.c): определение прав доступа для переданной маски операций.
Общая логика работы выглядит следующим образом
AclMode
aclmask(const Acl *acl, Oid roleid, Oid ownerId,
AclMode mask, AclMaskHow how)
{
AclMode result;
AclMode remaining;
AclItem *aidat;
int i,
num;
// Ранний выход при отсутствии требований к правам доступа
if (mask == 0)
return 0;
result = 0;
// Владелец всегда имеет доступ
if ((mask & ACLITEM_ALL_GOPTION_BITS) &&
has_privs_of_role(roleid, ownerId))
{
result = mask & ACLITEM_ALL_GOPTION_BITS;
if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
return result;
}
num = ACL_NUM(acl);
aidat = ACL_DAT(acl);
// Проверка доступа для текущей роли
for (i = 0; i < num; i++)
{
AclItem *aidata = &aidat[i];
if (aidata->ai_grantee == ACL_ID_PUBLIC ||
aidata->ai_grantee == roleid)
{
result |= aidata->ai_privs & mask;
if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
return result;
}
}
// Проверка доступа для связных роли
remaining = mask & ~result;
for (i = 0; i < num; i++)
{
AclItem *aidata = &aidat[i];
if (aidata->ai_grantee == ACL_ID_PUBLIC ||
aidata->ai_grantee == roleid)
continue; /* already checked it */
if ((aidata->ai_privs & remaining) &&
has_privs_of_role(roleid, aidata->ai_grantee))
{
result |= aidata->ai_privs & mask;
if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
return result;
remaining = mask & ~result;
}
}
return result;
}
Обновление GUC
Дальше происходит обновление GUC конфигурации. Настройки получаются из различных источников:
Кеш (локаль и кодировка)
Аргументы точки входа
Startup пакет
Задержка
Раньше мы уже выполняли задержку перед аутентификацей (pre_auth_delay). Теперь, по логике, надо задержаться уже после. Для этого используется параметр post_auth_delay из postgresql.conf
/* Apply PostAuthDelay as soon as we've read all options */
if (PostAuthDelay > 0)
pg_usleep(PostAuthDelay * 1000000L);
Инициализация своей структуры бэкэнда
Теперь, когда мы “точно” сможем коммуницировать с клиентом, инициализируем себя для видимости другим бэкэндам и отправки своего статуса.
Ранее мы получили свою структуру PgBackendStatus, но на данный момент она не полностью инициализирована. Конечная инициализация происходит сейчас.
Одновременно с нами эту стуктуру (и массив) могут обновлять и другие бэкэнды (создание новых, завершение старых), поэтому для работы с ним нужно получить блокировку.
Для минимизации времени удержания лока сначала копируется текущая структура и работа идет уже со скопированной версией, а для записи мы ненадолго получаем лок и подменяем реальную переменную нашей.
void
pgstat_bestart(void)
{
volatile PgBackendStatus *vbeentry = MyBEEntry;
PgBackendStatus lbeentry;
// Копирование во временную переменную
memcpy(&lbeentry,
unvolatize(PgBackendStatus *, vbeentry),
sizeof(PgBackendStatus));
// Обновление временной переменной
lbeentry.st_procpid = MyProcPid;
lbeentry.st_backendType = MyBackendType;
lbeentry.st_proc_start_timestamp = MyStartTimestamp;
lbeentry.st_activity_start_timestamp = 0;
lbeentry.st_state_start_timestamp = 0;
lbeentry.st_xact_start_timestamp = 0;
lbeentry.st_databaseid = MyDatabaseId;
if (lbeentry.st_backendType == B_BACKEND
|| lbeentry.st_backendType == B_WAL_SENDER
|| lbeentry.st_backendType == B_BG_WORKER)
lbeentry.st_userid = GetSessionUserId();
else
lbeentry.st_userid = InvalidOid;
if (MyProcPort)
memcpy(&lbeentry.st_clientaddr, &MyProcPort->raddr,
sizeof(lbeentry.st_clientaddr));
else
MemSet(&lbeentry.st_clientaddr, 0, sizeof(lbeentry.st_clientaddr));
lbeentry.st_state = STATE_UNDEFINED;
lbeentry.st_progress_command = PROGRESS_COMMAND_INVALID;
lbeentry.st_progress_command_target = InvalidOid;
lbeentry.st_query_id = UINT64CONST(0);
// Входим в критическую зону
PGSTAT_BEGIN_WRITE_ACTIVITY(vbeentry);
lbeentry.st_changecount = vbeentry->st_changecount;
// Подменяем значения
memcpy(unvolatize(PgBackendStatus *, vbeentry),
&lbeentry,
sizeof(PgBackendStatus));
// ...
// Выходим из критической зоны
PGSTAT_END_WRITE_ACTIVITY(vbeentry);
// ...
}
Синхронизация настроек с клиентом
Сейчас можно начать синхронизацию с клиентом:
Настройки кастомизации: локаль, представление даты, кодировка, тайм-зона и т.д.
Общие метаданные: название приложения, является ли пользователь рутом, hot standby, версия сервера и т.д.
Все настройки, допускающие отправку клиенту, отправляются.
Представление GUC в коде
Из выше написанного видно, что настройки - не простые key-value значения. Они также имеют метаданные.
Настройки могут иметь значения с типами int
, double
, bool
, string
. Для каждого типа данных - своя структура. (src/include/utils/guc_tables.h)
/* GUC records for specific variable types */
struct config_bool
{
struct config_generic gen;
/* constant fields, must be set correctly in initial value: */
bool *variable;
bool boot_val;
GucBoolCheckHook check_hook;
GucBoolAssignHook assign_hook;
GucShowHook show_hook;
/* variable fields, initialized at runtime: */
bool reset_val;
void *reset_extra;
};
struct config_int
{
struct config_generic gen;
/* constant fields, must be set correctly in initial value: */
int *variable;
int boot_val;
int min;
int max;
GucIntCheckHook check_hook;
GucIntAssignHook assign_hook;
GucShowHook show_hook;
/* variable fields, initialized at runtime: */
int reset_val;
void *reset_extra;
};
struct config_real
{
struct config_generic gen;
/* constant fields, must be set correctly in initial value: */
double *variable;
double boot_val;
double min;
double max;
GucRealCheckHook check_hook;
GucRealAssignHook assign_hook;
GucShowHook show_hook;
/* variable fields, initialized at runtime: */
double reset_val;
void *reset_extra;
};
struct config_string
{
struct config_generic gen;
/* constant fields, must be set correctly in initial value: */
char **variable;
const char *boot_val;
GucStringCheckHook check_hook;
GucStringAssignHook assign_hook;
GucShowHook show_hook;
/* variable fields, initialized at runtime: */
char *reset_val;
void *reset_extra;
};
struct config_enum
{
struct config_generic gen;
/* constant fields, must be set correctly in initial value: */
int *variable;
int boot_val;
const struct config_enum_entry *options;
GucEnumCheckHook check_hook;
GucEnumAssignHook assign_hook;
GucShowHook show_hook;
/* variable fields, initialized at runtime: */
int reset_val;
void *reset_extra;
};
Первым полем в каждой структуре является config_generic - структура, содержащая данные, характерные для каждого типа.
struct config_generic
{
/* constant fields, must be set correctly in initial value: */
const char *name; /* name of variable - MUST BE FIRST */
GucContext context; /* context required to set the variable */
enum config_group group; /* to help organize variables by function */
const char *short_desc; /* short desc. of this variable's purpose */
const char *long_desc; /* long desc. of this variable's purpose */
int flags; /* flag bits, see guc.h */
/* variable fields, initialized at runtime: */
enum config_type vartype; /* type of variable (set only at startup) */
int status; /* status bits, see below */
GucSource source; /* source of the current actual value */
GucSource reset_source; /* source of the reset_value */
GucContext scontext; /* context that set the current value */
GucContext reset_scontext; /* context that set the reset value */
GucStack *stack; /* stacked prior values */
void *extra; /* "extra" pointer for current actual value */
char *last_reported; /* if variable is GUC_REPORT, value last sent
* to client (NULL if not yet sent) */
char *sourcefile; /* file current setting is from (NULL if not
* set in config file) */
int sourceline; /* line in source file */
};
За работу с GUC отвечает соответствующий модуль. Расположен в src/backend/utils/misc/guc.c
Для каждого типа значений, определен свой массив.
// src/backend/utils/misc/guc.c
static struct config_bool ConfigureNamesBool[];
static struct config_int ConfigureNamesInt[];
static struct config_real ConfigureNamesReal[];
static struct config_enum ConfigureNamesEnum[];
static struct config_string ConfigureNamesString[];
Для поиска среди всех настроек, существует массив guc_variables типа config_generic. Это сортированный массив всех переменных
// src/backend/utils/misc/guc.c
/*
* Actual lookup of variables is done through this single, sorted array.
*/
static struct config_generic **guc_variables;
Этот список заполняется во время инициализации вызовом build_guc_variables
void
build_guc_variables(void)
{
int size_vars;
int num_vars = 0;
struct config_generic **guc_vars;
int i;
for (i = 0; ConfigureNamesBool[i].gen.name; i++)
{
struct config_bool *conf = &ConfigureNamesBool[i];
/* Rather than requiring vartype to be filled in by hand, do this: */
conf->gen.vartype = PGC_BOOL;
num_vars++;
}
for (i = 0; ConfigureNamesInt[i].gen.name; i++)
{
struct config_int *conf = &ConfigureNamesInt[i];
conf->gen.vartype = PGC_INT;
num_vars++;
}
for (i = 0; ConfigureNamesReal[i].gen.name; i++)
{
struct config_real *conf = &ConfigureNamesReal[i];
conf->gen.vartype = PGC_REAL;
num_vars++;
}
for (i = 0; ConfigureNamesString[i].gen.name; i++)
{
struct config_string *conf = &ConfigureNamesString[i];
conf->gen.vartype = PGC_STRING;
num_vars++;
}
for (i = 0; ConfigureNamesEnum[i].gen.name; i++)
{
struct config_enum *conf = &ConfigureNamesEnum[i];
conf->gen.vartype = PGC_ENUM;
num_vars++;
}
/*
* Create table with 20% slack
*/
size_vars = num_vars + num_vars / 4;
guc_vars = (struct config_generic **)
guc_malloc(FATAL, size_vars * sizeof(struct config_generic *));
num_vars = 0;
for (i = 0; ConfigureNamesBool[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesBool[i].gen;
for (i = 0; ConfigureNamesInt[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesInt[i].gen;
for (i = 0; ConfigureNamesReal[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesReal[i].gen;
for (i = 0; ConfigureNamesString[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesString[i].gen;
for (i = 0; ConfigureNamesEnum[i].gen.name; i++)
guc_vars[num_vars++] = &ConfigureNamesEnum[i].gen;
if (guc_variables)
free(guc_variables);
guc_variables = guc_vars;
num_guc_variables = num_vars;
size_guc_variables = size_vars;
qsort((void *) guc_variables, num_guc_variables,
sizeof(struct config_generic *), guc_var_compare);
}
Выше было сказано, что отправляются только допускающие отправку опции. В config_generic определяется поле flags - битовая маска метаданных опции. Определяются флаги в src/include/utils/guc.h
/*
* bit values in "flags" of a GUC variable
*/
#define GUC_LIST_INPUT 0x0001 /* input can be list format */
#define GUC_LIST_QUOTE 0x0002 /* double-quote list elements */
#define GUC_NO_SHOW_ALL 0x0004 /* exclude from SHOW ALL */
#define GUC_NO_RESET_ALL 0x0008 /* exclude from RESET ALL */
#define GUC_REPORT 0x0010 /* auto-report changes to client */
#define GUC_NOT_IN_SAMPLE 0x0020 /* not in postgresql.conf.sample */
#define GUC_DISALLOW_IN_FILE 0x0040 /* can't set in postgresql.conf */
#define GUC_CUSTOM_PLACEHOLDER 0x0080 /* placeholder for custom variable */
#define GUC_SUPERUSER_ONLY 0x0100 /* show only to superusers */
#define GUC_IS_NAME 0x0200 /* limit string to NAMEDATALEN-1 */
#define GUC_NOT_WHILE_SEC_REST 0x0400 /* can't set if security restricted */
#define GUC_DISALLOW_IN_AUTO_FILE 0x0800 /* can't set in
* PG_AUTOCONF_FILENAME */
За возможность отправки клиенту отвечает флаг GUC_REPORT. Для синхронизации GUC с клиентом вызывается функция BeginReportingGUCOptions, которая его проверяет.
// src/backend/utils/misc/guc.c
/*
* Start up automatic reporting of changes to variables marked GUC_REPORT.
* This is executed at completion of backend startup.
*/
void
BeginReportingGUCOptions(void)
{
int i;
// ...
/* Transmit initial values of interesting variables */
for (i = 0; i < num_guc_variables; i++)
{
struct config_generic *conf = guc_variables[i];
if (conf->flags & GUC_REPORT)
ReportGUCOption(conf);
}
// ...
}
Дополнительно клиенту передается секретный ключ (пара): ID процесса и токен отмены. По этой паре можно будет запрашивать отмену выполнения запроса.
С этого момента клиент ждет ReadyForQuery пакета, который будет отослан уже в главном цикле.
Загрузка библиотек
Теперь загружаются библиотеки
Загружаемые библиотеки указываются в переменных local_preload_libraries, shared_preload_libraries и session_preload_libraries в postgresql.conf
Сейчас загружаются session_preload_libraries и local_preload_libraries.
Динамическая загрузка и модуль dfmgr
Для загрузки библиотек Postgres использует динамическую загрузку библиотек.
Для работы с динамическими функциями/библиотеками используется модуль dfmgr
Он определяет интерфейс для взаимодействия с функциями, определяемыми во внешних библиотеках
Функция, которую необходимо вызвать идентфицируется структурой FunctionCallInfoBaseData (src/include/fmgr.h)
typedef struct FunctionCallInfoBaseData
{
FmgrInfo *flinfo; /* ptr to lookup info used for this call */
fmNodePtr context; /* pass info about context of call */
fmNodePtr resultinfo; /* pass or return extra info about result */
Oid fncollation; /* collation for function to use */
#define FIELDNO_FUNCTIONCALLINFODATA_ISNULL 4
bool isnull; /* function must set true if result is NULL */
short nargs; /* # arguments actually passed */
#define FIELDNO_FUNCTIONCALLINFODATA_ARGS 6
NullableDatum args[FLEXIBLE_ARRAY_MEMBER];
} FunctionCallInfoBaseData;
typedef struct FunctionCallInfoBaseData *FunctionCallInfo;
Каждая вызываемая функция имеет следующую сигнатуру
typedef Datum (*PGFunction) (FunctionCallInfo fcinfo);
Для работы с внешними библиотеками используются функции
void *load_external_function(const char *filename, const char *funcname,
bool signalNotFound, void **filehandle);
void *lookup_external_function(void *filehandle, const char *funcname);
void load_file(const char *filename, bool restricted);
void **find_rendezvous_variable(const char *varName);
Size EstimateLibraryStateSpace(void);
void SerializeLibraryState(Size maxsize, char *start_address);
void RestoreLibraryState(char *start_address);
Для загрузки библиотек используется функция load_file. Она сначала выгружает (если уже была загружена), а затем загружает файл обратно. Для самой загрузки используется функция internal_load_library (src/backend/utils/fmgr/dfmgr.c)
/*
* Load the specified dynamic-link library file, unless it already is
* loaded. Return the pg_dl* handle for the file.
*
* Note: libname is expected to be an exact name for the library file.
*/
void *internal_load_library(const char *libname);
Работа этой функции заключается в следующем:
Проверить, что файл не был загружен ранее
Если был загружен, то вернуть ссылку на хранящуюся библиотеку
Аллоцировать память под структуру загруженного файла
Открыть файл библиотеки системным вызовом dlopen
Получить "магическую функцию". Она возвращает структуру
Pg_magic_struct
и используется для проверки совместимости по версиямВызывать функцию _PG_init, если присутствует
Сохранить загруженную библиотеку в памяти
Вернуть указатель на эту библиотеку
Инициализация контекста памяти
Последнее, что осталось - аллокация памяти для входящего запроса.
Память для него хранится в 2 переменных:
MessageContext - контекст текущего запроса (Глобальный контекст)
row_description_context - контекст памяти, хранящий в себе описание строк при ответе клиенту. Например, название столбца, тип данных столбца (object ID) или модификаторы типа. (Только в файле с точкой входа src/backend/tcop/postgres.c)
Конец
На этом инициализация Postgres заканчивается и дальше идет главный цикл