Приветствую!
Продолжаем изучение работы 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) { intfirstchar; StringInfoData input_message; sigjmp_buflocal_sigjmp_buf; volatile bool send_ready_for_query = true; boolidle_in_transaction_timeout_enabled = false; boolidle_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) { intsave_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 { intfd;/* current FD, or VFD_CLOSED if none */ unsigned short fdstate;/* bitflags for VFD's state */ ResourceOwner resowner;/* owner, for automatic cleanup */ FilenextFree;/* link to next free VFD, if in freelist */ FilelruMoreRecently;/* doubly linked recency-of-use list */ FilelruLessRecently; off_tfileSize;/* 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 */ intfileFlags;/* open(2) flags for (re)opening the file */ mode_tfileMode;/* 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; Filefile; 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) { intsave_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 */ intBasicOpenFile(const char *fileName, int fileFlags); intBasicOpenFilePerm(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 { int16handler;/* SyncRequestHandler value, saving space */ int16forknum;/* ForkNumber, saving space */ RelFileNode rnode; uint32segno; } FileTag; // src/include/storage/relfilenode.h typedef struct RelFileNode { OidspcNode;/* tablespace */ OiddbNode;/* database */ OidrelNode;/* 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. */ intsmgr_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). */ intmd_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_nodenode; } 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 InvalidBuffer0
Сами операции с буфером объявляются в 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 { Bufferbuffer; int32refcount; } 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 */ uint32allProcCount; /* 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) */ boolisshared;/* true if table is in shared memory */ boolisfixed;/* if true, don't enlarge */ /* freezing a shared table isn't allowed, so we can keep state here */ boolfrozen;/* true = no more inserts allowed */ /* We keep local copies of these fixed values to reduce contention */ Sizekeysize;/* hash key length in bytes */ longssize;/* segment size --- must be power of 2 */ intsshift;/* 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) */ uint64size; /* how many elements have valid contents */ uint32members; /* mask for bucket and size calculations, based on size */ uint32sizemask; /* boundary after which to grow hashtable */ uint32grow_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_SCOPESH_TYPE *SH_CREATE(uint32 nelements, void *private_data); #else /* * <prefix>_hash <prefix>_create(MemoryContext ctx, uint32 nelements, * void *private_data) */ SH_SCOPESH_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) */ CommandTagcommandTag;/* 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 */ intcursorOptions;/* DECLARE CURSOR option bits */ boolrun_once;/* portal will only be run once */ /* Status data */ PortalStatus status;/* see above */ boolportalPinned;/* a pinned portal can't be dropped */ boolautoHeld;/* 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: */ TupleDesctupDesc;/* 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. */ SnapshotportalSnapshot; /* 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. */ SnapshotholdSnapshot;/* 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. */ boolatStart; boolatEnd; uint64portalPos; /* Presentation data, primarily used by the pg_cursors system view */ TimestampTz creation_time;/* time at which this portal was defined */ boolvisible;/* include this portal in pg_cursors? */ /* Stuff added at the end to avoid ABI break in stable branches: */ intcreateLevel;/* 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. */ intst_changecount; /* The entry is valid iff st_procpid > 0, unused if st_procpid == 0 */ intst_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 */ Oidst_databaseid; Oidst_userid; SockAddrst_clientaddr; char *st_clienthostname;/* MUST be null-terminated */ /* Information about SSL connection */ boolst_ssl; PgBackendSSLStatus *st_sslstatus; /* Information about GSSAPI connection */ boolst_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; Oidst_progress_command_target; int64st_progress_param[PGSTAT_NUM_PROGRESS_PARAM]; /* query identifier, optionally computed using post_parse_analyze_hook */ uint64st_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 { intlinenumber; char *rawline; ConnTypeconntype; List *databases; List *roles; struct sockaddr_storage addr; intaddrlen;/* zero if we don't have a valid addr */ struct sockaddr_storage mask; intmasklen;/* zero if we don't have a valid mask */ IPCompareMethod ip_cmp_method; char *hostname; UserAuthauth_method; char *usermap; char *pamservice; boolpam_use_hostname; boolldaptls; char *ldapscheme; char *ldapserver; intldapport; char *ldapbinddn; char *ldapbindpasswd; char *ldapsearchattribute; char *ldapsearchfilter; char *ldapbasedn; intldapscope; char *ldapprefix; char *ldapsuffix; ClientCertMode clientcert; ClientCertName clientcertname; char *krb_realm; boolinclude_realm; boolcompat_realm; boolupn_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 { Oidai_grantee;/* ID that this item grants privs to */ Oidai_grantor;/* grantor of privs */ AclModeai_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_RIGHTS12/* 1 plus the last 1<<x */ #define ACL_NO_RIGHTS0 /* Currently, SELECT ... FOR [KEY] UPDATE/SHARE requires UPDATE privileges */ #define ACL_SELECT_FOR_UPDATEACL_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) { AclModeresult; AclModeremaining; AclItem *aidat; inti, 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; boolboot_val; GucBoolCheckHook check_hook; GucBoolAssignHook assign_hook; GucShowHook show_hook; /* variable fields, initialized at runtime: */ boolreset_val; void *reset_extra; }; struct config_int { struct config_generic gen; /* constant fields, must be set correctly in initial value: */ int *variable; intboot_val; intmin; intmax; GucIntCheckHook check_hook; GucIntAssignHook assign_hook; GucShowHook show_hook; /* variable fields, initialized at runtime: */ intreset_val; void *reset_extra; }; struct config_real { struct config_generic gen; /* constant fields, must be set correctly in initial value: */ double *variable; doubleboot_val; doublemin; doublemax; GucRealCheckHook check_hook; GucRealAssignHook assign_hook; GucShowHook show_hook; /* variable fields, initialized at runtime: */ doublereset_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; intboot_val; const struct config_enum_entry *options; GucEnumCheckHook check_hook; GucEnumAssignHook assign_hook; GucShowHook show_hook; /* variable fields, initialized at runtime: */ intreset_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 */ GucContextcontext;/* 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 */ intflags;/* flag bits, see guc.h */ /* variable fields, initialized at runtime: */ enum config_type vartype;/* type of variable (set only at startup) */ intstatus;/* status bits, see below */ GucSourcesource;/* source of the current actual value */ GucSourcereset_source;/* source of the reset_value */ GucContextscontext;/* context that set the current value */ GucContextreset_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) */ intsourceline;/* 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) { intsize_vars; intnum_vars = 0; struct config_generic **guc_vars; inti; 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_INPUT0x0001/* input can be list format */ #define GUC_LIST_QUOTE0x0002/* double-quote list elements */ #define GUC_NO_SHOW_ALL0x0004/* exclude from SHOW ALL */ #define GUC_NO_RESET_ALL0x0008/* exclude from RESET ALL */ #define GUC_REPORT0x0010/* auto-report changes to client */ #define GUC_NOT_IN_SAMPLE0x0020/* not in postgresql.conf.sample */ #define GUC_DISALLOW_IN_FILE0x0040/* can't set in postgresql.conf */ #define GUC_CUSTOM_PLACEHOLDER0x0080/* placeholder for custom variable */ #define GUC_SUPERUSER_ONLY0x0100/* show only to superusers */ #define GUC_IS_NAME0x0200/* limit string to NAMEDATALEN-1 */ #define GUC_NOT_WHILE_SEC_REST0x0400/* 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) { inti; // ... /* 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 */ fmNodePtrcontext;/* pass info about context of call */ fmNodePtrresultinfo;/* pass or return extra info about result */ Oidfncollation;/* collation for function to use */ #define FIELDNO_FUNCTIONCALLINFODATA_ISNULL 4 boolisnull;/* function must set true if result is NULL */ shortnargs;/* # 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 заканчивается и дальше идет главный цикл.
ссылка на оригинал статьи https://habr.com/ru/post/709404/
Добавить комментарий