OS/2 为开发人员提供了一个 32 位的线性地址空间,操作系统以页为单位对内存进行分配、保护和操作。
OS/2 应用程序以内存对象的形式对内存进行分配和操作,一个内存对象可以包含一页或多页。最小内存分配(allocation)是一页。但是应用程序可以以内存块的形式来子分配(
suballocate)内存,内存块是内存对象的一部分,其大小可以从 1 个字节到内存对象的大小。当应用程序请求 OS/2 分配内存时,操作系统就为其保留一个线性地址范围。直到这段内存被提交时,才会将物理内存映射到这个范围的线性地址空间上。所谓提交(commitment)就是将物理内存分配到一个线性地址范围上。任何试图对未提交的内存进行读写的操作都会导致访问异常。
Linux 也有一个 32 位的线性地址空间;但是与 OS/2 不同的是,Linux 中的每个进程都可以看到一个不同的线性地址空间集。任何进程申请分配动态内存都会导致将一个新的线性地址空间范围加入该进程的地址空间。这个地址范围称为
内存区域(memory region)。而物理内存的分配则被延迟到实际引用时才会真正进行。
表 1. 内存管理映射表
OS/2
|
Linux
|
类别
|
分配
DosAllocMem
DosSetMem
子分配
DosAllocMem
DosSubSetMem
DosSubAllocMem
DosSubFreeMem
DosSubUnsetMem
|
按页对齐,具有内存保护功能
posix_memalign
mprotect
按页对齐,没有内存保护功能
posix_memalign
不按页对齐,没有内存保护功能
malloc/new
| 特定于上下文的 | DosFreeMem | free
delete(在用 new 分配的情况下)
| 可映射的 | DosAllocSeg | malloc/new | 可映射的 | DosFreeSeg | free/delete | 可映射的 |
类别一列表示该 OS/2 结构是可映射的,还是特定于上下文的:
- 可映射的:
这个 OS/2 结构可以被映射为特定的 Linux 结构,二者具有相近的类型、参数、返回值和结构形式,而且功能相似。
- 特定于上下文的:
给定的 OS/2 结构在 Linux 中可能有等价的结构,也可能没有,或者 Linux 有个结构提供类似的功能。无论是哪种情况,决定是否使用特定的 Linux 结构要取决于应用程序的上下文。
回页首 内存的提交或子分配
在 OS/2 中,推荐的内存管理方式是在程序执行的早期就分配一大段内存,然后在需要使用内存时再对这些内存进行提交或子分配。内存对象被分配之后,应用程序可以使用以下两种方法中的一种来管理内存对象:
- 根据需要对内存进行提交或回收内存(参阅
清单 1)。
- 将这个内存对象作为一个堆(heap),并从这个堆中再分配内存(参阅
清单 2)。
对内存进行提交或回收为应用程序提供了更多控制进程的手段,但是应用程序必须要跟踪哪些页被提交了,哪些没有。
当从一个堆中分配内存时,OS/2 负责跟踪对物理内存页的提交和回收情况,这就是说应用程序无需担心这个问题。
在 Linux 中,应用程序不需要担心提交/回收和子分配的问题。申请分配任意大小的内存都可以使用相同的内存分配的系统调用(
malloc/posix_memalign )。内存分配器将总是从空闲列表中返回一个最合适大小的内存块。操作系统负责内存的提交和分配。
Linux 采用一种乐观的内存分配策略。这意味着即使
malloc() 返回一个非空值,也不能确保现在真的有内存可用。当系统出现内存不足的情况时,OOM(“out of memory”)程序就会杀掉一个或多个进程。
分配内存
在 OS/2 中,使用系统调用
DosAllocMem() 来保留一段范围内的内存页。内存可以在分配时进行提交,也可以以后调用
DosSetMem() 进行提交:
APIRET DosAllocMem(PPVOID ppbBase, ULONG size, ULONG attr);
APIRET DosSetMem(PVOID pbBase, ULONG size, ULONG flag);
-
ppbBase 指向所分配的内存位置的指针。
-
size 说明所请求分配的字节数。
-
attr 是一组标志,描述内存分配属性,以及希望的访问保护。
-
flag 是一组标志,指定内存提交或回收,以及希望的访问保护。
对已提交页的访问由一个访问保护属性进行控制;保护属性有读保护(
PAG_READ )、写保护(
PAG_WRITE )、执行保护(
PAG_EXECUTE )以及守护页保护(
PAG_GUARD )。注意由
DosAllocMem 分配的内存总是按页对齐的,不能重新设置大小。
如果一个应用程序希望使用子分配机制,必须首先使用
DosAllocMem() 分配一段内存,然后调用
DosSubSetMem() 为子分配建立要分配的内存堆。然后应用程序就可以使用
DosSubAllocMem() 从堆中分配一段内存,或者使用
DosSubFreeMem() 函数来释放内存。 最后使用
DosSubUnSetMem() 来结束子分配的设置:
APIRET DosSubSetMem(PVOID pbBase, ULONG flag, ULONG size);
APIRET DosSubAllocMem(PVOID pbBase, PPVOID ppMemblock, ULONG size);
APIRET DosSubFreeMem(PVOID pbBase, PVOID pOffset, ULONG size);
APIRET DosSubUnsetMem(PVOID pbBase);
-
pbBase 指向由
DosAllocMem() 分配的内存。
-
size 是字节数。
-
ppMemblock 是指向要由
DosSubAllocMem() 分配的内存块的指针。
-
pOffset 是指向要由
DosSubFreeMem() 释放的内存块的指针。
在移植程序时,在 Linux 中内存分配可以分为两类:按页对齐的和不按页对齐的。
有或没有内存保护的按页对齐内存分配
在 Linux 中,可以使用系统调用
posix_memalign() 来分配按页对齐的内存。默认情况下,内存区有
PROT_READ | PROT_WRITE 标志。这些内存保护属性可以调用
mprotect() 进行修改。库函数
free() 可以用来释放由
posix_memalign() 分配的内存空间:
int posix_memalign(void **memptr, size_t alignment, size_t size);
int mprotect(const void *addr, size_t size, int prot);
-
*memptr 包含了所分配的内存的地址。所分配的内存地址是对齐过的,必须是 2 的幂或
sizeof(void *) 的整数倍。
-
addr 是指向所分配的内存的指针,这段内存的保护标志必须使用
mprotect() 进行修改。
-
prot 是保护标志,值可以为:
-
PROT_NONE :该段内存根本不能访问。
-
PROT_READ :该段内存可读。
-
PROT_WRITE :该段内存可写。
-
PROT_EXEC :该段内存可以包含执行代码。
没有内存保护的不按页对齐内存分配
默认情况下,OS/2 分配的内存都是按页对齐的。如果不需要按页对齐的内存,而且默认的访问保护标志
PROT_READ | PROT_WRITE 就已足够了,那么在 Linux 中就可以使用
malloc() 来分配内存,使用
free() 来释放已经分配的内存:
void *malloc(size_t size)
另外,对于使用 C++ 编写的程序来说,可以使用关键字
new 来分配内存,使用关键字
delete 来释放内存空间。请确保在使用
new 时要使用
nothrow 选项,这样当分配内存出错时,就不会触发异常,而是返回一个空指针。
回页首 释放内存
OS/2 使用
DosFreeMem() 从主进程的虚拟地址空间中释放之前分配的内存对象:
APIRET DosFreeMem(PVOID pbBase);
Linux 使用
free() 释放之前分配的内存:
void free(void *memptr)
分段的内存分配
系统调用
DosAllocSeg() 也可以用来分配多达 64K 的内存块;
DosMemAvail() 用来查找还可以分配的可用内存数量,
DosFreeSeg() 用来释放内存段。参数 size 说明要分配的字节数(最大为 64KB);selector 是一个指向整数的指针,它是要获取的选择器;flags 必须为 0。
USHORT BLXAPI DosAllocSeg (USHORT size, PSEL selector, USHORT flags)
USHORT BLXAPI DosFreeSeg(SEL selector)
Linux 中没有分段的内存分配模型。
例子
清单 1. OS/2 大内存分配
// OS/2 example of memory allocation/de-allocation by committing
// and decommitting memory
APIRET ulrc;
PBYTE pb;
/* Allocate 16KB object */
ulrc = DosAllocMem((PVOID *) &pb, 2097152, PAG_READ | PAG_WRITE);
/* Commit 4KB */
ulrc = DosSetMem(pb, 4096, PAG_COMMIT);
strcpy(pb, "The memory object has just been used.");
printf("%s\n",pb);
/* De-Commit 4KB */
ulrc = DosSetMem(pb, 4096, PAG_DECOMMIT);
//Free the memory area
ulrc = DosFreeMem(pb);
|
清单 2. OS/2 小内存分配
// OS/2 example of memory suballocation. It sets up 8192 bytes
// for suballocation and then allocates two small blocks of memory
APIRET ulrc;
PBYTE pbBase, pb1, pb2;
/* Allocate 8K object */
ulrc = DosAllocMem((PVOID *) &pbBase, 8192, fALLOC);
/* Set up object or suballocation */
ulrc = DosSubSetMem(pbBase, DOSSUB_INIT, 8192);
/* Suballocate 100 bytes */
ulrc = DosSubAllocMem(pbBase,(PVOID *) &pb1,100);
/* Suballocate 500 bytes */
ulrc = DosSubAllocMem(pbBase,(PVOID *) &pb2,500);
/* Free 1st suballocation*/
ulrc = DosSubFreeMem(pbBase, pb1, 100);
/* Suballocate 50 bytes */
ulrc = DosSubAllocMem(pbBase, (PVOID *) &pb1, 50);
/* Free all suballocation*/
ulrc = DosSubFreeMem(pbBase, pb1, 50);
ulrc = DosSubFreeMem(pbBase, pb1, 500);
/* End the suballocation */
ulrc = DosSubUnsetMem(pbBase)
//Free the memory area
ulrc = DosFreeMem(pbBase);
|
清单 3. OS/2 段
SEL
selector;
/* Allocate a 4KB block */
if (DosAllocSeg(4096, &selector, 0) != 0) {
printf("DosAllocSeg()
failed\n");
return
1;
}
// print the allocated memory address
printf("4 Kb block allocated at :
%04X\n",selector);
// Free the memory segment
if (DosFreeSeg(selector) != 0) {
printf("DosFreeSeg() failed - selector = %04X\n",
selector);
return
1;
}
|
清单 4. Linux 按页对齐的内存分配
// Page-aligned memory allocation with memory write protection
int main (void)
{
char** ppcBuf; /* Pointer to memory
object */
int ret = 0; /* Return code
*/
//allocate page-aligned memory area. By default both read and write
//are allowed. If that's the requirement then no need to use
mprotect
ret = posix_memalign(ppcBuf, 4096, 4096);
//write protect the memory
ret = mprotect(*ppcBuf, 4096, PROT_READ);
//Free the memory area
free(*ppcBuf);
return 0;
}
|
清单 5. Linux 不按页对齐的内存分配
// Non page-aligned memory allocation with default memory protection
// both read and write allowed
#define USE_MALLOC 1
char *pcBaseAddress = NULL;
if (USE_MALLOC == 1) {
// Allocate memory using malloc
pcBaseAddress = (char *) malloc(4096L);
}
else {
// Allocate memory using new
pcBaseAddress = new (nothrow) char[4096];
}
if (pcBaseAddress == NULL) {
return 1;
}
if (USE_MALLOC == 1) {
// free the memory space
free(pcBaseAddress);
}
else {
// free memory space using delete
delete [] pcBaseAddress;
}
|
回页首 共享内存
表 2. 共享内存映射表
OS/2
|
Linux
|
类别
|
有名共享内存
DosAllocSharedMem
DosGetNamedSharedMem
| shmget
shmat
| 可映射的 | DosFreeMem | shmdt
shmctl
| 可映射的 |
共享内存(Shared memory)是两个或多个应用程序可以读写的内存。共享内存在使用之前要进行一些准备工作,以便所有的应用程序都可以获得一个指针指向这段内存,从而可以访问数据。应用程序必须显式地请求反问共享内存;所有未经授权的应用程序不能访问共享内存。
共享内存的类型
在 OS/2 中,有两种共享内存:有名共享内存和无名共享内存。
在
有名共享内存(named shared memory,也称为命名共享内存)中,任何知道共享内存名的应用程序都可以访问这段共享内存。
在
无名共享内存(unnamed shared memory)中,创建共享内存的进程必须通过某种 IPC 机制向要访问共享内存的进程传递一个指向共享内存的指针。
在 Linux 中,共享内存通常都是有名共享内存(使用一个关键字惟一标识);Linux 不支持无名共享内存。
有名共享内存
在 OS/2 中,可以调用
DosAllocSharedMem() 在虚拟地址空间中分配一个共享内存对象。分配共享内存对象会创建一个描述一段要共享的内存区域的对象。共享内存对象的虚拟地址空间在每个进程的虚拟地址空间中都是一样的,这样任何进程可以在自己原来的地址空间中使用相同的地址访问这个共享对象:
APIRET DosAllocSharedMem(PPVOID ppb, PSZ pszName, ULONG cb, ULONG
flag);
-
ppb 是一个变量的指针,该变量是要获取的已分配的页范围的基地址。
-
pszName 是共享内存名;例如,\SHAREMEM\PUBLIC.DAT。
-
cb 是要分配的共享内存对象的字节数。
-
flag 是分配属性以及希望的访问保护标志。
其他进程可以使用
DosGetNamedSharedMem() 来访问这个共享内存对象。要指定共享内存对象的名字,共享内存名字符串中必须包括前缀“
\SHAREMEM\ ”。要在进程的虚拟地址空间中获得一个有名共享内存对象所分配的虚拟地址,请使用:
APIRET DosGetNamedSharedMem(PPVOID ppb, PSZ pszName, ULONG flag);
-
ppb 是一个指向变量的指针,该变量是要获取的共享内存对象的基地址。
-
pszName 是与该共享内存对象有关的名字字符串的地址。
-
flag 是属性标志,它说明了希望该共享内存对象的访问保护模式。
系统调用
DosFreeMem() 用来释放共享内存对象。
在 Linux 中,在创建或访问共享内存时必须同时使用系统调用
shmget() 和
shmat() 。
shmget() 返回与参数 key 值相关的共享内存段的标识符。这个 key 值可以用来模拟
DosAllocSharedMem 中
pszName 参数的行为。如果
shmflg 是
IPC_CREAT ,就创建一个新的共享内存段,其大小等于
PAGE_SIZE 的整数倍(向下一个整页对齐)。如果不使用这个标志,那么
shmget() 就会查找与该 key 值相关的现有共享内存段:
int shmget (key_t key , int size , int shmflg );
系统调用
shmat() 将由 shmid 指定的共享内存段附加到调用进程的地址空间上。
shmid 是调用
shmget() 时返回的结果,附加的地址是由
shmaddr 指定的;如果将其设置为 0, 操作系统就会自行选择一个地址。
shmflg 是访问保护标志;如果是只读访问,就将其设置为
SHM_RDONLY ,否则设置为 0:
void *shmat ( int shmid, void *shmaddr, int shmflg);
要释放共享内存,首先需要调用
shmdt() 将由 shmaddr 所指定的地址处的共享内存段与调用进程的数据段断开:
int shmdt (void *shmaddr);
要彻底释放共享内存,需要调用
shmctl() ,并指定
IPC_RMID 命令。这个命令只有在两种情况下才能执行:执行该命令的进程的有效用户 ID 等于与 shmid 相关的数据结构中的
shm_perm.cuid 或
shm_perm.uid 的值,或者该进程具有超级用户权限。要释放内存,可以将
buf 设置为 NULL:
int shmctl(int shmid, int cmd,struct shmid_ds *buf);
例子
清单 6. OS/2 共享内存
/* This example allocates named shared memory as Read/Write, and commits it
during allocation. It also writes to it, and shows how other processes can
access it. */
int main (VOID)
{
PVOID pvShrObject = NULL; /* Pointer to shared memory
object */
PSZ pszMemName = "\\SHAREMEM\\MYTOOL\\APPLICAT.DAT";
/* Object name */
PVOID pvAltObject = NULL; /* Alternate pointer to shared
memory */
APIRET rc = NO_ERROR; /* Return code */
ULONG ulObjSize = 1024; /* Size (system rounds to 4096
- page body */
rc = DosAllocSharedMem(&pvShrObject, /* Pointer to object
pointer */
pszMemName, /* Name for shared
memory */
ulObjSize, /* Desired size of
object */
PAG_COMMIT | /* Commit
memory */
PAG_WRITE ); /* Allocate memory as
read/write */
if (rc != NO_ERROR) {
printf("DosAllocSharedMem error: return code = %u\n",rc);
return 1;
}
strcpy(pvShrObject, "Write your shared application data here.");
rc = DosGetNamedSharedMem(&pvAltObject, /* Pointer to pointer
of object */
pszMemName, /* Name of shared
memory */
PAG_READ); /* Want read-only
access */
if (rc != NO_ERROR) {
printf("DosGetNamedSharedMem error: return code = %u\n",rc);
return 1;
}
printf("Shared data read was \"%s\"\n",pvAltObject);
rc = DosFreeMem(pvShrObject);
if (rc != NO_ERROR) {
printf("DosFreeMem error: return code = %u\n",rc);
return 1; }
return NO_ERROR;
}
|
清单 7. Linux 共享内存
// In the following example, Process 1 allocates the shared memory
// and writes its process id to the shared memory. Process 2 obtains
// access to the memory and prints out process id of Process 1.
// In production code, you would also need to include error-checking;
// this code sample assumes that all calls are successful.
Process 1
#include <sys/types.h>
#include <sys/shm.h>
#define APP_SHARED_MEMORY 0x20
typedef struct {
int iPID;
} shared_t;
shared_t *shared_mem; // The address of the shared memory
int iShmId; // The shared mem identifier
// The shared memory is allocated, It is indentified by the key
// APP_SHARED_MEMORY. This same key must be used by process 2
// to obtain access to the shared memory
iShmId = shmget (APP_SHARED_MEMORY, sizeof(shared_t), IPC_CREAT);
shared_mem = (shared_t *) shmat ( iShmid, 0, 0); // Obtain the shared
memory address
shared_mem-> iPID = getpid(); // Write the process
id to the shared mem
// Wait for a signal or do some processing
:
:
shmdt(shared_mem); // Detach the shared memory reference
Process 2
#include <sys/ipc.h>
#include <sys/shm.h>
#define APP_SHARED_MEMORY 0x20
typedef struct {
int iPID;
} shared_t;
shared_t *shared_mem; // The address of the shared memory
int iShmId; // The shared mem identifier
// Obtain the id of shared memory indentified by key APP_SHARED_MEMORY
iShmId = shmget (APP_SHARED_MEMORY, sizeof(shared_t), 0);
shared_mem = (shared_t *) shmat ( iShmid, 0, 0); // Obtain the shared
// memory address
// print out the process id of process 1
printf ("Process 1 PID %d\n" , shared_mem-> iPID);
// Detach and remove the shared memory
shmdt(shared_mem);
shmctl(iShmId, IPC_RMID, NULL);
:
:
|
获取全部物理内存
在 OS/2 中,可以使用 DosQuerySysInfo 来获取全部物理内存:
APIRET DosQuerySysInfo (istart, ilast, pBuf, cbBuf)
-
istart
是返回的第一个系统变量的序号。
-
ilast
是返回的最后一个系统变量的序号。
-
pBuf
是数据缓冲区的地址,也就是系统返回变量值的地方。
-
cBuf
是数据缓冲区的长度,以字节为单位。
系统调用 DosQuerySysInfo 返回静态变量的值。通用的参数有:
- QSV_TOTPHYSMEM:系统中物理内存的总数,以字节为单位。
- QSV_TOTRESMEM:系统中常用内存的总数,以字节为单位。
- QSV_TOTAVAILMEM:系统中所有进程可以分配的最大内存总数,以字节为单位。
在 Linux 中,可以使用系统调用 sysinfo 来获取全部的物理内存:
int sysinfo (struct sysinfo *info )
表 3. 系统信息映射表
OS/2
|
Linux
|
类别
| DosQuerySysInfo | sysinfo |
可映射的
|
在 Linux 2.3.16 之前的版本上,sysinfo 会以字节为单位给出内存的大小,并以下面这个结构的形式返回一些信息:
struct sysinfo {
long uptime; /* Seconds
since boot */
unsigned long loads[3]; /* 1, 5, and 15 minute
load averages */
unsigned long totalram; /* Total usable main
memory size */
unsigned long freeram; /* Available memory
size */
unsigned long sharedram; /* Amount of shared
memory */
unsigned long bufferram; /* Memory used by
buffers */
unsigned long totalswap; /* Total swap space
size */
unsigned long freeswap; /* swap space still
available */
unsigned short procs; /* Number of current
processes */
char _f[22]; /* Pads structure
to 64 bytes */
};
|
从 Linux 2.3.23 (在 i386 体系结构平台上)和 2.3.48 (在所有的体系结构平台上)起,内存大小都是以 mem_unit 整数倍的字节数给出的,所采用的结构如下:
struct sysinfo {
long uptime; /* Seconds
since boot */
unsigned long loads[3]; /* 1, 5, and 15 minute
load averages */
unsigned long totalram; /* Total usable main
memory size */
unsigned long freeram; /* Available memory
size */
unsigned long sharedram; /* Amount of shared
memory */
unsigned long bufferram; /* Memory used by
buffers */
unsigned long totalswap; /* Total swap space
size */
unsigned long freeswap; /* swap space still
available */
unsigned short procs; /* Number of current
processes */
unsigned long totalhigh; /* Total high memory
size */
unsigned long freehigh; /* Available high
memory size */
unsigned int mem_unit; /* Memory unit size in
bytes */
char _f[20-2*sizeof(long)-sizeof(int)]; /* Padding
for libc5 */
};
|
例子
清单 8. OS/2 中的物理内存大小
usigned long ulPhysMem;
// Get the total number of bytes of physical memory in this system.
DosQuerySysInfo(QSV_TOTPHYSMEM, QSV_TOTPHYSMEM, &ulPhysMem,
sizeof(ulPhysMem));
// ulPhysMem will have the total physical memory in bytes
printf("The total physical memory in bytes is %ld\n", ulPhysMem);
|
清单 9. Linux 中的物理内存大小
struct sysinfo s_info;
....
sysinfo(&s_info);
// the sysinfo structure has the total physical memory of the system.
printf(" The total physical memory available is %ld\n in Bytes",
(s_info.totalram * mem_unit));
|
回页首 管道
在 OS/2 和 Linux 中都可以使用管道(pipe)技术实现进程间通信。进程可以对管道进行读写,就仿佛是标准输入和标准输出一样。在 OS/2 中,管道是双向的,当创建管道时会返回一个句柄,这个句柄可以用来在进程间实现双向通信。在 Linux 中,管道也是双向的,不同之处在于它会返回两个句柄:一个读句柄和一个写句柄。
在对管道进行映射时,需要考虑以下几点因素:
-
大小
在 OS/2 中,管道大小可以使用一个参数指定。而在 Linux 中,内核将管道大小设置为 4096 字节,用户应用程序不能修改该值。
-
类型
是有名管道(named pipe,也称为命名管道)还是无名管道(unamed pipe)?无名管道的用法在 OS/2 和 Linux 中相同。OS/2 上的有名管道允许不同机器上的进程相互间进行通信。Linux 则将这种通信只限定在一台机器上。为了在 Linux 上实现 OS/2 中有名管道的功能,可以使用客户端/服务器编程技术(TCP/IP Sockets)。
表 4. 管道映射表
OS/2
|
Linux
|
类别
| DosCreatePipe
DosCreateNPipe (Server side)
DosConnectNPipe (Server Side)
DosOpen (Client side)
| pipe
mkfifo
open
|
上下文相关的
| DosWrite
DosRead
| write
read
|
可映射的
| DosDisconnectNPipe (Server)
DosClose (Client)
| close |
上下文相关的
|
无名管道
在 OS/2中,可以使用系统调用 DosCreatePipe() 创建一个无名管道:
APIRET DosCreatePipe (HFILE hReadPipe, HFILE hWritePipe, ULONG cb)
-
hReadPipe和
hWritePipe
分别是指向管道的读句柄和写句柄的指针。
-
cb
用来设置管道的大小。
在 Linux 中,可以使用系统调用 pipe 来创建一个无名管道:
int pipe (int filedes[])
-
filedes[0]和
filedes[1]
分别是指向管道的读句柄和写句柄的指针。
有名管道
在 OS/2 中,有名管道不但可以用来在同一台机器上运行的相关进程或无关进程之间进行通信,甚至可以在不同机器上运行的进程间进行通信。一个进程(服务器进程)创建一个有名管道,并连接到这个有名管道上。然后,客户端进程连接到这个管道的另外一端上,通过读写管道来回传递数据。
在 OS/2 中可以使用系统调用 DosCreateNPipe() 创建一个有名管道:
APIRET DosCreateNPipe (PSZ pszName, PHPIPE pHpipe, ULONG openmode,
ULONG pipemode, ULONG cbOutbuf, ULONG cbInbuf, ULONG msec)
-
pszName
是管道的 ASCII 名。
-
pHpipe
是管道句柄。
-
openmode
是一组标志,定义了打开管道时的模式。
-
pipemode
是一组标志,定义了管道的模式。
-
cbOutbuf和
cbInbuf
分别是为外发(从服务器端到客户端)缓冲区和内达(从客户端到服务器端)缓冲区分配的字节数。
-
msec
是等待有名管道实例变为可用时等待的最长时间,单位是毫秒。
服务器进程可以使用系统调用 DosConnectNPipe() 连接到管道的一端上,这样就可以将管道设置成监听模式,客户端进程现在就可以使用 DosOpen() 来访问这个管道了:
APIRET DosConnectNPipe (HPIPE hpipe)
-
hpipe
是 DosCreateNPipe()返回的管道句柄。
系统调用 DosOpen() 允许客户端进程访问由服务器创建的处于监听模式的有名管道:
APIRET DosOpen(PSZ pszFileName, PHFILEpHf, PULONG pulAction, ULONG
cbFile,ULONGulAttribute, ULONGfsOpenFlags, ULONG fsOpenMode,
ULONGpeaop2)
-
pszFileName
是有名管道名的 ASCII 值。
-
pHf
包含了有名管道的句柄。
在 Linux 中,有名管道(FIFO)用来在同一台机器上运行的进程之间进行通信。mkfifo() 用来在 Linux 上创建一个有名管道(FIFO)。在创建 FIFO 之后,可以使用 open() 系统调用打开这个 FIFO,以后便可以执行读写操作了:
int mkfifo(const char * pathname , mode_tmode )
-
pathname
是有名管道的名字。
-
mode
指定 FIFO 的权限。
在 Linux 中,可以使用 open() 来打开 FIFO,其中 filename 参数应该是该文件(FIFO)名的 ASCII值:
int open (const char * filename, int flags )
int open (const char * filename, int flags, mode_t mode)
使用管道读写数据
在 OS/2 中,可以使用 DosRead() 系统调用从有名管道和无名管道中读取数据,其中 hFile 是管道读端的句柄:
APIRET DosRead (HFILE hFile, PVOID pBuffer, ULONG cbRead, PULONG
pcbActual)
在 Linux 中,可以使用 read() 系统调用从管道中读取数据,其中 fd 是管道读端的句柄:
size_t read(int fd, void *buf, size_t count)
在 OS/2 中,可以使用 DosWrite() 系统调用向管道写入数据,其中 hFile 是管道写端的句柄:
APIRET DosWrite(HFILE hFile, PVOIDpBuffer,ULONG cbWrite, PULONG
pcbActual )
在 Linux 中,可以使用 write() 系统调用向管道中写入数据,其中 fd 是管道写端的句柄:
ssize_t write(int fd, const void *buf, size_t count)
关闭管道
对于 OS/2 中的有名管道来说,服务器进程最后需要使用 DosDisConnectNPipe()断开与客户端进程的连接。通常,客户端进程首先要执行 DosClose() 关闭管道,然后服务器端才断开与管道的连接。但是,服务器端的确也可以在客户端之前断开连接,从而强制客户端关闭管道。对于无名管道来说, DosClose() 就可以关闭连接了:
APIRET DosDisConnectNPipe (HPIPE hpipe)
APIRET DosClose (HPIPE hpipe)
在 Linux 中,可以使用
close 系统调用关闭 FIFO,其中 fd 是 FIFO 读/写端的句柄:
int close (int fd)
例子
清单 10. OS/2 中的无名管道
/*
In this example, thread 1 creates an unnamed pipe, and writes to the
pipe. Thread 2 reads the data once it becomes available.
*/
HFILE ReadHandle = 0; /* Read handle of pipe */
HFILE WriteHandle = 0; /* Write handle of pipe */
ULONG PipeSize = 4096; /* Size of pipe */
APIRET rc = NO_ERROR; /* API return code */
Thread 1
// Create pipe
rc = DosCreatePipe(&ReadHandle, &WriteHandle, PipeSize);
// get access to the pipe
...
// Write to pipe
rc = DosWrite(&WriteHandle,&cBuf, &len, &ulWrote);
...
// Close write handle
rc = DosClose(&WriteHandle);
Thread 2
// wait for the data to come
// Read from pipe
rc = DosRead(&ReadHandle, &cBuf, &ulLen, &ulRead);
..
// Close read handle
rc = DosClose(&ReadHandle);
|
清单 11. OS/2 中的有名管道
// Client program
// This client program opens a named pipe, and connects to the
// server. It writes messages to the pipe and closes the pipe
// when it has no more data to write.
# define PIPE_NAME \\PIPE\\EXAMPLE
#define BUF_SIZE 255
/* Pipe handle */
HFILE PipeHandle = NULLHANDLE;
ULONG bytes = 0;
ULONG Action = 0;
APIRET retCode = NO_ERROR; //Return code
CHAR message[BUF_SIZE +1] = ""; /* Message buffer */
// Open the pipe
rc = DosOpen(PIPE_NAME,
&PipeHandle,
&Action,
0, 0,
FILE_OPEN,
OPEN_ACCESS_READWRITE |
OPEN_SHARE_DENYREADWRITE |
OPEN_FLAGS_FAIL_ON_ERROR, NULL);
do {
rc = DosWrite(PipeHandle, message, strlen(message), &bytes);
....
....
// end of message
done = 1;
} while (!done);
// close the write handle of PIPE
rc = DosClose(PipeHandle);
// Server program
// This Server program opens a named pipe, and waits for a client
// connection. It writes messages to the pipe, and closes the pipe
// when it finds no data to write
# define PIPE_NAME \\PIPE\\EXAMPLE
#define BUF_SIZE 255
HPIPE PipeHandle = NULLHANDLE;//Pipe handle
ULONG ulBytes = 0;//Bytes read /written
CHAR message[BUF_SIZE + 1] = ""; /* Input/Output
buffer */
APIRET retCode = NO_ERROR; /* Return code */
// create a named pipe
retCode = DosCreateNPipe(PipeName, /* Name of pipe to
create */
&PipeHandle, /* Handle returned
for pipe */
NP_ACCESS_DUPLEX, /* Duplex pipe */
NP_WAIT |
NP_TYPE_MESSAGE |
NP_READMODE_MESSAGE |
NP_WMESG | /* Write messages */
NP_RMESG | /* Read messages */
0x01, /* Unique instance of
pipe */
sizeof(message), /* Output buffer size */
sizeof(message), /* Input buffer size */
0L); /* Use default
time-out */
// wait for connection
retCode = DosConnectNPipe(PipeHandle);
do {
// connected, read the data
retCode = DosRead(PipeHandle, message,
sizeof(message),&ulBytes);
// display the message
printf("\n\nMessage received was: %s\n\n", message);
..
// end of message
done = 1;
} while (!done);
// end of message, disconnect
rc = DosDisConnectNPipe(PipeHandle);
|
清单 12. Linux 中的无名管道
/*
In this example, thread 1 creates an unnamed pipe, and writes to the
pipe. Thread 2 reads the data once it is available in the unnamed pipe.
*/
Thread 1
int data_processed;
int file_pipes[2];
const char some_data[] = "123";
// Create pipe
pipe(file_pipes);
// Get access to pipe
// Write to pipe
data_processed = write(file_pipes[1], some_data, strlen(some_data));
// Close the write handle
close(file_pipes[1]);
Thread 2
int data_processed;
char buffer[BUFSIZ + 1];
// get access to pipe
..
// read from pipe
data_processed = read(file_pipes[0], buffer, BUFSIZ);
// close the read handle
close(file_pipes[0]);
|
清单 13. Linux 中的有名管道(FIFO)
// Client program
/* This client program logs a message to the FIFO, and closes the
write end when it finds no message to write */
# define FIFO_NAME "/home/guest/testpipe"
# define BUF_SIZE 2552
int retCode = 0 ;
FILE *fd;
int done = 0;
char buff[BUF_SIZE+1];
int dataWrite = 0;
// Open the FIFO with write permission
fd = open (FIFO_NAME, O_WDONLY);
do {
// read from fifo
// display the message
strcpy(buffer, "test message");
dataWrite = write(fd, buffer, BUF_SIZE);
....
// done with all the messages
done =1 ;
} while(!done);
// Close the write end of the FIFO
fclose(fd);
//Server program
/* This Server program creates a FIFO, and waits for a message. If
the message is available, it prints the message to screen.
It closes the read end when it finds no message to read */
# define FIFO_NAME "/home/guest/testpipe"
# define BUF_SIZE 2552
int retCode = 0 ;
FILE *fd;
int done = 0;
char buff[BUF_SIZE+1];
// create a FIFO with user having write and read permission, and
// group and others having read permission.
retCode = mkfifo(FIFO_NAME, S_IWUSR | S_IRUSR |
S_IRGRP | S_IROTH);
// Open the FIFO with read permission
fd = open (FIFO_NAME, O_RDONLY);
do {
// read from fifo
// display the message
dataRead = read(fd, buffer, BUF_SIZE);
// display the message
....
done =1 ;
} while(!done);
fclose(fd);
|
回页首 与文件相关的调用
查询文件的相关信息
OS/2 可以在扩展属性中保存有关文件的其他信息;Linux不支持这种功能。
表 5. 文件信息映射表
OS/2
|
Linux
|
类别
| DosQueryPathInfo | stat |
可映射的
|
获取文件信息
在 OS/2 中,可以使用系统调用 DosQueryPathInfo 来获取有关文件或目录的信息:
APIRET DosQueryPathInfo (pszPathName, ulInfoLevel, pInfoBuf,
cbInfoBuf)
-
pszPathName
是一个指向文件名或目录名的指针。
-
ulInfoLevel
是所需要的信息的级别,其值可以为1、2、3、5;级别 4 是保留值。
-
pInfoBuf
是包含所请求的路径信息级别的存储空间的地址:
-
级别 1:获得文件状态 3,其中包括基本的文件信息,例如创建的日期/时间,最后一次访问的日期/时间,等等。
-
级别 2:获得文件状态 4。这与文件状态 3 相同,另外还多个一个域显示扩展属性的大小。级别 2 用来在读取文件之前查询扩展属性的大小。
-
级别 3:获得有关文件的 EA(扩展属性)的一个子集。这些信息用来:
- 保存文件对象的注释(例如,文件创建者的名字)。
- 对文件对象进行分类(例如,源代码、例子、图标、位图)。
- 描述文件对象中包含的数据的格式(例如,数据记录)。
- 在文件对象中附加上其他数据。
-
级别 4:保留。
-
级别 5: 给出完整的文件名。
-
cbInfoBuf
是存储空间 pInfoBuf 的长度,以字节为单位。
在 Linux 中,可以使用系统调用 stat 来获取有关文件或目录的信息:
int stat (const char *pcPathName, struct stat *pBuf)
-
pcPathName
是一个指向文件名或目录名的指针。
-
pBuf
是一个指向包含文件/目录信息的结构的指针。该结构如下所示:
struct stat {
dev_t st_dev; /* device */
ino_t st_ino; /* inode */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device type (if inode device) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last change */
};
|
请注意,如果另外一个进程正在对文件对象进行更新,而两个这个进程的共享和访问权限存在冲突,那么 DosQueryPathInfo 调用就会失败。为了防止出现这种情况,文件对象必须以一种拒绝写的共享模式打开。
Linux 中的
stat 调用对正在访问的文件不需要任何访问权限,它只需要对该路径中的所有目录具有查找权限即可。
例子
清单 14. 在 OS/2 中查询有关文件的信息
char * pcPathAndCalFileName = "C:/test.Tbl"; // old file name
char * pcPathAndOldFileName = "C:/test.Old"; // new file name
FILESTATUS3 fsStatBuf; // structure filled
by DosQueryPathInfo
//
// Rename the current test file, if it exists, to get ready for
// calibration
if (!DosQueryPathInfo(pcPathAndCalFileName, FIL_STANDARD, &fsStatBuf,
sizeof(fsStatBuf))){
if (!DosQueryPathInfo(pcPathAndOldFileName,
FIL_STANDARD,
&fsStatBuf, sizeof(fsStatBuf))){
// Remove the old calibration data file "test.Old"
remove(&pcPathAndOldFileName[0]);
}
// Rename the current "test.Tbl" test data file to the name
// "test.Old" that is used for old test data.
rename( &pcPathAndCalFileName[0], &pcPathAndOldFileName[0] );
}
|
清单 15. 在 Linux 中查询有关文件的信息
char * pcPathAndCalFileName = "/home/guest/test.tbl"; // old file name
char * pcPathAndOldFileName = "/home/guest/test.old"; // new file name
struct stat buf; // buffer with
// file information
if (stat(pcPathAndCalFileName, &buf) == 0){
// File exists so rename it. Before that check if `test.old' exists.
// If yes then remove that file
if (stat(pcPathAndOldFileName, &buf) == 0){
remove(pcPathAndOldFileName);
// Errors returned by this call should be handled appropriately
}
rename(pcPathAndCalFileName, pcPathAndOldFileName)
// More error handling
}
else
{
//
// File does not exist
//
}
|
回页首 复制文件句柄
有两种方法可以复制文件句柄:重新分配一个新句柄,或者将原来的一个已知句柄用作一个新句柄。
在 OS/2 中,DosDupHandle() 可以同时支持这两种特性。在 Linux 中,如果要分配一个新句柄,可以使用系统调用 dup();如果要将一个已知句柄当作一个新句柄使用,可以使用系统调用 dup2()。
表 6. 复制文件句柄的映射表
OS/2
|
Linux
|
类别
| DosDupHandle | dup
dup2
|
可映射的
|
在 OS/2 中,可以使用系统调用 DosDupHandle() 来复制文件句柄:
APIRET DosDupHandle(HFILE hFile, PHFILE pHfile);
-
hFile
保存要复制的文件句柄。
-
phFile
是一个指向保存句柄信息位置的指针。
可能值如下表所示:
- 0xFFFFFFFF
分配并返回一个新文件句柄。
- 其他值
将该值作为一个新文件句柄分配。有效值可以是分配给标准 I/O 的任何值,或者现在进程打开的文件句柄。
Linux 使用系统调用 dup()。当在 OS/2 中 DosDupHandle() 使用第二个参数(例如 0xFFFFFFFF)时, 0xFFFFFFFF 就表示简单地分配一个新句柄。在 Linux 中,dup 可以实现相同的功能:dup() 系统调用返回一个新的文件描述符;而 dup2() 则复制在文件描述符参数中传递进来的文件描述符:
int dup(int oldfd)
int dup2(int oldfd, int newfd)
-
oldfd
要复制的文件描述符。
-
newfd
oldfd 所复制到的文件描述符。
例子
下面这个例子对 OS/2 和 Linux 中文件句柄的复制进行了比较:
清单 16. 在 OS/2 中复制文件句柄
enum {
StandardOutput = 0x00000001, // for standard output
StandardError = 0x00000002, // for standard error
AllocNewHandle = 0xFFFFFFFF // for allocating a new handle
}
HFILE hfOut, hfErr, hfSaveOut, ulSaveErr;
unsigned long ulRet;
//
// Set the standard handles
//
hfOut = StandardOutput;
hfSaveOut = (HFILE) AllocNewHandle;
hfErr = StandardError;
hfSaveErr = (HFILE) AllocNewHandle;
//
// Save the original handles.
//
DosDupHandle(hfOut, &hfSaveOut);
DosDupHandle(hfErr, &hfSaveErr);
//
// Assign some other file handle or pipe handle to the to the standard
// output & error
//
ulRet = DosDupHandle(hpWrite, &hfOut);
ulRet = DosDupHandle(hpWrite, &hfErr);
:
: Do some processing
:
//
// Retrieve the original handles.
//
ulRet = DosDupHandle(hfSaveOut, &hfOut);
ulRet = DosDupHandle(hfSaveErr, &hfErr);
//
// Close the temporary handles
//
ulRet = DosClose(hfSaveErr);
ulRet = DosClose(hfSaveOut);
|
清单 17. 在 Linux 中复制文件句柄
enum {
StandardOutput = 1, //for standard output
StandardError = 2, //for standard error
}
// Instead of HFILE, in Linux the file descriptor is
// an integer
int fdOut, fdErr, fdSaveOut, fdSaveErr;
int iRet;
//
// Set the standard handles
//
fdOut = StandardOutput;
fdErr = StandardError;
//
// Save the original handles.
//
fdSaveOut = dup(fdOut);
fdSaveErr = dup(fdErr);
//
// Assign some other file handle or pipe handle to the to the
// standard output and error
//
fdOut = dup2 (hpWrite, fdOut);
fdErr = dup2 (hpWrite, fdErr);
:
: Do some processing
:
//
// Retrieve the original handles.
//
fdOut = dup2(fdSaveOut, fdOut);
fdErr = dup2(fdSaveErr, fdErr);
//
// Close the temporary handles
//
close(fdSaveErr);
close(fdSaveOut);
|
修改文件的访问时间
表 7. 文件访问映射表
OS/2
|
Linux
|
类别
| DosSetPathInfo | utime |
可映射的
|
在 OS/2 中,可以使用系统调用 DosSetPathInfo():
APIRET DosSetPathInfo (PSZ pszPathName, ULONG ulInfoLevel,
PVOID pInfoBuf, ULONG cbInfoBuf, ULONG flOptions)
-
pszPathName
是一个指向文件或子目录的完整路径名的 ASCII 值的指针。
-
ulInfoLevel
是正在定义的文件目录信息的级别。有效值可以为 1 和 2。
- pInfoBuf 是一个指向正在设置的信息的指针。
-
级别 1
对于级别 1 来说,传递的是 FILESTATUS3。它包含文件的一些标准信息,例如创建日期/时间,访问日期/时间等。
-
级别 2
对于级别 2 来说,传递的是 EAOP2 结构。这样可以设置文件的扩展属性集。OS/2 将其他信息保存在扩展属性列表中。
DosSetPathInfo() 不但要设置文件或子目录的访问/修改时间,而且要设置其他一些参数,例如文件创建的日期/时间、文件大小以及文件属性。然而,在本节中,我们只讨论如何修改文件的访问/修改时间。因此,我们使用级别 1,并传递一个包含新的访问/修改时间的 FILESTATUS3 结构。
在 Linux 中,可以使用系统调用 utime() 来修改指定文件的访问时间:
int utime(const char * filename, struct utimbuf * buf );
-
filename
是一个指向要修改访问时间的文件的 ASCII 文件名的指针。
-
buf
是一个指向包含该文件的新访问/修改时间结构的指针。utimbuf 结构如下所示:
struct utimbuf {
time_t actime; /* access time */
time_t modtime; /* modification time */
};
|
例子
清单 18. 在 OS/2 中设置文件信息
char *pcFileName= "xyz.ini";
FILESTATUS3 fs3DirInfo;
/* get the file information */
DosQueryPathInfo(pcFileName, FIL_STANDARD, &fs3DirInfo,
sizeof(fs3DirInfo));
/* get the current time */
tm *ptm = localtime(&lTime);
/* update the file information structure with the new modification
time */
fs3DirInfo.fdateLastWrite.day = ptm->tm_mday;
fs3DirInfo.fdateLastWrite.month = ptm->tm_mon + 1;
fs3DirInfo.fdateLastWrite.year = ptm->tm_year - 80;
fs3DirInfo.ftimeLastWrite.twosecs = ptm->tm_sec / 2;
fs3DirInfo.ftimeLastWrite.minutes = ptm->tm_min;
fs3DirInfo.ftimeLastWrite.hours = ptm->tm_hour;
/* set the new file information */
DosSetPathInfo(pcFileName, FIL_STANDARD, &fs3DirInfo,
sizeof(fs3DirInfo), 0);
|
清单 19. 在 Linux 中设置文件的访问时间
struct utimbuf buf;
int iRc;
time_t tTime;
tm *tmr;
/* Get the local time */
tTime = time(0);
localtime_r(&tTime,&tmr);
/* Update the structure with the new modification time */
buf.actime = buf.modtime = mktime(tmr);
/* set the new file information */
iRc = utime("check.txt", &buf);
|
回页首 结束语
本文介绍了将程序从 OS/2 移植到 Linux 上时有关内存管理、IPC 机制和文件处理的一些对应问题。本文的目的并不是要详尽地介绍各个技术细节,而是提供一份将应用程序从 OS/2 移植到 Linux 上时使用的参考手册。本文基于可以达到的适合的移植设计,给出了一些必要的提示和警告,从而帮助简化程序移植的过程。有关本文中提到的 Linux 调用的更详细用法的解释,开发人员可以参考手册页。本系列的下一篇文章将介绍与设备驱动程序接口、定时器及相关问题有关的系统调用的映射。
参考资料
|