Приветствую!
Продолжаем изучение работы 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 заканчивается и дальше идет главный цикл.
