本系列文章希望能够成为在 OS/2 和 Linux 之间进行迁移的一个参考。在迁移过程中任何时候您都可
以来参考它,不过最好是在计划阶段,因为它提供了您在设计迁移方案和策略时应该了解的技巧与警告。
这一次您将研究 OS/2 和 Linux 中的计时器调用和 DLL 调用,还有两个系统之间的映射。
在本系列文章的第
1 部分和
第
2 部分,您已经了解了线程、
semaphores 和 mutexes 等同步机制、内存管理、各种 IPC 机制以及文件管理。
计时器调用
首先,我们来看几种类型的计时器:
- 时间间隔计时器(Interval timers)。
- 开始计时器(Start timers)。
- 终止计时器(Stop timers)。
- 高精度计时器(High resolution timers)。
时间间隔计时器
在 OS/2 中,
DosStartTimer 会启动一个重复性的时间间隔计时器,它在每
一个计时器间隔中都会发出一个事件信号量。Linux 没有可以直接与之对应的重复性地发出信号量的调用,
但是您可以组合使用
setitimer 系统调用和 Linux 信号处理机制来实现
这一功能。
在 OS/2 中,计时器值参数的单位是毫秒。在 Linux 中,这一信息由一个
struct itimerval 类型的对象来指定,它的单位既可以是秒也
可以是毫秒。
表 1. 时间间隔计时器调用映射表
OS/2
|
Linux
|
类别
| DosStartTimer | setitimer | 可映射 | DosStopTimer | setitimer
itimerclear
| 可映射 |
开始计时器
在 OS/2 中,
DosStartTimer() 系统调用会启动一个异步的、
重复性的时间间隔计时器:
APIRETDosStartTimer(ULONG msec, HSEM hsem, PHTIMER phtimer);
-
msec 是以毫秒为单位的时间,是发出事件信号量的时间间隔。
-
hsem 是每
msec 长度的时间逝去后发出的
事件信号量的句柄。
-
phtimer 是指向计时器句柄的指针。
Linux 没有直接与之对应的每隔一定时间重复发出信号量的 API 调用。使用时间间隔计时器来实现此
功能。时间间隔计时器向进程发送一个
SIGALRM 信号。事件信号量被发送到
信号处事器中,或者等待被发送的信号量的代码位于事件处理器例程之中:
int setitimer (int mode, const struct itimerval *newvalue, struct itimerval
*oldvalue);
-
mode 必须是
ITIMER_REAL ,以使得
计时器可以实时地递减,并在每次过期时发出一个
SIGALRM 。
-
newvalue 是一个指针,指向一个包含了要设置的计时器值的
itimerval 结构体。
-
oldvalue 是一个指针,指向一个包含了老的计时器值的
itimerval 结构体。
如果成功,则
setitimer 返回 0(-1 表示错误),并将
errno 设置为适当的值。计时器值由第一个参数
newvalue 指定,其类型为
struct
itimerval 。当
newvalue->it_value 非零时,
它会指明下一次计时器过期的时间。当
newvalue ->it_interval
非零时,它指定当计时器过期时重新加载
newvalue
->it_value 所用的值。
在信号处理器中只使用同步安全的(async-safe)函数。例如,在信号处理器中不要使用
pthread 条件变量。
终止计时器
在 OS/2 中,
DosStopTimer() 会终止一个异步计时器:
APIRETDosStopTimer(HTIMER htimer);
-
htimer 是要终止的计时器的句柄。成功则返回
0,失败则返回非 0。
在 Linux 中,要终止一个时间间隔计算器,首先调用宏
timerclear 来清空结构体
itimerval 的
it_value 域。
然后使用一个清空过的
itimerval 来调用
setitimer 。
将
it_value 设置为 0 可以禁用计时器,不必理会
it_interval 的值。将
it_interval 设置为 0
可以在下一次过期后禁用计时器(假定
it_value 为非零)。
timerclear 是一个宏定义(在 sys/time.h 中):
#define timerclear(tvp) ((tvp)->tv_sec = (tvp)->tv_usec = 0)
清单 1 中的代码启动一个在每个计时器间隔重复发出一个事件信号量的异步计时器。
清单 1. OS/2 时间间隔计时器示例
int main(VOID)
{
HEV hevEvent1 = 0; /* Event semaphore handle */
HTIMER htimerEvent1 = 0; /* Timer handle */
APIRET rc = NO_ERROR; /* Return code */
ULONG ulPostCount = 0; /* Semaphore post count */
ULONG i = 0; /* A loop index */
/*
* Create an even semaphore that will be posted on timer interval
*/
rc = DosCreateEventSem(NULL, /* Unnamed semaphore */
&hevEvent1, /* Handle of semaphore
* returned
*/
DC_SEM_SHARED, /* Indicate a shared semaphore */
FALSE); /* Put in RESET state */
if (rc != NO_ERROR) {
printf("DosCreateEventSem error: return code = %u\n", rc);
return 1;
}
/*
* Start the timer setting a interval at 1 sec
*/
rc = DosStartTimer(1000L, /* 1 second interval */
(HSEM) hevEvent1, /* Semaphore to post */
&htimerEvent1); /* Timer handler (returned) */
if (rc != NO_ERROR) {
printf("DosStartTimer error: return code = %u\n", rc);
return 1;
}
for (i = 1 ; i < 6 ; i++)
{
DosWaitEventSem(hevEvent1, SEM_INDEFINITE_WAIT); /* Wait indefinitely */
DosResetEventSem(hevEvent1, /* Reset the semaphore */
&ulPostCount); /* And get count
* (should be 1)
*/
printf("Iteration %u: ulPostCount = %u\n", i, ulPostCount);
} /* for loop */
rc = DosStopTimer(htimerEvent1); /* Stop the timer */
if (rc != NO_ERROR)
{
printf("DosStopTimer error: return code = %u\n", rc);
return 1;
}
DosCloseEventSem(hevEvent1); /* Get rid of semaphore */
return NO_ERROR;
}
|
清单 2 中的代码启动一个在每个计时器间隔重复发出一个事件信号量的异步计时器。
清单 2. Linux 时间间隔计时器示例
sem_t sem;
/* callback to handle SIGALRM */
void f (int signo)
{
/* Here you can post on a semaphore */
sem_post(&sem);
}
int main ()
{
int i;
int rc;
/* Declare an itimer object to start a timer */
struct itimerval itimer;
/* Initialize the semaphore. */
sem_init(&sem,0,0);
/* timer interval is 1 sec. */
itimer.it_interval.tv_sec = 1;
itimer.it_interval.tv_usec = 0;
/* Time to the next timer expiration */
itimer.it_value.tv_sec = 1;
itimer.it_value.tv_usec = 0;
/* Install a signal handler to handle SIGALRM */
signal (SIGALRM, f);
/* Start a timer with the ITIMER_REAL clock */
i = setitimer (ITIMER_REAL, &itimer, 0);
if (i == -1)
{
perror ("timer st failed ...\n");
exit (0);
}
for (i = 1 ; i < 6 ; i++)
{
rc = sem_wait(&sem);
if (rc != 0)
{
perror ("sem_wait:");
return 1;
}
printf("Iteration %d: \n", i);
} /* for loop */
/* Set the timer object value with zero, by using the macro
* timerclear and
* set the ITIMER_REAL by calling setitimer.
* This essentially stops the timer.
*/
timerclear(&itimer.it_value);
setitimer (ITIMER_REAL, &itimer, 0);
return 0;
}
|
高精度计时器
接下来,我们将向您介绍如何获得高精度计时器的当前计时器计数和频率。在 OS/2 中,
DosTmrQueryTime() 会返回 Intel 8254 计时器(可编程中断计时器)的时间戳
计数器。Linux 没有可用的 API 可以获得这一信息。您可以使用在 <asm/timex.h> 内核头文件中定义的
get_cycles() 内联函数。
联合时钟的频率使用时间戳计数器,因为计数器与频率的比值在不同的时钟上是相同的。
表 2. 计时器查询调用映射表
OS/2
|
Linux
|
类别
| DosTmrQueryTime | get_cycles | 可映射 | DosTmrQueryFreq | | 不可映射,
但是您可以通过 /proc/cpuinfo 获得时钟频率;参见
清单 3。
|
在 OS2 中,
DosTmrQueryTime() 系统调用从 IRQ0 高精度计时器(Intel 8254)获得
高精度计时器计数的一个快照:
APIRET DosTmrQueryTime(PQWORD pqwTmrTime);
-
pqwTmrTime 是返回计时器计数值的输出参数。
在 Linux 中,
get_cycles() 内联函数返回系统时钟的时间戳计数器,
它是一个 64 位值。这个函数总是成功的(本质上讲,它执行了一个机器指令):
static inline cycles_t get_cycles (void);
在 OS2 中,
DosTmrQueryFreq() 获得 IRQ0 高精度计时器(Intel 8254)
的频率:
APIRET DosTmrQueryFreq (PULONG pulTmrFreq)
-
pulTmrFreq 是输出参数,
DosTmrQueryFreq
将计时器频率返回到它。
Linux 没有与
DosTmrQueryFreq 相当的调用,但是您可以通过 /proc/cpuinfo 获得
时钟频率。
清单 3 展示了一个用来获取 CPU 频率的用户自定义函数。
它打开 /proc/cpuinfo 文件,搜索“cpu MHz”字符串,然后读取 CPU 频率。这个函数将读取的值的单位转换
为 Hz,然后返回转换后的值。
清单 3. 获取时钟频率的 Linux 实现
/*
* Macro to compare keywords
* strcmp is called only when the first characters are the same
*/
#define WordMatch(a, b) ( (*(a) == *(b)) && (strcmp(a, b) == 0) )
int GetClockFreq(unsigned long *pulCpuFreq)
{
char* pcFilename = "/proc/cpuinfo";
char pcReadLine[BUFSIZE];
char pcKeyword[BUFSIZE];
char pcKeyword2[BUFSIZE];
int iDone = 0; /* Flag to determine end of loop */
*pulCpuFreq = 0;
memset(pcReadLine,0,BUFSIZE);
memset(pcKeyword,0,BUFSIZE);
memset(pcKeyword2,0,BUFSIZE);
/*
* Open the /proc/cpuinfo file
*/
fstream fpCpuInfo(pcFilename, ios::in);
if (fpCpuInfo)
{
/*
* Read a line into the buffer
*/
while ((fpCpuInfo.getline(pcReadLine, BUFSIZE)) && (iDone == 0))
{
/*
* Instantiate istrmInput to translate input line into
* a stream object
*/
istrstream istrmInput(pcReadLine);
/*
* Get first 2 word from the input line and build the keyword
*/
istrmInput >> pcKeyword;
istrmInput >> pcKeyword2;
strcat(pcKeyword, " ");
strcat(pcKeyword, pcKeyword2);
if (WordMatch(pcKeyword, "cpu MHz"))
{
/*
* Get the Mhz value from input line by skipping
* past the colon.
*/
istrmInput >> pcKeyword;
istrmInput >> pcKeyword;
/*
* Convert the char* to float and multiply by 1000000 to
* get the clock in Hz
*/
*pulCpuFreq = (unsigned long)(atof(pcKeyword) * 1000000);
iDone = 1;
}
} /* end of while */
fpCpuInfo.close();
}
return ((iDone)?0:-1);
}
|
清单 4 展示了
DosTmrQueryTime 和
DosTmrQueryFreq 调用的使用。
清单 4. OS/2 计时器查询示例
void GetTimeStampPair( )
{
struct QWORD qwStartTime, qwStopTime;
double diff;
Unsigned long ulTmrFreq;
DosTmrQueryTime(&qwStartTime);
/* perform an activity */
diff = 0;
DosTmrQueryTime(&qwStopTime);
/* Calculate the difference between two times */
diff = qwStopTime.ulLo - qwStartTime.ulLo;
/* Query the Intel chip's resolution */
DosTmrQueryFreq(&ulTmrFreq);
printf ("Timer Frequency is %ld\n Hz", ulTmrFreq);
/* calculate the time take for the assignment operation */
/* diff time/frequency */
printf(" Time taken for the activity %ld\n", diff/ulTmrFreq);
} /* end GetTimeStampPair */
|
清单 5 展示了如何通过映射 OS/2
DosTmrQueryTime 和
DosTmrQueryFreq 调用来测量两个操作之间的时间间隔。
清单 5. Linux 计时器查询示例
/*
* This function uses the user defined function GetClockFreq()
*/
#define NANOSLEEP 10000000 /* 10 ms */
#define MSLEEP NANOSLEEP/1000000
int main (void)
{
unsigned long t1, t2, jitter, freq;
double accum;
int ret;
struct sched_param sp;
struct timespec ts,rem;
/* Set the scheduling priority to the real time */
sp.sched_priority = sched_get_priority_max(SCHED_RR);
sched_setscheduler(getpid(), SCHED_RR, &sp);
mlockall(MCL_FUTURE|MCL_CURRENT);
rem.tv_sec = 0;
ret = GetClockFreq (&freq);
if (ret)
{
/* error condition */
return -1;
}
rem.tv_sec = 0;
rem.tv_nsec = NANOSLEEP;
t1 = get_cycles();
do {
ts.tv_sec = rem.tv_sec;
ts.tv_nsec = rem.tv_nsec;
rem.tv_nsec = 0;
ret = nanosleep(&ts, &rem);
} while (rem.tv_nsec != 0);
t2 = get_cycles();
jitter = t2 - t1;
/* How much time elapsed. Number of ticks
* divided by frequency of the clock
*/
accum = (double)jitter/freq;
return 0;
}
|
动态链接库(DLL)调用
动态链接使得很多链接的进程直到程序开始运行时才运行。它可以带来其他方式不能得到的多种
好处:它允许程序在运行期加载和卸载例程,这一方便特性是其他方式很难提供的。
在 Linux 中,动态链接库(DLL)也称为共享库(.so)。系统的动态加载器在系统定义的目录中
(比如 /lib、/usr/lib 和 /usr/X11/lib 等等)查找共享库。当您构建了一个不属于系统部分的
共享库时,使用
LD_LIBRARY_PATH 环境变量来告知动态加载器到其他
目录中进行查找。当链接包括
dlopen 、
dlsym 以及类似调用的模块时,需要给出
-ldl 命令行选项。
表 3. DLL 调用映射表
OS/2
|
Linux
|
类别
| DosLoadModule | dlopen | 可映射 | DosQueryProcAddr | dlsym | 可映射 | DosFreeModule | dlclose | 可映射 |
加载 DLL
在 OS/2 中,您可以使用
DosLoadModule() 来显式地加载一个动态
链接库:
APIRET DosLoadModule(PSZ pszName, ULONG cbName, PSZ pszModname,
PHMODULE phmod);
-
pszModname 是一个指针,指向一个包含有动态链接模块名的变量。
-
phmod 是一个指针,指向返回动态链接模块句柄的变量。
在 Linux 中,使用
dlopen() 库函数来加载动态库。这个函数返回动态
链接库的一个句柄:
void * dlopen(const char *pathname, int mode);
-
pathname 取得要加载的库的名称。
如果它为 NULL,则返回主程序的句柄。
-
mode 可以是
RTLD_LAZY ,
表示认为未定义的符号是来自动态库的代码,也可以是
RTLD_NOW ,
表示要在
dlopen 返回前确定所有未定义的符号,如果不能完成则失败。
有时,您“或者”可以通过一个标志使用
RTLD_GLOBAL ,在这种情况下,
库中定义的外部符号将可以由随后加载的库所使用。
获取 DLL 函数的引用
在 OS/2 中,
DosQueryProcAddr() 会获得动态链接模块中指定
过程的地址:
APIRET DosQueryProcAddr(HMODULE hmod, ULONG ordinal, PSZ pszName, PFN
ppfn);
-
hmod 指的是
DosLoadModule
所返回的模块句柄。
-
pszName 是函数名。
-
ppfn 是返回的函数指针。
在 Linux 中,
dlsym() 返回符号的地址,其参数为
dlopen 返回的动态库的
句柄 和以 null
结尾的符号名称:
void * dlsym(void *handle, char *symbol);
-
handle 是加载的库的句柄。
-
symbol 是符号名。如果没有找到符号,那么
dlsym 就会返回 NULL。
注意,为了支持函数重载,C++ 函数名被处理过了(mangled),因此函数在库中可能会具有不同的名称。
您也可以确定处理的机制并搜索经过处理的符号。当声明中指定了
extern "C" 限定符时,编译器使用 C 风格的链接。
卸载 DLL
在 OS/2 中,
DosFreeModule() 将动态链接模块的引用从此
进程中释放。如果没有任何进程使用动态链接模块,则从系统内存中释放这个模块。由句柄
标识的模块必须原来是使用
DosLoadModule 加载的。如果句柄
不合法,则返回一个错误:
APIRET DosFreeModule (HMODULE hmod);
在 Linux 中,
dlclose() 将
dlopen
先前打开的共享对象从当前进程断开。一旦使用
dlclose 关闭了对象,
dlsym 就再也不能使用它的符号:
int dlclose(void *handle);
-
handle 指的是
dlopen 所返回的句柄。如果引用的对象成功关闭,则
dlclose 返回 0。如果对象不能关闭,或者如果句柄没有指向一个
打开的对象,则
dlclose 返回一个非零值。
注意,在 Linux 中,库函数
dlerror 返回一个以 null 结尾的字符串
(没有最后的换行),描述动态链接过程中发生的最后一个错误:
const char *dlerror(void);
清单 6 中的代码加载动态链接模块 DISPLAY.DLL,获得
draw() 函数的
引用,然后卸载它。它不显示错误处理。
清单 6. 加载 DLL 的 OS/2 示例
int main(VOID) {
PSZ ModuleName = "C:\\OS2\\DLL\\DISPLAY.DLL"; /* Name of module */
UCHAR LoadError[256] = ""; /* Area for Load failure information */
HMODULE ModuleHandle = NULLHANDLE; /* Module handle */
PFN ModuleAddr = 0; /* Pointer to a system function */
ULONG ModuleType = 0; /* Module type */
APIRET rc = NO_ERROR; /* Return code */
/* Load the DLL */
rc = DosLoadModule(LoadError, /* Failure information buffer */
sizeof(LoadError), /* Size of buffer */
ModuleName, /* Module to load */
&ModuleHandle); /* Module handle returned */
/* Get the handle to the function draw() */
rc = DosQueryProcAddr(ModuleHandle, /* Handle to module */
1L, /* No ProcName specified */
"draw", /* ProcName (not specified) */
&ModuleAddr); /* Address returned */
/* Unload the DLL */
rc = DosFreeModule(ModuleHandle);
}
|
清单 7 中的代码加载共享库 libdisplay.so,获得
draw() 函数的引用,
然后卸载它。
清单 7. 加载共享库的 Linux 示例
void* handle, *handle2; /* Handle to the symbols */
/* Get the handle to the shared library. Passing 0 or NULL as
* first parameter to dlopen returns the handle to "main" program
*/
handle = dlopen("libdisplay.so", RTLD_LAZY);
if (handle != NULL)
{
/* Get the handle to the symbol "draw" */
handle2 = dlsym(handle, "draw");
if (handle2 != NULL)
{
/* use the function */
}
/* When finished, unload the shared library */
dlclose(handle);
}
|
结束语
本文是系列文章的第三期,从时间间隔计时器和共享库的角度介绍了从 OS/2 到 Linux 的映射。当您
采取涉及到从 OS/2 到 Linux 的任何迁移行动时,都可以使用本系列文章作为参考。通过这些技巧和警告,
您可以简化迁移行动并得到合适的迁移设计。参考 Linux 手册以获得关于这里提及的 Linux 调用的详细资料。
确保去阅读本系列文章的前两篇,
并告诉我们您本人在迁移过程中的顾虑、故事、成功事迹和问题。
参考资料
作者简介
Dinakar Guniguntala 拥有 REC Surat 的计算机工程的工程学士学位。他从 1998 年 2 月
开始,就一直就职于印度的 IBM Global Services,在这里他从事操作系统内幕的研究。
他的工作包括 OS/2 内核、图形引擎、文件系统、嵌入式 Linux、Linux 内核和 Linux 线程库。
可以通过
dgunigun-at-in.ibm.com与他联系。
Sathyan Doraiswamy 拥有 Bangalore 的 Visvesvaraya 大学工程学院电子与通信专业的
工程学士学位。他于 1997 年 6 月加入了 IBM 印度部门。他就职于工程与技术服务部门,
专攻嵌入式和实时系统。他具有在 Windows 和 Linux 以及 Vxworks 上编写
设备驱动程序的丰富经验。可以通过
dsathyan-at-in.ibm.com与他联系。
Anand K. Santhanam 在印度的 Madras 大学获得了计算机科学的工程学士学位。
从 1999 年 7 月以来,他一直就职于 IBM Global Services,
在那里他从事 ARM-Linux、字符/基于 X 的设备驱动、嵌入式系统上的电源管理、PCI 设备驱动程序以及Linux 上的多线程程序的开发。他的其他兴趣包括 OS 内幕和网络。
可以通过
asanthan-at-in.ibm.com与他联系。
Srinivasan S. Muthuswamy 在印度的 Coimbatore 国立科技大学获得了计算机工程
的工程学士学位。从 2000 年 8 月以来,他就一直就职于印度的 IBM Global Services。
他从事 Linux 上多线程应用程序的开发,以及使用 WSBCC/Java 技术/WebSphere/MQSeries
技术开发 Web 应用程序。他还从事有关 Crystal Reports 和 Lotus Domino 等方面的工作。
可以通过
smuthusw-at-in.ibm.com与他联系。
Rinson Antony 在印度的 Bombay 大学获得了计算机工程的工程学士学位。
他从 2000 年 7 月以来,一直就职于印度的 IBM Global Services。他的工作包括
Linux 上开发多线程应用程序和使用 Java 技术/WebSphere/XML 开发 Web 应用程序。
他的其他兴趣是 PCI 设备驱动程序和网络。可以通过
arinson-at-in.ibm.com与他联系。
Brahmaiah Vallabhaneni 在印度的 BITS, Pilani 获得了工程学士学位。
从 2002 年 8 月以来,他一直就职于印度的 IBM Global Services。他的工作包括在
嵌入式系统上移植 GNU C 编译器,开发字符设备驱动程序和电源管理程序,
以及在 Linux 上开发 PCI 设备驱动程序和多线程程序。他的其他兴趣包括 Linux 内幕和网络。
可以通过
bvallabh-at-in.ibm.com与他联系。
|