- 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命名