pg的服务进程在操作系统中的名字都叫postgres,pg在多用户模式下启动后,用pidof postgres命令可以看到至少6个进程id。
但其实,pg的进程结构是:
单用户模式下只有1个进程postmaster
多用户模式下,有一个postmaster进程,它会启动几个辅助进程,如:
SysLogger(负责系统日志)
PgStat(负责收集数据的统计信息)
AutoVacuum(系统自动清理)
BgWriter(后台写进程)
WalWriter(预写式日志进程)
PgArch(预写式日志归档进程)
当每个客户端连接pg时,会先和postmaster请求分配一个postgres服务进程,分配服务进程后,客户端就只和服务进程交互了。postmaster只负责监听系统的状态,将必要的任务交给辅助进程来做。
其实postmaster和postgres都是从postgres程序装载的,只是执行的分支不一样。在linux上,pg安装目录下bin/postmaster只是bin/postgres的一个软连接,在windows上是一个拷贝。
众所周知,这种1:1的进程结构(一个客户端对应一个服务进程)比多线程的结构开销要大。进程的创建和回收比线程更加消耗资源。但是多进程的一个好处就是:系统的各个模块、各个服务进程之间耦合度更低,不会因为某一个后台进程崩溃导致系统的崩溃。在高性能的服务器上,通常几十个进程的开销并不会明显影响到性能,记得有人测试过,超过200个客户端连接到pg后同时执行查询,pg的性能会有明显的下降,其实这也不奇怪。200个用户的并发对事务管理系统和存储管理系统的压力也会导致性能下降,相信200个进程的创建开销并不是主要的。总而言之,相信pg的进程结构还是比较适合目前硬件条件下的数据库系统的。
下面就来跟一下pg启动的代码:
pg启动时,我设置的命令行参数是:
-D [数据集簇的路径]
pg的主函数入口在src/backend/main/main.c中的main函数。
执行的流程如下:
【1】从argv[0]中取出postgres程序的文件名,通过这个文件名,pg可以知道自己是从哪个程序装载进来的:
progname = get_progname(argv[0]);
【2】将argv和环境变量复制一份,并将原来的argv和environ指针覆盖:
argv = save_ps_display_args(argc, argv);
【3】设置locale和服务目录:
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("postgres"));
【4】检查argv[1]中是否是--version、-?、--help、-V等参数,若是,则打印版本或者帮助,不启动pg服务直接exit(0):
if (argc > 1) { if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) { help(progname); exit(0); } if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) { puts("postgres (PostgreSQL) " PG_VERSION); exit(0); } }
【5】检查当前执行postgres程序的系统用户是否是root:
check_root(progname);
【6】检查argv[1],如果是--boot,则通过src/backend/bootstrap/bootstrap.c中的AuxiliaryProcessMain函数启动初始化数据集簇的bootstrap进程,这可以在白手起家创建系统库。initdb其实也只走的这个分支。如果是--describe-config则打印配置信息。如果是--single则启动单用户模式。缺省情况下,pg会在这里调用src/backend/postmaster/postmaster.c的PostMasterMain函数,以多用户模式启动:
if (argc > 1 && strcmp(argv[1], "--boot") == 0) AuxiliaryProcessMain(argc, argv); /* does not return */ else if (argc > 1 && strcmp(argv[1], "--describe-config") == 0) GucInfoMain(); /* does not return */ else if (argc > 1 && strcmp(argv[1], "--single") == 0) PostgresMain(argc, argv, NULL, /* no dbname */ get_current_username(progname)); /* does not return */ else PostmasterMain(argc, argv); /* does not return */ abort(); /* should not get here */
进入PostMasterMain跟踪调试。
发现这里先是拿到当前的进程pid和时间,设置当前进程所创建的文件的umask,然后就是初始化内存上下文。在pg7.1中有了内存上下文管理。这个内存上下文在pg中是全局的,各个子进程都可以访问到。所有的内存空间都从上下文中申请和释放,这样就避免了内存泄漏。
接下来是解析命令行参数、加载配置、检查数据集簇的路径、创建数据集簇中的锁文件、创建套接字监听、注册信号处理函数、启动辅助进程(Syslogger、Pgstat、AutoVacuum、bgwriter)、装载用户认证文件(hba和ident文件)等等。
最后执行ServerLoop函数,进入监听循环,等待客户端的请求。