兩個(gè)甚至多個(gè)進(jìn)程使用共享內(nèi)存(shm)通信,總遇到同步問題。這里的“同步問題”不是說進(jìn)程讀寫同步問題,這個(gè)用信號(hào)量就好了。這里的同步問題說的是同步退出問題,到底誰先退出,怎么知道對(duì)方退出了。舉個(gè)例子:進(jìn)程負(fù)責(zé)讀寫數(shù)據(jù)庫A,進(jìn)程B負(fù)責(zé)處理數(shù)據(jù)。那么進(jìn)程A得比進(jìn)程B晚退出才行,因?yàn)橐4孢M(jìn)程B處理完的數(shù)據(jù)??墒牵敛恢溃率裁磿r(shí)候退出啊。A、B是無關(guān)聯(lián)的進(jìn)程,也不知道對(duì)方的pid。它們唯一的關(guān)聯(lián)就是讀寫同一塊共享內(nèi)存。正常情況下,進(jìn)程B在共享內(nèi)存中寫個(gè)標(biāo)識(shí):進(jìn)程A你可以退出了,也是可以的。不過進(jìn)程B可能是異常退出,連標(biāo)識(shí)都來不及寫。其次,共享內(nèi)存用來做數(shù)據(jù)通信的,加這么個(gè)標(biāo)識(shí)感覺不太好,有濫用的感覺。
采用socket通信沒有這個(gè)問題,因?yàn)檫M(jìn)程B退出怎么也會(huì)導(dǎo)致socket斷開,哪怕是超時(shí)。但shm卻沒有協(xié)議來檢測(cè)這些行為,如果自己也做一個(gè)未免太麻煩。那就從共享內(nèi)存下手吧。
共享內(nèi)存是由內(nèi)核來管理的,一個(gè)進(jìn)程刪除本身打開的共享內(nèi)存并不影響另一個(gè)進(jìn)程的共享內(nèi)存,哪怕都是同一塊共享內(nèi)存。這是因?yàn)楣蚕韮?nèi)存在內(nèi)核中一個(gè)引用計(jì)數(shù),一個(gè)進(jìn)程使用該共享內(nèi)存就會(huì)導(dǎo)致引用計(jì)數(shù)加1。如果其中一個(gè)進(jìn)程調(diào)用了刪除函數(shù),只有這個(gè)計(jì)數(shù)為0才會(huì)真正刪除共享內(nèi)存。那么,需要最后才退出的進(jìn)程檢測(cè)這個(gè)計(jì)數(shù)就可以了。
在System V的共享內(nèi)存中,創(chuàng)建一個(gè)共享內(nèi)存會(huì)初始化一個(gè)結(jié)構(gòu):
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
使用shmctl函數(shù)可以讀取該結(jié)構(gòu)體,其中的shm_nattch就是使用該共享內(nèi)存的進(jìn)程數(shù)。
不過,現(xiàn)在有了新的POSIX標(biāo)準(zhǔn),當(dāng)然要用新標(biāo)準(zhǔn)了。shm_open創(chuàng)建的共享內(nèi)存也具有“一個(gè)進(jìn)程刪除本身打開的共享內(nèi)存并不影響另一個(gè)進(jìn)程的共享內(nèi)存”的特點(diǎn)??墒怯胹hm_open創(chuàng)建的共享內(nèi)存不再有上面的結(jié)構(gòu),那么,內(nèi)核是怎么管理shm_open創(chuàng)建共享內(nèi)存??看下面的源碼:
/* shm_open - open a shared memory file *//p>
p>/* Copyright 2002, Red Hat Inc. *//p>
p>#include sys/types.h>
#include sys/mman.h>
#include unistd.h>
#include string.h>
#include fcntl.h>
#include limits.h>/p>
p>int
shm_open (const char *name, int oflag, mode_t mode)
{
int fd;
char shm_name[PATH_MAX+20] = "/dev/shm/";/p>
p> /* skip opening slash */
if (*name == '/')
++name;/p>
p> /* create special shared memory file name and leave enough space to
cause a path/name error if name is too long */
strlcpy (shm_name + 9, name, PATH_MAX + 10);/p>
p> fd = open (shm_name, oflag, mode);/p>
p> if (fd != -1)
{
/* once open we must add FD_CLOEXEC flag to file descriptor */
int flags = fcntl (fd, F_GETFD, 0);/p>
p> if (flags >= 0)
{
flags |= FD_CLOEXEC;
flags = fcntl (fd, F_SETFD, flags);
}/p>
p> /* on failure, just close file and give up */
if (flags == -1)
{
close (fd);
fd = -1;
}
}/p>
p> return fd;
}
我嚓,這就是創(chuàng)建一個(gè)普通的文件啊,只是創(chuàng)建的位置在/dev/shm下(也就是RAM上)。再來看看刪除共享內(nèi)存的函數(shù)shm_unlink:
/* shm_unlink - remove a shared memory file *//p>
p>/* Copyright 2002, Red Hat Inc. *//p>
p>#include sys/types.h>
#include sys/mman.h>
#include unistd.h>
#include string.h>
#include limits.h>/p>
p>int
shm_unlink (const char *name)
{
int rc;
char shm_name[PATH_MAX+20] = "/dev/shm/";/p>
p> /* skip opening slash */
if (*name == '/')
++name;/p>
p> /* create special shared memory file name and leave enough space to
cause a path/name error if name is too long */
strlcpy (shm_name + 9, name, PATH_MAX + 10);/p>
p> rc = unlink (shm_name);/p>
p> return rc;
}
這也只是一個(gè)普通的unlink函數(shù)。也就是說,POSIX標(biāo)準(zhǔn)的共享內(nèi)存就是一個(gè)文件。所謂的“一個(gè)進(jìn)程刪除本身打開的共享內(nèi)存并不影響另一個(gè)進(jìn)程的共享內(nèi)存”就相當(dāng)于你用fstream對(duì)象打開了一個(gè)文件,然后去文件夾把文件刪除了(也就是對(duì)文件進(jìn)行了unlink操作),可是fstream對(duì)象還可以正常讀寫文件,并沒有什么引用計(jì)數(shù)。這下好了,進(jìn)程退出時(shí)又沒法同步了。
不過,在linux下怎么會(huì)有解決不了的問題呢?解決不了只能說明自己太菜。既然是文件,那就從文件下手。那文件有什么是原子操作,又可以計(jì)數(shù)的呢。答案:硬鏈接。比如:
xzc@xzc-HP-ProBook-4446s:/dev/shm$ stat abc
文件:"abc"
大?。? 塊:8 IO 塊:4096 普通文件
設(shè)備:15h/21d Inode:5743159 硬鏈接:1
權(quán)限:(0664/-rw-rw-r--) Uid:( 1000/ xzc) Gid:( 1000/ xzc)
最近訪問:2015-01-25 21:27:00.961053098 +0800
最近更改:2015-01-25 21:27:00.961053098 +0800
最近改動(dòng):2015-01-25 21:27:00.961053098 +0800
創(chuàng)建時(shí)間:-
xzc@xzc-HP-ProBook-4446s:/dev/shm$
這個(gè)硬鏈接可以通過fstat函數(shù)獲取??墒且@樣實(shí)現(xiàn)的話,意味著需要先創(chuàng)建一塊共享內(nèi)存,每個(gè)進(jìn)程引用的時(shí)候需要調(diào)用link函數(shù)來創(chuàng)建一個(gè)硬鏈接。問題解決了,可是這樣會(huì)在/dev/shm下多個(gè)N多個(gè)文件。這可是RAM啊,雖然現(xiàn)在的服務(wù)器都比較牛,但這樣做也不太好吧。好吧,還有一個(gè)flock文件鎖。flock使用LOCK_SH參數(shù)多個(gè)進(jìn)程對(duì)同一個(gè)文件加鎖。這樣,進(jìn)程B初始化共享內(nèi)存時(shí)加鎖(可以有多個(gè)這樣的進(jìn)程),在退出(包括異常退出)時(shí)解鎖。進(jìn)程A在退出時(shí)檢測(cè)這個(gè)鎖。當(dāng)發(fā)現(xiàn)無鎖時(shí)說明可以安全退出了。
同步退出的問題基本解決了。來不及寫代碼去驗(yàn)證,下次吧。
PS:內(nèi)核unlink時(shí)應(yīng)該也是有計(jì)數(shù)才知道當(dāng)前有沒有進(jìn)程打開文件,在什么時(shí)候應(yīng)該刪除文件。這個(gè)還得去查資料,看用不用得上。另外lsof這個(gè)工具是可以檢測(cè)到所有打開該共享內(nèi)存的進(jìn)程及相應(yīng)的狀態(tài)。這個(gè)應(yīng)該也是有對(duì)應(yīng)的api的,只是現(xiàn)在還沒搞懂。