linux/unix编程手册-46_50


title: linux/unix编程手册-46_50 date: 2018-10-01 11:53:07 categories: programming tags: tips

linux/unix编程手册-46(System V 消息队列)

消息队列和FIFO比较

  • msgget()返回的标识符不同于传统I/O的文件描述符
  • 面向消息不同于字节流
  • 消息有整型表示的类型,可以根据类型读取
创建或打开一个消息队列(略,45章覆盖了)
交换消息和控制消息
#include<sys/types.h>
#include<sys/msg.h>

struct mymsg{
    long mtype;
    char mtext[];
};

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//return 0 successl-1 error

ssize_t msgrcv(int msqid, const void *msgp, size_t maxmsgsz,long msgtyp, int msgflg);
// return bytes copied to msgp.mtext, -1 error

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//return 0 success, -1 error
复制代码

msgsnd

  • msgflg目前只定义了IPC_NO_WAIT(执行非阻塞发送,如果队列满了,会返回EAGAIN)
  • msgsnd需要写权限
  • msgsz指定mtext包含字节数

msgrcv

  • 如果mtext>maxmsgsz,不会删除消息同时默认返回E2BIG
  • msgtyp
    • 等于0: 删并返回第一条
    • 大于0: 匹配msgtyp的第一条,删并返回(一般可以指定成进程ID)
    • 小于0: 会当成优先队列处理,选取队列中msgtyp最小并且<=|msgtyp|的第一条消息,删除并返回
  • msgflg
    • IPC_NOWAIT 类似
    • MSG_EXCEPT (linux 特有 #define _GNU_SOURCE)作为对msgtyp的补充,匹配不等于msgtyp的消息
    • MSG_NOERROR 当果mtext>maxmsgsz,会截断mtext至maxmsgsz,删除消息并返回

msgctl

  • cmd
    • IPC_RMID:删除消息队列对象和关联的msqid_ds数据结构,会忽略第三个参数,阻塞的读写进程会立刻醒来,msgsnd和msgrcv返回EIDRM
    • IPC_STAT:将消息队列关联的数据结构的副本放到buf
    • IPC_SET: 用buf更新关联的msqid_ds结构中被选中的字段
    • IPC_INFO:(linux),同时buf 为 msginfo类型,返回消息队列的各种限制 msgctl(0, IPC_INFO, (struct msqid_ds) &buf)*
    • MSG_INFO:(linux,SEM_INFO|SHM_INFO),同时buf 为 msginfo类型,返回消息队列的资源消耗,返回的是entries的最大项下标
    • MSG_STAT:(linux,SEM_STAT,SHM_STAT),第一个参数为entries的下标,返回ipc对象标识符 如果阻塞时msgsnd和msgrcv被信号处理器中断,调用失败并且返回EINTR,且不会自动重启,不论信号处理是否设置了SA_RESTART标记
消息队列关联数据结构
struct msqid_ds {
    struct ipc_perm msg_perm;   /* Ownership and permissions */
    time_t msg_stime;           /* Time of last msgsnd() */
    time_t msg_rtime;           /* Time of last msgrcv() */
    time_t msg_ctime;           /* Time of last change */
    unsigned long __msg_cbytes; /* Number of bytes in queue */
    msgqnum_t msg_qnum;         /* Number of messages in queue */
    msglen_t msg_qbytes;        /* Maximum bytes in queue */
    pid_t msg_lspid;            /* PID of last msgsnd() */
    pid_t msg_lrpid;            /* PID of last msgrcv() */
};
复制代码
消息队列的限制
  • MSGMNI:消息队列的数量
  • MSGMAX:单条消息最多可入字节数
  • MSGMNB:消息队列msg_qbytes最大值
  • MSGTQL:消息总数
  • MSGPOOL:消息队列数据缓冲池的大小

缺点
  • 不是通过文件描述的引用,epoll等无法应用在消息队列上,如果代码里也是用文件描述符,会导致复杂
  • 用键而不是文件名标识消息使设计复杂
  • 消息队列无连接,内核对它没有引用计数
    • 如何能够安全删除消息队列
    • 如何确保消息队列不被删除
  • 一般使用posix消息队列替代system v消息队列
  • 更深一点使用基于多文件描述符的通信模型,ex:FIFO/SOCKET + epoll

linux/unix编程手册-47(System V 信号量)

信号量是用于同步进程动作,而不是进程间传递数据的

在一个信号量上的操作

  • 将信号量设置成一个绝对值
  • 当前值基础上加
  • 当前值基础上减(可能阻塞,内核不允许信号量小于0)
  • 等待信号量的值为0(可能阻塞)
创建或打开一个信号集
#include<sys/types.h>
#include<sys/sem.h>

int semget(key_t key, int nsems, int semflg);

// nsems:信号集中信号量的数量
复制代码
信号量的控制
#include<sys/types.h>
#include<sys/sem.h>

union semnum {
    int                 val;
    struct semid_ds     buf;
    unsigned short *    array;
#if defined(__linux__)
    struct seminfo *    __buf;
#endif
};

int semctl(int semid, int semnum, int cmd, .../* union semun arg */);


// semnum标明了集合的具体哪个信号量
复制代码

cmd

  • IPC_RMID:删除
  • IPC_STAT:将semid_ds拷贝到arg.buf
  • IPC:SET:arg.buf替换
  • GETVAL:获取第semnum个信号量的值
  • SETVAL:将第semnum个信号量的值设为arg.val
  • GETALL:获取所有信号量的值放到arg.array指向的数组中
  • SETALL:获取所有信号量的值设置成arg.array指向的数组的值
  • GETPID:函数会返回上一个在该信号量上执行semop()进程的id,没有则0
  • GETNCNT:函数会返回等待该信号量值增长的进程数
  • GETNCNT:函数会返回等待该信号量值置为0的进程数

后面6个cmd在返回时可能已经过期

信号量关联数据结构
struct semid_ds {
        struct ipc_perm sem_perm; /* Ownership and permissions */
        time_t sem_otime; /* Time of last semop() */
        time_t sem_ctime; /* Time of last change */
        unsigned long sem_nsems; /* Number of semaphores in set */
};
复制代码
信号量初始化
  • linux上 semget创建的集合中的信号量会默认初始化0,但为了移植性,需要在semctl显式的初始化
信号量操作
#include<sys/types.h>
#include<sys/sem.h>
struct sembuf {
        unsigned short sem_num; /* Semaphore number */
        short sem_op; /* Operation to be performed */
        short sem_flg; /* Operation flags (IPC_NOWAIT and SEM_UNDO) */
};

int semop(int semid, struct sembuf *sops, unsigned int nsops);
//return 0 succes, -1 error

#define _GNU_SOURCE
int semtimedop(int semid, struct sembuf *sops, unsigned int nsops, struct timespec *timeout);

复制代码
  • sops[i].sem_op
    • =0,检查现在是不是0,不是阻塞到变成0
    • !=,+sem_op(可正可负,负可能阻塞)值
  • sem_flg
    • 设置IPC_NOWAIT后,原来需要阻塞会返回EAGAIN
    • 设置SEM_UNDO,内核会记录一个进程在一个信号量上使用SEM_UNDO操作做出的调整总和,进程终止时减去这个总和
      • 异常,当执行减去这个操作后,信号量值<0:
        • 阻塞(不可行)
        • 尽可能减少(到0)并退出(linux的策略)
        • 直接退出不执行操作(部分unix实现)
  • 如果多个的话,操作是原子的,一个阻塞整个操作都会阻塞
多个阻塞信号量操作的处理
  • A让信号量减2,B让信号量减1,C使信号量加1,A会最后才会不阻塞
  • A让信号量1,2都减1,,B让信号量1减1,C使信号量1加1,A会最后才会不阻塞
信号量的限制(一些常量,用的会比较少吧)
信号量的缺点
  • 和消息队列差不多System V IPC的共同的缺点
  • 替代方案较少,ex:记录锁

linux/unix编程手册-48(System V 共享内存)

相较于消息队列和信号量,共享内存无需将数据在用户空间和内核空间传递

创建或打开一个共享内存段
#include<sys/types.h>
#include<sys.shm.h>

int shmget(key_t key, size_t size, int shmflg);
// size 是分配的字节数,创建时会自动被提升到最近系统分页的整数倍

复制代码

shmflg在linux上还有一些特殊的值

  • SHM_HUGETLB:特权进程可以通过此创建巨页的共享内存,减少TLB的条目数量
    • 在虚拟内存管理中,内核维护一个将虚拟内存地址映射到物理地址的表,对于每个页面操作,内核都需要加载相关的映射。如果你的内存页很小,那么你需要加载的页就会很多,导致内核会加载更多的映射表。而这会降低性能。使用“大内存页”,意味着所需要的页变少了。从而大大减少由内核加载的映射表的数量。这提高了内核级别的性能最终有利于应用程序的性能。简而言之,通过启用“大内存页”,系统具只需要处理较少的页面映射表,从而减少访问/维护它们的开销!
  • SHM_NORESERVE:(略)
使用共享内
#include<sys/types.h>
#include<sys.shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);
//return address if success,附加

int shmdt(onst void *shmaddr);
//0 success, -1 error, 分离
复制代码

shmaddr

  • 为NULL,段会被附加到内核选择的一个合适的地址处(通常推荐做法)
  • 不为NULL,shmflg没有设置SHM_RND掩码位,会被附加到shmaddr地址处,地址必须是系统分页大小整数倍,不然EINVAL
  • 不为NULL,设置了掩码位,地址会被舍入到最近的常亮SHMLBA(和分页大小的区别?)的倍数,通常SHMLBA是系统分页大小的某个倍数,保证同一段的不同附加操作在CPU快速缓冲中不会不一致(???)

一个进程附加一个共享内存需要读写权限,在shmflg可指定SHM_RDONLY,自己限制只有读权限

fork()时创建的子进程会继承父进程的共享内存段 exec()时,所有附加内存段会被分离,进程终止之后也会被分离

共享内存在虚拟内存的位置
  • 共享内存段的虚拟地址从TASK_UNMAPPED_BASE(32为通常为0x40000000) 定义的位置开始
  • /proc/PID/maps 可以看到程序映射的共享内存段和共享库的位置
共享内存中存储指针

共享内存中存储的只能是指针地址的相对偏移量(不同附加的虚拟地址是不一致的),解引用时也需要补上偏移量

共享内存控制操作
#include<sys/types.h>
#include<sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
复制代码

类似前两个

共享内存关联数据结构
struct shmid_ds {
    struct ipc_perm shm_perm;       /* Ownership and permissions */
    size_t shm_segsz;               /* Size of segment in bytes */
    time_t shm_atime;               /* Time of last shmat() */
    time_t shm_dtime;               /* Time of last shmdt() */
    time_t shm_ctime;               /* Time of last change */
    pid_t shm_cpid;                 /* PID of creator */
    pid_t shm_lpid;                 /* PID of last shmat() / shmdt() */
    shmatt_t shm_nattch;            /* Number of currently attached processes */
};
复制代码
共享内存的限制(略)

linux/unix编程手册-49(内存映射)

概述

mmap()系统调用再调用进程的虚拟地址空间创建一个新的内存映射

分类

  • 文件映射
  • 匿名映射:分页会初始化0

多个进程共享内存映射共享的情形

  • 映射了同一文件同一区域
  • fork创建子进程
  • 私有映射:映射内容发生变更对其他进程不可见,内核使用了copy-on-write实现,写的时候会创建新的分页,将修改写到上面
  • 共享映射:映射内容发生变更对其他进程可见,文件映射来讲变更会发生在底层文件上
  • fork 继承,exec解除

'/proc/PID/maps'查看内存映射的信息

#include<sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
//return 映射内存的开始地址,或者MAP_FAILED出现异常

复制代码
  • addr: NULL时自动分配,其他时作为参考, flag包含MAP_FIXED时,addr必须分页对齐
  • lenght:分配字节数会被提升到分页大小的下个整数倍
  • prot:掩码,等于PROT_NONE(区域无法访问,用作守护分页?)或者PROT_READ,PROT_WRITE,PROT_EXEC的组合
  • flag:MAP_PRIVATE,MAP_SHARED
  • fd,offset用于文件系统
    • fd打开的文件权限需要和prot和flags参数值匹配(如果指定了PROT_WRITE和MAP_SHARED,需要写权限)
    • fstat获取下文件的字节数一般会这么操作
    • mmap之后可以关闭文件描述符(影响写么?)
  • SUSv4:放宽了要求
    • offset不需要时分页大小的倍数
    • 指定了MAP_FIXED, 要求addr不为0时分页对齐,或者addr和offset除以系统分页大小所得余数相等
解除映射区域
#include<sys/mman.h>

int munmap(void *addr, size_t length);
//return 0 succes, -1 fail
// 如果addr+lenght范围不存在内存映射,返回0什么都不做

复制代码
文件映射

私有文件映射

  • 用例(一般由程序加载器和动态连接器创建)
    • 允许执行同一程序或通一个共享库的进程共享同样的只读文本段
    • 一个一个可执行文件或共享库的初始化数据段

共享文件映射

  • 内存映射I/O
    • 是read和write的替代方案,比较
    • 正常的read,write需要两次传输,一次在文件和内核高速缓冲区,一次是高速缓冲区和用户缓冲区,mmap之后无需第二次传输
    • 正常的read,write会在用户空间和内核空间的缓冲区分别保存,mmap之后内核空间和用户空间共享一个缓冲区
    • 大型文件重复执行随机访问会提高较多性能
  • 作为共享文件映射的IPC
    • 和SYSTEM V 共享内存对象的区别:
      • 变更会反映到映射文件,遭遇重启时可以持久化

边界情况

  • 存在三个值,文件的字节数A,length的字节数B,向上舍入后的分配的字节数C,向上舍入的分页为D
    • 访问>C,并且不是别的映射的字节,会导致SIGSEGV错误
    • 当扩充超过了文件结尾时,即D区域不包含文件的字节时如下
同步映射区域
#include<sys/mman.h>

int msync(void *addr, size_t length, int flags);
// 0 s, -1 f
复制代码

flags

  • MS_SYNC:阻塞到同步完成
  • MS_ASYNC
  • MS_INVALIDATE:时映射数据缓存副本无效,当内存中所有修改同步到文件后,不一致的分页标记无效,下次引用时会拉取更新(用于使其他进程对写入立即可见)
其他mmap()标记

匿名映射
  • 创建的方法
    • flags指定MAP_ANONYMOUS,并且fd=-1
    • 打开/dev/zero设备文件,将描述符传给mmap()
重新映射一个映射区域:mremap()(略)
MAP_NO_RESERVE和过度利用交换空间(略,查一下overcommit_ratio)

OOM killer

  • /proc/PID/oom_adj中oom_score, -16~15,越大越容易被杀掉,-17的话不会被杀
非线性映射和MAP_FIXED标记(略)
其他/proc/$PID/maps保存进程的内存映射
address           perms offset  dev   inode   pathname
08048000-08056000 r-xp 00000000 03:0c 64593   /usr/sbin/gpm
复制代码

perms:s=share,p=private

linux/unix编程手册-50(虚拟内存操作)

改变内存保护
#include<sys/mman.h>

int mprotect(void *addr, size_t length, int prot);
// 0 s, -1 e
复制代码
内存锁

将虚拟内存部分或全部锁进物理内存,系统调用 mlock 家族允许程序在物理内存上锁住它的部分或全部地址空间。这将阻止Linux 将这个内存页调度到交换空间(swap space),即使该程序已有一段时间没有访问这段空间

#include<sys/mman.h>

int mlock(void *addr, size_t length);

int munlock(void *addr, size_t length);

int mlockall(int flags);

int munlockall(void);

#define _BSD_SOURCE
int mincore(void *addr, size_t length, unsigned char *vec);
// 0 s, -1 e
// 从addr下面的一个分页开始锁住length字节分页
复制代码

略其他

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值