linux/unix编程手册-51_55

288 阅读8分钟

title: linux/unix编程手册-51_55 date: 2018-10-03 11:53:07 categories: programming tags: tips

linux/unix编程手册-51(POSIX IPC 介绍)

System V IPC 和 POSIX IPC比较

  • POSIX 接口简单
  • POSIX 用名字代替键,用open, close, unlink,和传统unix文件模型更一致
  • POSIX 是引用计数的,简化了对象删除,所有进程都关闭了之后对象会被删除
  • POSIX移植性差一些??
  • System V IPC 提供了 ipcsipcrm命令来列出和删除ipc对象,POSIX IPC不存在这类命令,其是挂载在某处虚拟或真实文件存在的

linux/unix编程手册-52(POSIX 消息队列)

和System V 消息队列对比

  • 引用计数
  • 有一个关联的优先级,严格按照优先级排序
  • 提供了一个特性,在消息可用时,异步的通知进程

打开,关闭和断开消息队列

#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注销

描述符和消息队列的关系(类似文件描述符和文件)

消息队列特性

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取值

  • SIGEV_NONE: 不会通知注册进程,但会删除注册信息
  • SIGEV_SIGNAL: 通过生成一个sigev_signo字段中指定的信号来通知进程,如果是实时信号,sigev_value附带数据
  • SIGEV_THREAD: sigev_notify_function指定的函数通知进程,sigev_value作为参数传递

linux特有特性

  • POSIX IPC对象被实现成了虚拟文件系统中的文件
    • 可以通过mount挂载mount -t mqueue source target例如 mount -t mqueue none /dev/mqueue,source 通常是none,会出现在/proc/mounts上,target是挂载点

linux/unix编程手册-53(POSIX 信号量)

  • 命名信号量:通过调用名字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表明信号是线程共享还是进程共享

  • 0 线程共享,sem得是全局变量地址或者堆地址,进程结束时自动销毁
  • !=0, 进程间共享,sem得是共享内存区域的一个地址(POSIX,System V共享内存,或是内存映射)
  • 信号量和pthread互斥体

linux/unix编程手册-54(POSIX 共享内存)

  • 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()调用后终止)

linux/unix编程手册-55(文件加锁)

flock()对整个文件加锁 fcntl()对一个文件区域加锁,包含了flock的功能

由于stdio库会在用户空间缓冲,需要注意

  • 使用read,write取代stdio库执行
  • 文件加锁前刷新,锁释放前再刷新一次
  • setbuf禁用刷新

利用flock给文件加锁

#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

利用fcntl给记录加锁

#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 /testfsmount | grep mand查看哪些文件系统是强制
  • 文件强制加锁,通过开启set-group-ID 位和关闭group-execute位 chomd g+s,g-x /testfs/file
  • open,write这些都可能阻塞,
  • 尽可能避免使用强制锁

/proc/locks文件

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