本系列文章希望能够成为在 OS/2 和 Linux 之间进行迁移的一个参考。在迁移过程中任何时候您都可以来参考它,不过最好是在计划阶段,因为它提供了您在设计迁移方案和策略时应该了解的技巧与警告。
这一次您将研究 OS/2 和 Linux 中的计时器调用和 DLL 调用,还有两个系统之间的映射。
在本系列文章的第 1 部分和第 2 部分,您已经了解了线程、semaphores 和 mutexes 等同步机制、内存管理、各种 IPC 机制以及文件管理。
首先,我们来看几种类型的计时器:
在 OS/2 中, DosStartTimer
会启动一个重复性的时间间隔计时器,它在每一个计时器间隔中都会发出一个事件信号量。Linux 没有可以直接与之对应的重复性地发出信号量的调用,但是您可以组合使用 setitimer
系统调用和 Linux 信号处理机制来实现这一功能。
在 OS/2 中,计时器值参数的单位是毫秒。在 Linux 中,这一信息由一个 struct itimerval
类型的对象来指定,它的单位既可以是秒也可以是毫秒。
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
指定,其类型为 structitimerval
。当 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 中的代码启动一个在每个计时器间隔重复发出一个事件信号量的异步计时器。
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 中的代码启动一个在每个计时器间隔重复发出一个事件信号量的异步计时器。
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()
内联函数。
联合时钟的频率使用时间戳计数器,因为计数器与频率的比值在不同的时钟上是相同的。
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,然后返回转换后的值。
/* * 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
调用的使用。
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
调用来测量两个操作之间的时间间隔。
/* * 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; } |
动态链接使得很多链接的进程直到程序开始运行时才运行。它可以带来其他方式不能得到的多种好处:它允许程序在运行期加载和卸载例程,这一方便特性是其他方式很难提供的。
在 Linux 中,动态链接库(DLL)也称为共享库(.so)。系统的动态加载器在系统定义的目录中(比如 /lib、/usr/lib 和 /usr/X11/lib 等等)查找共享库。当您构建了一个不属于系统部分的共享库时,使用 LD_LIBRARY_PATH
环境变量来告知动态加载器到其他目录中进行查找。当链接包括 dlopen
、 dlsym
以及类似调用的模块时,需要给出 -ldl
命令行选项。
OS/2 | Linux | 类别 |
DosLoadModule | dlopen | 可映射 |
DosQueryProcAddr | dlsym | 可映射 |
DosFreeModule | dlclose | 可映射 |
在 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
,在这种情况下,库中定义的外部符号将可以由随后加载的库所使用。 在 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 风格的链接。
在 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()
函数的引用,然后卸载它。它不显示错误处理。
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()
函数的引用,然后卸载它。
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与他联系。
联系客服