关于sem_open(3)

今天新闻组上有人问到这么个问题,为什么sem_open(“/tmp/nimeni”,O_CREAT|O_EXCL,SHM_MODE,1);总是得到ENOENT?

翻开sem_open的man手册看看,我们大体就会知道,第一个参数有问题,man手册中这么说:

sem_open(3):

sem_open() creates a new POSIX semaphore or opens an existing
semaphore. The semaphore is identified by name. For details of
the construction of name, see sem_overview(7).

sem_overview(7):

Named semaphores
A named semaphore is identified by a name of the form
/somename. Two processes can operate on the same named
semaphore by passing the same name to sem_open(3).
我看了下面一个人的引用,发现我实在是没看man手册的必要了,这里的描述有问题!所以我找到了sem_open()在glibc中的实现(nptl/sem_open.c):
[c]
//….
if (mountpoint.dir == NULL)
{
__set_errno (ENOSYS);
return SEM_FAILED;
}

/ Construct the filename. /
while (name[0] == ‘/‘)
++name;

if (name[0] == ‘’)
{
/ The name “/“ is not supported. /
__set_errno (EINVAL);
return SEM_FAILED;
}
size_t namelen = strlen (name) + 1;

/ Create the name of the final file. /
finalname = (char *) alloca (mountpoint.dirlen + namelen);
mempcpy (mempcpy (finalname, mountpoint.dir, mountpoint.dirlen),
name, namelen);

/ If the semaphore object has to exist simply open it. /
if ((oflag & O_CREAT) == 0 || (oflag & O_EXCL) == 0)
{
try_again:
fd = __libc_open (finalname,
(oflag & ~(O_CREAT|O_ACCMODE)) | O_NOFOLLOW | O_RDWR);

//….
[/c]

所以这里的问题其实很简单,最根本的问题是那个path参数就究竟是用来干什么的?用一句话说,其实就是指定一个文件位置,这个位置是以tmpfs的挂载点为根目录的。也就是说,如果它以/开头,说明是从tmpfs根目录开始,而如果不是,就是从相对路径开始,不过“当前目录”也是根目录(这正是为什么sem_open()的实现中会去掉开头的/)。到了这里我们可以看出一开始那个问题的原因了,它传递的是”/tmp/nimeni”,也就假设了tmpfs的挂载目录(通常是/dev/shm)下有tmp这个目录,而事实上没有,所以即使他加上了O_CREAT|O_EXCL也会返回ENOENT。

所以,传递给sem_open()的第一个参数究竟怎么写才好呢?”/somename”和”somename”都可以,都容易读而且也都对,但是名字中间有/就不行了。

从上面我们也可以看出,对于程序员来说,有些时候啊,读文档真的还不如直接去读代码来得更快!所以这也提示我们:代码要写得像文档一样易读!我坚信好的代码就应该如此!