- POSIX 接口簡單
- POSIX 用名字代替鍵,用open, close, unlink,和傳統unix文件模型更一致
- POSIX 是引用計數的,簡化了對象刪除,全部進程都關閉了以後對象會被刪除
- POSIX移植性差一些??
- System V IPC 提供了
ipcs
和ipcrm
命令來列出和刪除ipc對象,POSIX IPC不存在這類命令,其是掛載在某處虛擬或真實文件存在的
和System V 消息隊列對比node
- 引用計數
- 有一個關聯的優先級,嚴格按照優先級排序
- 提供了一個特性,在消息可用時,異步的通知進程
#include<fcntl.h>
#include<sys/stat.h>
#include<mqueue.h>
mqd_t mq_open(const char *name, int oflag, .../* mode_t mode, struct mq_attr */);
// oflag 中指定O_CREAT 後須要傳mode(權限掩碼)
int mq_close(mqd_t mqdes);
// 0 s, -1 e
// 若是經過mqdes註冊了消息通知,通知註冊會被刪除
int mq_unlink(const char *name);
// 0 s, -1 e
複製代碼
fork繼承exec註銷linux
struct mq_attr {
long mq_flags; /* Message queue description flags: 0 or O_NONBLOCK [mq_getattr(), mq_setattr()] */
long mq_maxmsg; /* Maximum number of messages on queue [mq_open(), mq_getattr()] */
long mq_msgsize; /* Maximum message size (in bytes) [mq_open(), mq_getattr()] */
long mq_curmsgs; /* Number of messages currently in queue [mq_getattr()] */
};
複製代碼
#include<mqueue.h>
int mq_getattr(mqd_t mqdes, struct mq_attr *attr);
// 0 s, -1 e
int mq_setattr(mqd_t mqdes, const struct mq_attr *newattr, struct mq_attr *oldattr);
// 0 s, -1 e
//SUSv3 規定mq_setattr()只能修改mq_flags, 爲啥是SUSv3規定的???
複製代碼
#include<mqueue.h>
int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);
// 0 s, -1 e
// msg_prio 優先級,0最小
size_t mq_receive(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);
// num of bytes received, -1 error
// 須要msg_len >= mq_msgsize不然報錯EMSGSIZE
#define _XOPEN_SOURCE 600
#include<time.h>
// 和以上一致,只是多了沒有設置O_NONBLOCK標記時的超時時間
int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio, const struct timespec *abs_timeout);
int mq_timedreceive(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio, const struct timespec *abs_timeout);
複製代碼
#include<mqueue.h>
union sigval {
int sival_int; /* Integer value for accompanying data */
void *sival_ptr; /* Pointer value for accompanying data */
};
struct sigevent {
int sigev_notify; /* Notification method */
int sigev_signo; /* Notification signal for SIGEV_SIGNAL */
union sigval sigev_value; /* Value passed to signal handler or thread function*/
void (*sigev_notify_function) (union sigval); /* Thread notification function */
void *sigev_notify_attributes; /* Really 'pthread_attr_t' */
};
int mq_notify(mqd_t mqdes, const struct sigevent *notification);
// 0 s, -1 e
複製代碼
- 任什麼時候刻只有一個進程,可以向特定的消息隊列註冊接收通知,一個消息隊列已經註冊了,以後的會返回EBUSY
- 一條消息進入空的隊列時,註冊進程纔會收到通知(註冊以後要等待變空,再收)
- 註冊進程發送一個通知後會刪除註冊信息
- 若是有別的進程阻塞在mq_receive,收到消息時,是別的進程讀取消息,註冊進程繼續保持註冊
- 一個進程能夠再次調用mq_notify同時傳入NULL的notification參數來撤銷註冊信息
sigev_notify取值shell
- SIGEV_NONE: 不會通知註冊進程,但會刪除註冊信息
- SIGEV_SIGNAL: 經過生成一個sigev_signo字段中指定的信號來通知進程,若是是實時信號,sigev_value附帶數據
- SIGEV_THREAD: sigev_notify_function指定的函數通知進程,sigev_value做爲參數傳遞
- POSIX IPC對象被實現成了虛擬文件系統中的文件
- 能夠經過
mount
掛載mount -t mqueue source target
例如mount -t mqueue none /dev/mqueue
,source 一般是none,會出如今/proc/mounts
上,target
是掛載點
- 命名信號量:經過調用名字open,進程間能夠訪問
- 匿名信號量:若是須要進程間共享時,信號量必須位於共享內存區域,線程間共享時則在相似堆上或全局變量中
#include<fcntl.h>
#include<sys/stat.h>
#include<semaphore.h>
sem_t *sem_open(const char *name, int oflag, .../* mode_t mode, unsigned int value*/)
// value是初始值
// 其餘均相似
int sem_close(sem_t *sem);
int sem_unlink(const char *name);
複製代碼
#include<semaphore.h>
int sem_wait(sem_t *sem);
// 信號量減1,若是不大於0會阻塞
int sem_trywait(sem_t *sem);
// 不會阻塞
#define _XOPEN_SOURCE 600
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
int sem_posy(sem_t *sem);
// 信號量加1
int sem_getvalue(sem_t *sem, int *sval);
//獲取當前值
複製代碼
會多兩個接口編程
#include<semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
複製代碼
pshared代表信號是線程共享仍是進程共享bash
- 0 線程共享,sem得是全局變量地址或者堆地址,進程結束時自動銷燬
- !=0, 進程間共享,sem得是共享內存區域的一個地址(POSIX,System V共享內存,或是內存映射)
- 信號量和pthread互斥體
- POSIX共享內存能讓無關進程共享一個映射區域而無需建立相應的映射文件,linux使用掛載/dev/shm目錄下的tmpfs文件系統,這個文件系統具備內核持久性
- 內存映射和System V 共享內存unix也經過tmpfs來實現,可是不具備內核持久性?
POSIX共享內存對象的操做流程異步
- 經過shm_open打開,獲得文件描述符
- 上一步的文件描述符,作一些fstat,ftruncate操做後傳入到mmap()調用,並在flags參數中指定MAP_SHARED
####共享內存對象的操做函數
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/man.h>
int shm_open(const char *name, int oflag, mode_t mode);
int shm_unlink(const char *name);
複製代碼
- shm_open: olfag 會多一個O_TRUNC,建立後將對象階段爲0
- shm_unlink:刪除指定的共享內存,不會刪除既有的內存映射(內存映射會在munmap()調用後終止)
flock()對整個文件加鎖 fcntl()對一個文件區域加鎖,包含了flock的功能ui
因爲stdio庫會在用戶空間緩衝,須要注意spa
- 使用read,write取代stdio庫執行
- 文件加鎖前刷新,鎖釋放前再刷新一次
- setbuf禁用刷新
#include<sys/file.h>
int flock(int fd, int operation);
// 0 s, -1 e
複製代碼
opeartion 可選參數,未設置非阻塞會一直等到解鎖線程
- LOCK_SH:放置共享鎖
- LOCK_EX:放置排它鎖
- LOCK_UN:解鎖
- LOCK_NB:非阻塞請求
再次調用能夠進行鎖的轉換,可是轉換不必定是原子的
鎖的繼承和釋放
- 鎖會在文件描述符被關閉後自動釋放
- 當一個文件描述符被複制(dup, dup2,fcntl,F_DUPFD操做,fork),鎖會繼承,全部的副本都關閉,或者一個副本解鎖後,鎖會釋放
- 鎖是在進程文件描述符上的,同一個進程open屢次同名文件,不一樣的fd上鎖,後面的會被(阻塞)
- flock()建立的鎖會在exec保留,除非標明瞭close-on-exec字段
限制
- 只能鎖整個文件,粒度粗
- flock只能設置勸告鎖
- 有些NFS實現不識別flock
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
//加鎖時通常調用
struct flock {
short l_type; /* Lock type: F_RDLCK, F_WRLCK, F_UNLCK */
short l_whence; /* How to interpret 'l_start': SEEK_SET,
SEEK_CUR, SEEK_END */
off_t l_start; /* Offset where the lock begins */
off_t l_len; /* Number of bytes to lock; 0 means "until EOF" */
pid_t l_pid; /* Process preventing our lock (F_GETLK only) */
};
fcntl(fd, cmd, &flockstr);
複製代碼
- l_whence:
- SEEK_SET:文件起始位置
- SEEK_CUR:當前位置,l_start能夠爲負數
- SEEK_END:文件結尾,l_start爲負數
- cmd 在設置鎖時爲
- F_SETLK:取決於l_type,若是爲F_RDLCK,R_WRLCK則加鎖,F_UNLCK解鎖,若是區域已經上鎖,會返回EAGAIN
- F_SETLKW:和上面同樣,只是會阻塞,若是正在處理一個信號沒有指定SA_RESTART,操做會被中斷
- F_GETLK:l_type必須爲F_RDLCK,F_WRLCK,檢測可否在flockstr指定區域上鎖,若是能夠,則返回l_type爲F_UNLCK,其餘不變若是不能上鎖,則返回任意一個有衝突的鎖
其餘
- 解鎖總會成功,即便原來沒有鎖
- 同一時刻,一個進程只能在穩健的某個特定區域上一種鎖,在原來鎖住的位置放一把新鎖(同一類型)不會發任何事情,若是是不一樣類型,寫轉讀原子,讀轉寫可能阻塞或者異常
- 死鎖:當內核會對每一個經過F_SETLKW發起的鎖請求檢測是否會致使死鎖,若是會,內核會選中其中一個被阻塞的進程使其fcntl()調用解除阻塞並返回錯誤EDEADLK
- 一個進程對同一個文件加屢次鎖沒法鎖住本身和flock不一樣,即便多個fd
- 鎖會合並和拆分,新鎖和舊鎖有重疊,舊鎖收縮,每一個打開的文件都有一個關聯的鏈表,保存着文件上的鎖
![]()
鎖的繼承和釋放
- fork建立的子進程不會繼承記錄鎖
- 記錄鎖鎖會在exec保留,除非標明瞭close-on-exec字段
- 一個進程中的全部線程共用一個記錄鎖
- 記錄鎖同時和一個進程和i-node關聯,即進程關閉了一個fd後,其餘這個文件的fd上的鎖也會釋放
- linux上使用強制加鎖,須要在掛載時配置ex:
mount -o mand /dev/xxx /testfs
,mount | grep mand
查看哪些文件系統是強制- 文件強制加鎖,經過開啓set-group-ID 位和關閉group-execute位
chomd g+s,g-x /testfs/file
- open,write這些均可能阻塞,
- 儘量避免使用強制鎖
$ cat /proc/locks
序號 鎖類型 鎖模式 讀寫鎖 pid 文件系統主次設備號+inode 起始字節 截止字節
1: POSIX ADVISORY WRITE 458 03:07:133880 0 EOF
2: FLOCK ADVISORY WRITE 404 03:07:133875 0 EOF
3: POSIX ADVISORY WRITE 312 03:07:133853 0 EOF
4: FLOCK ADVISORY WRITE 274 03:07:81908 0 EOF
複製代碼
- 鎖類型,FLOCK表示flock()建立,POSIX表示fcntl()建立
- 鎖模式,ADVISORY或者MANDATORY
- 若是序號前有
->
代表阻塞
找到一個進程給什麼文件上了鎖
ps -p $pid
ls -li /dev/ | awk '$6="主設備號,"&& $7=次設備號'
mount | grep 找到的設備
找到掛載點find 掛載點 -mount -inum $inode
找到文件
其餘
/var/run
目錄一般放置一些單例daemon進程的鎖文件,將進程id寫入鎖文件,$proc.pid命名