双向情感障碍怎么治:将应用程序从 OS/2 迁移到 Linux 上: 第 2 部分,内存管理、IPC 和文件处...

来源:百度文库 编辑:偶看新闻 时间:2024/04/26 09:07:34



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 特定于上下文的DosFreeMemfree
delete(在用 new 分配的情况下) 可映射的DosAllocSegmalloc/new可映射的DosFreeSegfree/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 可映射的DosFreeMemshmdt
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 值可以用来模拟 DosAllocSharedMempszName 参数的行为。如果 shmflgIPC_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.cuidshm_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 itduring allocation. It also writes to it, and shows how other processes canaccess 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    #include    #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    #include    #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 类别 DosQuerySysInfosysinfo 可映射的

在 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)

  • hReadPipehWritePipe
    分别是指向管道的读句柄和写句柄的指针。
  • 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
    是一组标志,定义了管道的模式。
  • cbOutbufcbInbuf
    分别是为外发(从服务器端到客户端)缓冲区和内达(从客户端到服务器端)缓冲区分配的字节数。
  • 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)

  • 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 类别 DosQueryPathInfostat 可映射的

获取文件信息


在 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 类别 DosDupHandledup
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 类别 DosSetPathInfoutime 可映射的

在 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 调用的更详细用法的解释,开发人员可以参考手册页。本系列的下一篇文章将介绍与设备驱动程序接口、定时器及相关问题有关的系统调用的映射。


参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文.

  • 请确保您阅读了本系列的第一篇, 将应用程序从 OS/2 迁移到 Linux 上:第 1 部分,线程、互斥锁和信号量( developerWorks,2004 年 2 月)。



  • H.M. Deitel 和 M.S. Kogan 所著的 TheDesign of OS/2 (Addison-Wesley, 1992 年)仍然可以在使用书籍站点上找到;ISBN 为0-201-54889-5。



  • 有关 Linux 中编程调用的更多细节或具体的调用,不要忘记查看 Linux 中的“info”和手册页。



  • C++ 程序员可以参考 mappingbetween IBM Open class (visual age) and STL classes (open standards)。


     
  • IBM 的 OS/2Strategy for 2004推荐了一个到 WebSphere软件平台 的转换。



  • 大部分 IBMeServer服务器现在都捆绑了 Linux。



  • IBM 红皮书为那些希望切换到 Windows 或 Linux 上的 OS/2 管理员提供了一份 OS/2Server Transition。



  • IBM Global Services LinuxPorting Service Practice提供了一个 OS/2 到 Linux 定制应用估价、移植和测试服务。



  • 将 OS/2 应用程序移植到 Linux(用 C 语言)( developerWorks,2002年 3 月)介绍了 Linux 团队在将 LANDP(LAN 分布式平台)从 OS/2 移植到 Linux 上时,LANDP 所碰到的一些问题。



  • 您也可以在同一台机器上同时使用 OS/2 和 Linux。请参考南加利福尼亚州的 OS/2 用户组 的 Howto use Linux and OS/2 Together资源列表,以及 SUSE LINUX 入门中的 OS/2and Linux with the OS/2 Boot Manager对双重引导的一个简要介绍。



  • 在 IBMdeveloperWorks Linux专区可以找到为 Linux 开发人员准备的更多参考资料。



  • 在 Developer Bookstore 的 Linux 区可以购买 有关Linux 的打折书籍。



  • 从 developerWorks 的 为您的Linux 应用程序加油提速 部分,下载运行在 Linux 上的 developerWorks 订阅产品的免费试用版,包括 WebSphereStudio Site Developer、WebSphere SDK for Web services、WebSphere ApplicationServer、DB2 Universal Database Personal Developers Edition、Tivoli AccessManager 以及 Lotus Domino Server。为了更容易上手,请自行收集每个产品的 how-to 文档和技术支持。