System V的信号量是一个庞大而且复杂的IPC模块,对于它的使用也是相对比较困难的,我的建议是使用posix.1定义的信号量,它简单易用,并且移植性好。为了便于日后对posix.1信号量的讨论,这片文章主要讨论如何使用System V的信号量的使用方法,下面一段程序简单的演示了一般的使用方法:
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>
int v7_sem_create()
{
key_t sem_set_key = ftok( "/tmp", 'S' );
int sem_set_id = semget( sem_set_key, 1, IPC_CREAT | IPC_EXCL | 0666 );
if ( sem_set_id != -1 )
{
if ( semctl( sem_set_id, 0, SETVAL, 1 ) == -1 )
{
perror( "[v7_sem_create]" );
semctl( sem_set_id, 0, IPC_RMID );
sem_set_id = -1;
}
}
else
{
perror( "[v7_sem_create]" );
}
return sem_set_id;
}
int v7_sem_get()
{
key_t sem_set_key = ftok( "/tmp", 'S' );
int sem_set_id = semget( sem_set_key, 1, 0666 );
if ( sem_set_id == -1 )
perror( "[v7_sem_get]" );
return sem_set_id;
}
int v7_sem_destroy( int sem_set_id )
{
int rs = semctl( sem_set_id, 0, IPC_RMID );
if ( rs == -1 )
perror( "[v7_sem_destroy]" );
return rs;
}
int v7_sem_enter( int sem_set_id )
{
struct sembuf sem;
int rs;
sem.sem_num = 0;
sem.sem_op = -1;
sem.sem_flg = SEM_UNDO;
rs = semop( sem_set_id, &sem, 1 );
if ( rs == -1 )
perror( "[v7_sem_enter]" );
return rs;
}
int v7_sem_exit( int sem_set_id )
{
struct sembuf sem;
int rs;
sem.sem_num = 0;
sem.sem_op = 1;
sem.sem_flg = SEM_UNDO;
rs = semop( sem_set_id, &sem, 1 );
if ( rs == -1 )
perror( "[v7_sem_exit]" );
return rs;
}
void v7_show_status( pid_t pid, int status )
{
int flag = 1;
printf( "[v7_show_status]: pid = %ul => ", pid );
if ( WIFEXITED( status ) )
{
flag = 0;
printf( "true if the child terminated normally, that is, "
"by calling exit() or _exit(), or "
"by returning from main()./n" );
}
if ( WEXITSTATUS( status ) )
{
flag = 0;
printf( "evaluates to the least significant eight bits of the "
"return code of the child which terminated, which may "
"have been set as the"
"argument to a call to exit() or _exit() or as the argument for a"
"return statement in the main program. This macro can only be"
" evaluated if WIFEXITED returned true. /n" );
}
if ( WIFSIGNALED( status ) )
{
flag = 0;
printf( " true if the child process terminated because of a signal"
" which was not caught./n" );
}
if ( WTERMSIG( status ) )
{
flag = 0;
printf( " the number of the signal that caused the child process"
"to terminate. This macro can only be evaluated if WIFSIGNALED"
"returned non-zero./n");
}
if ( WIFSTOPPED( status ) )
{
flag = 0;
printf( " true if the child process which caused the return is"
"currently stopped; this is only possible if the call was done"
"using WUNTRACED or when the child is being traced (see"
"ptrace(2))./n" );
}
if ( WSTOPSIG( status ) )
{
flag = 0;
printf( " the number of the signal which caused the child to stop."
"This macro can only be evaluated if WIFSTOPPED returned"
"non-zero./n" );
}
if ( flag )
{
printf( "Unknown status = 0x%X/n", status );
}
}
void v7_do_something( void )
{
int i;
int j;
for ( i = 0, j = 0; i < 10000; ++ i )
{
if ( i % 2 == 0 )
{
j += 2;
}
if ( i % 4 != 0 )
{
j -= 4;
}
if ( i % 6 != 0 )
{
j += 6;
}
if ( i % 8 == 0 )
{
j -= 8;
}
if ( i % 10 == 0 )
{
j += 10;
}
}
i -= j;
}
void v7_child1( void )
{
int sem_set_id;
int i;
sem_set_id = v7_sem_get();
if ( sem_set_id == -1 )
return;
for ( i = 0; i < 10; ++ i )
{
v7_sem_enter( sem_set_id );
printf( "[v7_child1]: enter semaphore/n" );
v7_sem_exit( sem_set_id );
v7_do_something();
}
}
void v7_child2( void )
{
int sem_set_id;
int i;
sem_set_id = v7_sem_get();
if ( sem_set_id == -1 )
return;
for ( i = 0; i < 10; ++ i )
{
v7_sem_enter( sem_set_id );
printf( "[v7_child2]: enter semaphore/n" );
v7_sem_exit( sem_set_id );
v7_do_something();
}
}
int main( void )
{
int sem_set_id;
int status;
pid_t pid1;
pid_t pid2;
// create semaphore
sem_set_id = v7_sem_create();
// acquire semaphore to synchronize children
v7_sem_enter( sem_set_id );
pid1 = fork();
if ( pid1 < 0 )
printf( "[main]: fail to fork child1/n" );
else if ( pid1 == 0 ) // child process
{
v7_child1();
return 0;
}
pid2 = fork();
if ( pid2 < 0 )
printf( "[main]: fail to fork child2/n" );
else if ( pid2 == 0 ) // child process
{
v7_child2();
return 0;
}
// parent process
// Release semaphore
v7_sem_exit( sem_set_id );
if ( waitpid( pid1, &status, 0 ) != pid1 )
perror( "[main, wait child1]" );
v7_show_status( pid1, status );
if ( waitpid( pid2, &status, 0 ) != pid2 )
perror( "[main, wait child2]" );
v7_show_status( pid2, status );
v7_sem_destroy( sem_set_id );
return 0;
}
System V的信号量的主要缺点是:
1)semget和semctl操作过于复杂,并且不是原子操作,也就是说v7_sem_create函数是多进程不安全的。
2)信号量的申请和释放是通过信号量集的id来控制的,相比于posix.1中的信号量,显的过于复杂,不易理解。
3)仅仅有IPC_NOWAIT操作,无法实现固定时间的超时机制。对于GNU定义的
int semtimedop(int semid, struct sembuf *sops, unsigned nsops, struct timespec *timeout);对于很多unix是不支持的。
System V信号量使用的注意点:
1)在调用semop时候,SEM_UNDO最好被设置在flg中,否则一旦使用进程异常死去,所占用的信号量资源不会被释放。
2)System V的信号量是kernal的持久性资源,只有reboot后才能释放。所以当全部进程退出时候,应该要删除它,否则消耗一定的资源。
3)之用IPC_CREAT 创建信号量集合而不用 IPC_EXCL的话,创建者无法确定是否获取的信号量集是否是新创建的还是已经存在,所以在用IPC_CREAT时最好追加IPC_EXCL。如果是仅仅获取信号量集合,那么这两个标记都不要加。
4)在使用semget函数时候,如果信号量集合已经存在,并且熟知他的访问属性的话,semflg可以设置为0,比如说int sem_set_id = semget( sem_set_key, 1, 0 );不过我建议最好明确的制定,比如:int sem_set_id = semget( sem_set_key, 1, 0666 );这样代码很清晰。