Linux 定时器应用【转】
Linux 定时器应用
实验目的
阅读 Linux 相关源代码,学习 Linux 系统中的时钟和定时器原理,即,ITIMER_REAL
实时计数,ITIMER_VIRTUAL 统计进程在用户模式执行的时间,ITIMER_PROF 统计进程
在用户模式和核心模式下的执行时间。
理解这些定时器的实现机制。
掌握操作定时器的命令,掌握定时器的使用。
实验内容
ITIMER_REAL 实时计数;ITIMER_VIRTUAL 统计进程在用户模式(进程本身执行)
执行的时间;ITIMER_PROF 统计进程在用户模式(进程本身执行)和核心模式(系统代表
进程执行)下的执行时间,与 ITIMER_VIRTUAL 比较,这个计时器记录的时间多了该进程
核心模式执行过程中消耗的时间。
针对一个计算 fibonacci 数的进程,设定三个定时器,获取该进程在用户模式的运行时
间,在核心模式的运行时间,以及总的运行时间。
实验提示
一、一个应用定时器的简单例子
我们首先来看一个关于 ITIMER_REAL 定时器的例子。在这个例子里面我们将会设置
一个 ITIMER_REAL 类型的定时器,它每过一秒都会发出一个信号,等到定时到达的时候
(即定时器时间值减到 0),程序将统计已经经过的时间。下面是具体的代码:
/*我们在使用 signal 和时钟相关的结构体之前,需要包含这两个头文件*/
#include <signal.h>
#include <sys/time.h>
/*声明信号处理函数,信号相关内容将在第八章同步机制中讲述,读者在这里只要明白这个函数是
在进程收到信号的时候调用就可以了*/
static void sig_handler(int signo);
long lastsec,countsec; /*这两个变量分别用来保存上一秒的时间和总共花去的时间*/
int main(void)
{
struct itimerval v; /*定时器结构体,结构体内容请参阅第三节中的介绍*/
long nowsec,nowusec; /*当前时间的秒数和微秒数*/ /*注册 SIGUSR1 和 SIGALARM 信号的处理函数为 sig_handler*/
if(signal(SIGUSR1,sig_handler)==SIG_ERR)
{
printf("Unable to create handler for SIGUSR1\n");
exit(0);
}
if(signal(SIGALRM,sig_handler)==SIG_ERR)
{
printf("Unable to create handler for SIGALRM\n");
exit(0);
}
/*初始化定时器初值和当前值*/
v.it_interval.tv_sec=9;
v.it_interval.tv_usec=999999;
v.it_value.tv_sec=9;
v.it_value.tv_usec=999999;
/*调用 setitimer 设置定时器,并将其挂到定时器链表上,这个函数的三个参数的含义分
别是设置 ITIMER_REAL 类型的定时器,要设置的值存放在变量 v 中,该定时器设置前
的值在设置后保存的地址,如果是这个参数为 NULL,那么就放弃保存设置前的值*/
setitimer(ITIMER_REAL,&v,NULL);
lastsec=v.it_value.tv_sec;
countsec=0;
/*该循环首先调用 getitimer 读取定时器当前值,再与原来的秒数比较,当发现已经过了
一秒后产生一个 SIGUSR1 信号,程序就会进入上面注册过的信号处理函数*/
while(1)
{
getitimer(ITIMER_REAL,&v);
nowsec=v.it_value.tv_sec;
nowusec=v.it_value.tv_usec;
if(nowsec==lastsec-1)
{
/*每过一秒,产生一个 SIGUSR1 信号*/
raise(SIGUSR1);
lastsec=nowsec;
countsec++; /*记录总的秒数*/
}
}
}
/*信号处理函数*/
static void sig_handler(int signo)
{
switch(signo)
{
/*接收到的信号是 SIGUSR1,打印 One second passed*/
case SIGUSR1: printf("One second passed\n");
break;
/*定时器定时到达*/
case SIGALRM:
{
printf("Timer has been zero,elapsed %d seconds\n",countsec);
lastsec=countsec;
countsec=0;
break;
}
}
}
上面的程序比较简单,主要是给大家看一下定时器的设置和读取的方法。下面我们将上
面的程序稍作修改,利用本章第一节介绍过的与进程相关的三种定时器来统计一个进程的用
户模式时间、核心模式时间、CPU 时间和总的进程执行时间。
二、统计关于进程的时间
我们在第一节介绍过和 Linux 进程相关的定时器有三种。ITIMER_REAL 实时计数;
ITIMER_VIRTUAL 统计进程在用户模式(进程本身执行)执行的时间;ITIMER_PROF 统
计进程在用户模式(进程本身执行)和核心模式(系统代表进程执行)下的执行时间,与
ITIMER_VIRTUAL 比较,这个计时器记录的时间多了该进程核心模式执行过程中消耗的时
间。通过在一个进程中设定这三个定时器,我们就可以了解到一个进程在用户模式、核心模
式以及总的运行时间。下面的这个程序除了定义了三个定时器和信号处理过程以外,其它的
地方和上面的程序完全相同。
#include <signal.h>
#include <sys/time.h>
static void sig_handler(int signo);
long countsec,lastsec,nowsec;
int main(void)
{
struct itimerval v;
/*注册信号处理函数*/
if(signal(SIGUSR1,sig_handler)==SIG_ERR)
{
printf("Unable to create handler for SIGUSR1\n");
exit(0);
}
if(signal(SIGALRM,sig_handler)==SIG_ERR)
{
printf("Unable to create handler for SIGALRM\n");
exit(0);
}
v.it_interval.tv_sec=10;
v.it_interval.tv_usec=0;
v.it_value.tv_sec=10;
v.it_value.tv_usec=0;
/*调用 setitimer 设置定时器,并将其挂到定时器链表上,这个函数的三个参数的含义分
别是设置何种类型的定时器;要设置的值存放在变量 v 中;该定时器设置前的值在设置
后保存的地址,如果是这个参数为 NULL,那么就放弃保存设置前的值*/
setitimer(ITIMER_REAL,&v,NULL);
setitimer(ITIMER_VIRTUAL,&v,NULL);
setitimer(ITIMER_PROF,&v,NULL);
countsec=0;
lastsec=v.it_value.tv_sec;
while(1)
{
getitimer(ITIMER_REAL,&v);
nowsec=v.it_value.tv_sec;
if(nowsec==lastsec-1)
{
if(nowsec<9)
{
/*同上面一样,我们每隔一秒发送一个 SIGUSR1 信号*/
raise(SIGUSR1);
countsec++;
}
lastsec=nowsec;
}
}
}
static void sig_handler(int signo)
{
struct itimerval u,v;
long t1,t2;
switch(signo)
{
case SIGUSR1:
/*显示三个定时器的当前值*/
getitimer(ITIMER_REAL,&v);
printf("real time=%.ld secs %ld
usecs\n",9-v.it_value.tv_sec,999999-v.it_value.tv_usec);
getitimer(ITIMER_PROF,&u);
printf("cpu time=%ld secs %ld
usecs\n",9-u.it_value.tv_sec,999999-u.it_value.tv_usec);
getitimer(ITIMER_VIRTUAL,&v);
printf("user time=%ld secs %ld
usecs\n",9-v.it_value.tv_sec,999999-v.it_value.tv_usec); /*当前 prof timer 已经走过的微秒数*/
t1=(9-u.it_value.tv_sec)*1000000+(1000000-u.it_value.tv_usec);
/*当前 virtual timer 已经走过的微秒数*/
t2=(9-v.it_value.tv_sec)*1000000+(1000000-v.it_value.tv_usec);
/*计算并显示 kernel time*/
printf("kernel time=%ld secs %ld
usecs\n\n",(t1-t2)/1000000,(t1-t2)%1000000);
break;
case SIGALRM:
printf("Real Timer has been zero,elapsed %d seconds\n",countsec);
exit(0);
break;
}
}
从上面的程序可以看出来,ITIMER_REAL 定时器运行的时间就是总运行时间,
ITIMER_PROF 定时器的运行时间就是 CPU 花在该进程上的所有时间。ITIMER_VIRTUAL
定时器运行的时间是进程在用户模式的运行时间。ITIMER_PROF 定时器的运行时间减去
ITIMER_VIRTUAL 定时器的运行时间就是进程在核心模式的运行时间。
三、更进一步的进程时间统计
上面的程序只在很短的时间内统计了进程在各种状态的执行时间。但是进程并没有真正
的负载作业,和现实中的进程差距比较大。下面我们要继续修改上面的程序,让这个进程做
点“事情”,然后我们再来看看在和实际情况比较相近的状态下定时器统计到的进程在各个
状态下的时间。在这个程序里面我们将创建两个子进程,加上父进程总共三个进程,这三个
进程分别调用 fibonacci()计算 fibonacci 数。在计算之前我们初始化定时器,完成之后,我们
将读取定时器,然后来统计进程相关的各种时间。
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
long unsigned int fibonacci(unsigned int n); /*计算 fibonacci 数的函数*/
static void par_sig(int signo); /*父进程的信号处理函数*/
static void c1_sig(int signo); /*子进程 1 的信号处理函数*/
static void c2_sig(int signo); /*子进程 2 的信号处理函数*/
/*用于分别记录父,子 1,子 2 进程 real time 过的总秒数*/
static long p_realt_secs=0,c1_realt_secs=0,c2_realt_secs=0;
/*用于分别记录父,子 1,子 2 进程 virtual time 过的总秒数*/
static long p_virtt_secs=0, c1_virtt_secs=0,c2_virtt_secs=0;
/*用于分别记录父,子 1,子 2 进程 proft time 过的总秒数*/
static long p_proft_secs=0,c1_proft_secs=0,c2_proft_secs=0;
/*用于分别取出父,子 1,子 2 进程的 real timer 的值*/
static struct itimerval p_realt,c1_realt,c2_realt;
/*用于分别取出父,子 1,子 2 进程的 virtual timer 的值*/ static struct itimerval p_virtt,c1_virtt,c2_virtt;
/*用于分别取出父,子 1,子 2 进程的 proft timer 的值*/
static struct itimerval p_proft,c1_proft,c2_proft;
int main()
{
long unsigned fib=0;
int pid1,pid2;
unsigned int fibarg=39;
int status;
struct itimerval v;
long moresec,moremsec,t1,t2;
pid1=fork();
if(pid1==0)
{
/*设置子进程 1 的信号处理函数和定时器初值*/
signal(SIGALRM,c1_sig);
signal(SIGVTALRM,c1_sig);
signal(SIGPROF,c1_sig);
v.it_interval.tv_sec=10;
v.it_interval.tv_usec=0;
v.it_value.tv_sec=10;
v.it_value.tv_usec=0;
setitimer(ITIMER_REAL,&v,NULL);
setitimer(ITIMER_VIRTUAL,&v,NULL);
setitimer(ITIMER_PROF,&v,NULL);
fib=fibonacci(fibarg); /*计算 fibonacci 数*/
/*取出子进程 1 的定时器值*/
getitimer(ITIMER_PROF,&c1_proft);
getitimer(ITIMER_REAL,&c1_realt);
getitimer(ITIMER_VIRTUAL,&c1_virtt);
/*通过定时器的当前值和各信号发出的次数计算子进程 1 总共用的 real time,cpu
time,user time 和 kernel time。moresec 和 moremsec 指根据定时器的当前值计算
出的自上次信号发出时过去的 real time,cpu time,user time 和 kernel time。计算
kernel time 时,moresec 和 moremsec 为 kernel time 的实际秒数+毫秒数*/
moresec=9-c1_realt.it_value.tv_sec;
moremsec= (1000000-c1_realt.it_value.tv_usec)/1000;
printf("Child 1 fib=%ld , real time=%ld sec,%ld
msec\n",fib,c1_realt_secs+moresec,moremsec);
moresec=9-c1_proft.it_value.tv_sec;
moremsec=(1000000-c1_proft.it_value.tv_usec)/1000;
printf("Child 1 fib=%ld , cpu time=%ld sec,%ld
msec\n",fib,c1_proft_secs+moresec,moremsec);
moresec=9-c1_virtt.it_value.tv_sec;
moremsec=(1000000-c1_virtt.it_value.tv_usec)/1000;
printf("Child 1 fib=%ld , user time=%ld sec,%ld
msec\n",fib,c1_virtt_secs+moresec,moremsec);
t1=(9-c1_proft.it_value.tv_sec)*1000+(1000000-c1_proft.it_value.tv_usec)/1000+c1_proft_secs*10000;
t2=(9-c1_virtt.it_value.tv_sec)*1000+(1000000-c1_virtt.it_value.tv_usec)/1000+c1_v
i rtt_secs*10000;
moresec=(t1-t2)/1000;moremsec=(t1-t2)%1000;
printf("Child 1 fib=%ld , kernel time=%ld sec,%ld msec\n",fib,moresec,moremsec);
fflush(stdout);
exit(0);
}
else
{
pid2=fork();
if(pid2==0)
{
/*设置子进程 2 的信号处理函数和定时器初值*/
signal(SIGALRM,c2_sig);
signal(SIGVTALRM,c2_sig);
signal(SIGPROF,c2_sig);
v.it_interval.tv_sec=10;
v.it_interval.tv_usec=0;
v.it_value.tv_sec=10;
v.it_value.tv_usec=0;
setitimer(ITIMER_REAL,&v,NULL);
setitimer(ITIMER_VIRTUAL,&v,NULL);
setitimer(ITIMER_PROF,&v,NULL);
fib=fibonacci(fibarg);
/*取出子进程 2 的定时器值*/
getitimer(ITIMER_PROF,&c2_proft);
getitimer(ITIMER_REAL,&c2_realt);
getitimer(ITIMER_VIRTUAL,&c2_virtt);
/*通过定时器的当前值和各信号发出的次数计算子进程 2 总共用的 real
time,cpu time,user time 和 kernel time。moresec 和 moremsec 指根据定时
器的当前值计算出的自上次信号发出时过去的 real time,cpu time,user
time 和 kernel time。计算 kernel time 时,moresec 和 moremsec 为 kernel
time 的实际秒数+毫秒数*/
moresec=9-c2_realt.it_value.tv_sec;
moremsec=(1000000-c2_realt.it_value.tv_usec)/1000;
printf("Child 2 fib=%ld , real time=%ld sec,%ld
msec\n",fib,c2_realt_secs+moresec,moremsec);
moresec=9-c2_proft.it_value.tv_sec;
moremsec=(1000000-c2_proft.it_value.tv_usec)/1000;
printf("Child 2 fib=%ld , cpu time=%ld sec,%ld
msec\n",fib,c2_proft_secs+moresec,moremsec);
moresec=9-c2_virtt.it_value.tv_sec;
moremsec=(1000000-c2_virtt.it_value.tv_usec)/1000;
printf("Child 2 fib=%ld , user time=%ld sec,%ld
msec\n",fib,c2_virtt_secs+moresec,moremsec);
t1=(9-c2_proft.it_value.tv_sec)*1000+(1000000-c2_proft.it_value.tv_usec)/
1000+c2_proft_secs*10000;
t2=(9-c2_virtt.it_value.tv_sec)*1000+(1000000-c2_virtt.it_value.tv_usec)/1
000+c2_virtt_secs*10000;
moresec=(t1-t2)/1000;
moremsec=(t1-t2)%1000;
printf("Child 2 fib=%ld , kernel time=%ld sec,%ld
msec\n",fib,moresec,moremsec);
fflush(stdout);
exit(0);
}
else
{
/*设置父进程的信号处理函数和定时器初值*/
signal(SIGALRM,par_sig);
signal(SIGVTALRM,par_sig);
signal(SIGPROF,par_sig);
v.it_interval.tv_sec=10;
v.it_interval.tv_usec=0;
v.it_value.tv_sec=10;
v.it_value.tv_usec=0;
setitimer(ITIMER_REAL,&v,NULL);
setitimer(ITIMER_VIRTUAL,&v,NULL);
setitimer(ITIMER_PROF,&v,NULL);
fib=fibonacci(fibarg);
/*取出父进程的定时器值*/
getitimer(ITIMER_PROF,&p_proft);
getitimer(ITIMER_REAL,&p_realt);
getitimer(ITIMER_VIRTUAL,&p_virtt);
/*通过定时器的当前值和各信号发出的次数计算子进程 1 总共用的
real time,cpu time,user time 和 kernel time。moresec 和 moremsec 指根据
定时器的当前值计算出的自上次信号发出时过去的 real time,cpu
time,user time 和 kernel time。计算 kernel time 时,moresec 和 moremsec
为 kernel time 的实际秒数+毫秒数*/
moresec=9-p_realt.it_value.tv_sec;
moremsec=(1000000-p_realt.it_value.tv_usec)/1000;
printf("Parent fib=%ld , real time=%ld sec,%ld
msec\n",fib,p_realt_secs+moresec,moremsec);
moresec=9-p_proft.it_value.tv_sec;
moremsec=(1000000-p_proft.it_value.tv_usec)/1000;
printf("Parent fib=%ld , cpu time=%ld sec,%ld
msec\n",fib,p_proft_secs+moresec,moremsec);
moresec=9-p_virtt.it_value.tv_sec;
moremsec=(1000000-p_virtt.it_value.tv_usec)/1000;
printf("Parent fib=%ld , user time=%ld sec,%ld
msec\n",fib,p_virtt_secs+moresec,moremsec);
t1=(9-p_proft.it_value.tv_sec)*1000+(1000000-p_proft.it_value.tv_usec)/1
000+p_proft_secs*10000;
7t2=(9-p_virtt.it_value.tv_sec)*1000+(1000000-p_virtt.it_value.tv_usec)/10
00+p_virtt_secs*10000;
moresec=(t1-t2)/1000;
moremsec=(t1-t2)%1000;
printf("Parent fib=%ld , kernel time=%ld sec,%ld
msec\n",fib,moresec,moremsec);
fflush(stdout);
waitpid(0,&status,0);
waitpid(0,&status,0);
exit(0);
}
printf("this line should never be printed\n");
}
}
long unsigned fibonacci(unsigned int n)
{
if(n==0)
return 0;
else if(n==1||n==2)
return 1;
else
return (fibonacci(n-1)+fibonacci(n-2));
}
/*父进程信号处理函数;每个 timer 过 10 秒减为 0,激活处理函数一次,相应的计数器加 10*/
static void par_sig(int signo)
{
switch(signo)
{
case SIGALRM:
p_realt_secs+=10;
break;
case SIGVTALRM:
p_virtt_secs+=10;
break;
case SIGPROF:
p_proft_secs+=10;
break;
}
}
/*子进程 1 的信号处理函数,功能与父进程的信号处理函数相同*/
static void c1_sig(int signo)
{
switch(signo)
{ case SIGALRM:
c1_realt_secs+=10;
break;
case SIGVTALRM:
c1_virtt_secs+=10;
break;
case SIGPROF:
c1_proft_secs+=10;
break;
}
}
/*子进程 2 的信号处理函数,功能与父进程的信号处理函数相同*/
static void c2_sig(int signo)
{
switch(signo)
{
case SIGALRM:
c2_realt_secs+=10;
break;
case SIGVTALRM:
c2_virtt_secs+=10;
break;
case SIGPROF:
c2_proft_secs+=10;
break;
}
}
在上面的程序中,我们为三个进程使用了三个不同的信号处理函数,这是因为每个进程
需要处理的数据不同。如果在 Linux 中运行这个程序,那么我们就可以看到下面的结果:
Child 1 fib=63245986 , real time=20 sec,250 msec
Child 1 fib=63245986 , cpu time=6 sec,840 msec
Child 1 fib=63245986 , user time=6 sec,800 msec
Child 1 fib=63245986 , kernel time=0 sec,40 msec
Child 2 fib=63245986 , real time=20 sec,380 msec
Child 2 fib=63245986 , cpu time=6 sec,850 msec
Child 2 fib=63245986 , user time=6 sec,840 msec
Child 2 fib=63245986 , kernel time=0 sec,10 msec
Parent fib=63245986 , real time=20 sec,290 msec
Parent fib=63245986 , cpu time=6 sec,870 msec
Parent fib=63245986 , user time=6 sec,820 msec
Parent fib=63245986 , kernel time=0 sec,50 msec
大家可以试着运行一下,看看结果是不是如上所示。当然每个人得到的具体时间可能都
不同,这个要看具体机器的运算速度。
Linux 定时器应用【转】的更多相关文章
- linux定时器用法
linux定时器 原文出自http://www.cnblogs.com/processakai/archive/2012/04/11/2442294.html 今天看书看到了关于alarm的一些用法 ...
- linux定时器crontab
linux定时器crontab用法: 1.基本格式 : * * * * * command 分 时 日 月 周 命令 第1列表示分钟1-59 每分钟用*或者 */1表示 第2列表示小时1-23(0表示 ...
- 4412 Linux定时器
一.Linux定时器基础知识 1.1 定时器的使用范围 延后执行某个操作,定时查询某个状态:前提是对时间要求不高的地方 1.2 内核时间概念 Hz:(系统时钟通过CONFIG_HZ来设置,范围是100 ...
- linux定时器(crontab)实例
linux实验示例----实现每2分钟将“/etc”下面的文件打包存储到“/usr/lobal”目录下 ·Step1:编辑当前用户的crontab并保存终端输入:>crontab -u root ...
- Linux定时器相关源码分析
Linux的定时器使用时间轮算法.数据结构不难理解,核心数据结构与散列表及其相似,甚至可以说,就是散列表.事实上,理解其散列表的本质,有助于对相关操作的理解. 数据结构 这里先列出一些宏,稍后解释: ...
- Smart210学习记录-----linux定时器
1.内核定时器: Linux 内核所提供的用于操作定时器的数据结构和函数如下: (1) timer_list 在 Linux 内核中,timer_list 结构体的一个实例对应一个定时器 1 stru ...
- linux 定时器编程实例(完善中).....
最近在写linux 下的定时器编程实验,测试发现 usleep函数在 x86 架构下的定时还是比较准确的,在arm9下 就不太准了. 今天用linux 下的setitimer()函数进行了定时 器的测 ...
- linux定时器HZ和Jiffies
1.linux HZ Linux核心几个重要跟时间有关的名词或变数,以下将介绍HZ.tick与jiffies. HZ Linux核心每隔固定周期会发出timer interrupt (IRQ 0),H ...
- linux定时器
我们常常有设置系统在某一时间执行相应动作的需求,比如设置电脑什么时候自动锁屏,什么时候自动关机,设置应用程序什么时候自动运行,什么时候自动退出.这些与时间相关的功能,都需要依靠操作系统中的定时器来实现 ...
随机推荐
- java入门--4110:圣诞老人的礼物-Santa Clau’s Gifts
学习了一下java的语法,就用poj上的题目做作练习,好更快的熟悉常用的java语法. 题目在这里 http://bailian.openjudge.cn/practice/4110/ import ...
- 【助教】浅析log4j的使用
有不少童鞋私信我一些在写代码时候遇到的问题,但是无法定位问题出在哪里,也没有日志记录,实际上,写日志是开发项目过程中很重要的一个环节,很多问题都可以从日志中找到根源,从而定位到出错位置,为解决问题提供 ...
- 基于Winform框架DataGridView控件的SqlServer数据库查询展示功能的实现
关键词:Winform.DataGridView.SqlServer 一个基于winform框架的C/S软件,主要实现对SqlServer数据库数据表的实时查询. 一.为DataGridView添加数 ...
- 12th final 发布评价 I
1. 约跑App——nice!:这次使用了摄像进行讲解,相比于上次能够更准确地向大家讲解,整体效果更好了,而且很好地针对同学提出的bug进行修改,能够在并不是很熟悉的领域做到这个程度已经很不容易了, ...
- CAS单点登录的时候出现票根'ST-xxxxxx-cas'不符合目标服务
CAS单点登录遇到问题:票根'ST-xxxxxx-cas'不符合目标服务,原因出在linux 时间未同步,差了3分钟 .
- Redis的核心Hystrix在Spring mvc的使用
核心Hystrix,Hystrix对于接口调用具有很好的保护,能在多服务依赖的分布式系统中,有效的提供应用的可用性,并且对失败应用进行熔断和恢复检查,让应用在复杂的环境中也能各种稳. http://t ...
- Mybatis中jdbcType和javaType、typeHandler的对照关系
JdbcType与Oracle.MySql数据类型对应列表,及 JdbcType Oracle MySql CHAR CHAR CHAR VARCHAR VARCHAR VARCHAR LONGV ...
- Hbase之JAVA API不能远程访问问题解决
1.配置Linux的hostname2.配置Linux的hosts,映射ip的hostname的关系3.配置访问windows的hosts 参考文档:http://blog.csdn.net/ty49 ...
- 生成器 yield
由于生成器的其中一种创建方式与列表推导式很相似,这里先说一下列表推导式. 列表推导式 列表推导式又叫列表生成式,官方叫做 list comprehension.顾名思义,这个是用来生成列表的. 用法: ...
- PostgreSQL——前言
PostgreSQL是以加州大学伯克利分校计算机系开发的POSTGRES, 版本 4.2为基础的对象关系型数据库管理系统(ORDBMS).POSTGRES 领先的许多概念在很久以后才出现在一些商业数据 ...