首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 系统运维 >

信号量移栽

2013-10-18 
信号量移植信号量移植讲完了前面关于多线程的基础知识后,说一下我最近关于移植的一些体会.将win32程序关于

信号量移植

信号量移植

讲完了前面关于多线程的基础知识后,说一下我最近关于移植的一些体会.

将win32程序关于多线程的内容移植到Linux下面,不能简单的按照函数对应来移植.不过通过下面的对应关系,再加上你对这些模式的深入了解,相信会移植的很成功.

Windows信号量是一些计数器变量,允许有限个线程/进程访问共享资源.LinuxPOSIX信号量也是一些计数器变量,可以用来在Linux上实现Windows上的信号量功能.

信号量的类型: Windows提供了有名(named)信号量和无名(unnamed)信号量.有名信号量可以在进程之间进行同步.

在 Linux上,在相同进程的不同线程之间,则只使用POSIX信号量.在进程之间,可以使用SystemV信号量.

等待函数中的超时:当在一个等待函数中使用时,可以为Windows信号量对象指定超时值.在Linux中,并没有提供这种功能,只能通过应用程序逻辑处理超时的问题.

在 Windows中,可以使用CreateSemaphore()创建或打开一个有名或无名的信号量.

HANDLECreateSemaphore(


LPSECURITY_ATTRIBUTESlpSemaphoreAttributes,


LONGlInitialCount,


LONGlMaximumCount,


LPCTSTRlpName


);


在这段代码中:

lpSemaphoreAttributes是一个指向安全性属性的指针.如果这个指针为空,那么这个信号量就不能被继承.

lInitialCount是该信号量的初始值.

lMaximumCount是该信号量的最大值,该值大于0.

lpName是信号量的名称.如果该值为NULL,那么这个信号量就只能在相同进程的不同线程之间共享.否则,就可以在不同的进程之间进行共享.

这个函数创建信号量,并返回这个信号量的句柄.它还将初始值设置为调用中指定的值.这样就可以允许有限个线程来访问某个共享资源.

在 Linux中,可以使用sem_init()来创建一个无名的POSIX信号量,这个调用可以在相同进程的线程之间使用.它还会对信号量计数器进行初始化:intsem_init(sem_t *sem, int pshared, unsigned int value).在这段代码中:

value(信号量计数器)是这个信号量的初始值.

pshared可以忽略,因为在目前的实现中,POSIX信号量还不能在进程之间进行共享.

这里要注意的是,最大值基于demaphore.h头文件中定义的SEM_VALUE_MAX.

在Linux中,semget()用于创建SystemV信号量,它可以在不同进程之间使用.可以用它来实现与Windows中有名信号量相同的功能.这个函数返回一个信号量集标识符,它与一个参数的键值关联在一起.当创建一个新信号量集时,对于与semid_ds数据结构关联在一起的信号量,semget()要负责将它们进行初始化,方法如下:

sem_perm.cuid和sem_perm.uid被设置为调用进程的有效用户ID.

sem_perm.cgid和sem_perm.gid被设置为调用进程的有效组ID.

sem_perm.mode的低9位被设置为semflg的低9位.

sem_nsems被设置为nsems的值.

sem_otime被设置为0.

sem_ctime被设置为当前时间.

用来创建 SystemV信号量使用的代码是:

intsemget(key_t key, int nsems, int semflg).

下面是对这段代码的一些解释:

key是一个惟一的标识符,不同的进程使用它来标识这个信号量集.我们可以使用ftok()生成一个惟一的键值.IPC_PRIVATE是一个特殊的key_t值;当使用IPC_PRIVATE作为key时,这个系统调用就会只使用semflg的低9位,但却忽略其他内容,从而新创建一个信号量集(在成功时).

nsems是这个信号量集中信号量的数量.

 semflg是这个新信号量集的权限.要新创建一个信号量集,您可以将使用IPC_CREAT来设置位操作或访问权限.如果具有该key值的信号量集已经存在,那么IPC_CREAT/IPC_EXCL标记就会失败.

注意,在SystemV信号量中,key被用来惟一标识信号量;在Windows中,信号量是使用一个名称来标识的.

为了对信号量集数据结构进行初始化,可以使用IPC_SET命令来调用semctl()系统调用.将arg.buf所指向的semid_ds数据结构的某些成员的值写入信号量集数据结构中,同时更新这个结构的sem_ctimemember的值.用户提供的这个arg.buf所指向的semid_ds结构如下所示:

sem_perm.uid

sem_perm.gid

sem_perm.mode(只有最低9位有效)

调用进程的有效用户 ID应该是超级用户,或者至少应该与这个信号量集的创建者或所有者匹配:

intsemctl(int semid, int semnum, int cmd = IPC_SET, ...).

在这段代码中:

semid是信号量集的标识符.

semnum是信号量子集偏移量(从0到nsems-1,其中n是这个信号量集中子集的个数).这个命令会被忽略.

 cmd是命令;它使用IPC_SET来设置信号量的值.

 args是这个信号量集数据结构中要通过IPC_SET来更新的值(在这个例子中会有解释).

最大计数器的值是根据在头文件中定义的SEMVMX来决定的.

在 Windows中,我们使用OpenSemaphore()来打开某个指定信号量.只有在两个进程之间共享信号量时,才需要使用信号量.在成功打开信号量之后,这个函数就会返回这个信号量的句柄,这样就可以在后续的调用中使用它了.

HANDLEOpenSemaphore(


DWORDdwDesiredAccess,


BOOLbInheritHandle,


LPCTSTRlpName


)


在这段代码中:

dwDesiredAccess是针对该信号量对象所请求的访问权.

bInheritHandle是用来控制这个信号量句柄是否可继承的标记.如果该值为TRUE,那么这个句柄可以被继承.

lpName是这个信号量的名称.

在 Linux中,可以调用相同的semget()来打开某个信号量,不过此时semflg的值为0:intsemget(key,nsems,0).在这段代码中:

key应该指向想要打开的信号量集的key值.

为了打开一个已经存在的信号量,可以将nsems和标记设置为0.semflg值是在返回信号量集标识符之前对访问权限进行验证时设置的.

在 Windows中,等待函数提供了获取同步对象的机制.可以使用的等待函数有多种类型;在这一节中,我们只考虑WaitForSingleObject()(其他类型将会分别进行讨论).这个函数使用一个信号量对象的句柄作为参数,并会一直等待下去,直到其状态变为有信号状态或超时为止.

DWORDWaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds );

在这段代码中:

hHandle是指向互斥句柄的指针.

dwMilliseconds是超时时间,以毫秒为单位.如果该值是INFINITE,那么它阻塞调用线程/进程的时间就是不确定的.

在Linux中,sem_wait()用来获取对信号量的访问.这个函数会挂起调用线程,直到这个信号量有一个非空计数为止.然后,它可以原子地减少这个信号量计数器的值:intsem_wait(sem_t * sem).

在 POSIX信号量中并没有超时操作.这可以通过在一个循环中执行一个非阻塞的sem_trywait()实现,该函数会对超时值进行计算:intsem_trywait(sem_t * sem).

在使用 SystemV信号量时,如果通过使用IPC_SET命令的semctl()调用设置初始的值,那么必须要使用semop()来获取信号量.semop()执行操作集中指定的操作,并阻塞调用线程/进程,直到信号量值为0或更大为止:intsemop(int semid, struct sembuf *sops, unsigned nsops).

函数 semop()原子地执行在sops中所包含的操作 ——也就是说,只有在这些操作可以同时成功执行时,这些操作才会被同时执行.sops所指向的数组中的每个nsops元素都使用structsembuf指定了一个要对信号量执行的操作,这个结构包括以下成员:

unsignedshort sem_num;(信号量个数)

shortsem_op;(信号量操作)

shortsem_flg;(操作标记)

要获取信号量,可以通过将sem_op设置为-1来调用

semop();在使用完信号量之后,可以通过将sem_op设置为1来调用semop()释放信号量.通过将sem_op设置为-1来调用semop(),信号量计数器将会减小1,如果该值小于0(信号量的值是不能小于0的),那么这个信号量就不能再减小,而是会让调用线程/进程阻塞,直到其状态变为有信号状态为止.

sem_flg中可以识别的标记是IPC_NOWAIT和SEM_UNDO.如果某一个操作被设置了SEM_UNDO标记,那么在进程结束时,该操作将被取消.如果sem_op被设置为0,那么semop()就会等待semval变成0.这是一个“等待为0”的操作,可以用它来获取信号量.

记住,超时操作在SystemV信号量中并不适用.这可以在一个循环中使用非阻塞的semop()(通过将sem_flg设置为IPC_NOWAIT)实现,这会计算超时的值.

在 Windows中,ReleaseSemaphore()用来释放信号量.

BOOLReleaseSemaphore(


HANDLEhSemaphore,


LONGlReleaseCount,


LPLONGlpPreviousCount


);


在这段代码中:

hSemaphore是一个指向信号量句柄的指针.

lReleaseCount是信号量计数器,可以通过指定的数量来增加计数.

lpPreviousCount是指向上一个信号量计数器返回时的变量的指针.如果并没有请求上一个信号量计数器的值,那么这个参数可以是NULL.

这个函数会将信号量计数器的值增加在lReleaseCount中指定的值上,然后将这个信号量的状态设置为有信号状态.

在 Linux中,我们使用sem_post()来释放信号量.这会唤醒对这个信号量进行阻塞的所有线程.信号量的计数器同时被增加1.要为这个信号量的计数器添加指定的值(就像是Windows上一样),可以使用一个互斥变量多次调用以下函数:intsem_post(sem_t * sem).

对于 SystemV信号量来说,只能使用semop()来释放信号量:

intsemop(int semid, struct sembuf *sops, unsigned nsops).

函数 semop()原子地执行sops中包含的一组操作(只在所有操作都可以同时成功执行时,才会将所有的操作同时一次执行完).sops所指向的数组中的每个nsops元素都使用一个structsembuf

结构指定了一个要对这个信号量执行的操作,该结构包含以下元素:

unsignedshort sem_num;(信号量个数)

shortsem_op;(信号量操作)

shortsem_flg;(操作标记)

要释放信号量,可以通过将sem_op设置为1来调用semop().通过将semop()设置为1来调用semop(),这个信号量的计数器会增加1,同时用信号通知这个信号量.


在 Windows中,我们使用CloseHandle()来关闭或销毁信号量对象.

BOOLCloseHandle(


HANDLEhObject


);


hObject是指向这个同步对象句柄的指针.

在 Linux中,sem_destroy()负责销毁信号量对象,并释放它所持有的资源:intsem_destroy(sem_t *sem).

对于 SystemV信号量来说,只能使用semctl()函数的IPC_RMID命令来关闭信号量集:

intsemctl(int semid, int semnum, int cmd = IPC_RMID, ...)


这个命令将立即删除信号量集及其数据结构,并唤醒所有正在等待的进程(如果发生错误,则返回,并将errno设置为EIDRM).调用进程的有效用户ID是超级用户,或者可以与该信号量集的创建者或所有者匹配的用户.参数semnum会被忽略. 

热点排行