这个中断系列文章主要针对移动设备中的Linux进行讨论,文中的例子基本都是基于ARM这一体系架构,其他架构的原理其实也差不多,区别只是其中的硬件抽象层。内核版本基于3.3。虽然内核的版本不断地提升,不过自从上一次变更到当前的通用中断子系统后,大的框架性的东西并没有太大的改变。
/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/
1. 设备、中断控制器和CPU
一个完整的设备中,与中断相关的硬件可以划分为3类,它们分别是:设备、中断控制器和CPU本身,下图展示了一个smp系统中的中断硬件的组成结构:
图 1.1 中断系统的硬件组成
设备 设备是发起中断的源,当设备需要请求某种服务的时候,它会发起一个硬件中断信号,通常,该信号会连接至中断控制器,由中断控制器做进一步的处理。在现代的移动设备中,发起中断的设备可以位于soc(system-on-chip)芯片的外部,也可以位于soc的内部,因为目前大多数soc都集成了大量的硬件IP,例如I2C、SPI、Display Controller等等。
中断控制器 中断控制器负责收集所有中断源发起的中断,现有的中断控制器几乎都是可编程的,通过对中断控制器的编程,我们可以控制每个中断源的优先级、中断的电器类型,还可以打开和关闭某一个中断源,在smp系统中,甚至可以控制某个中断源发往哪一个CPU进行处理。对于ARM架构的soc,使用较多的中断控制器是VIC(Vector Interrupt Controller),进入多核时代以后,GIC(General Interrupt Controller)的应用也开始逐渐变多。
CPU cpu是最终响应中断的部件,它通过对可编程中断控制器的编程操作,控制和管理者系统中的每个中断,当中断控制器最终判定一个中断可以被处理时,他会根据事先的设定,通知其中一个或者是某几个cpu对该中断进行处理,虽然中断控制器可以同时通知数个cpu对某一个中断进行处理,实际上,最后只会有一个cpu相应这个中断请求,但具体是哪个cpu进行响应是可能是随机的,中断控制器在硬件上对这一特性进行了保证,不过这也依赖于操作系统对中断系统的软件实现。在smp系统中,cpu之间也通过IPI(inter processor interrupt)中断进行通信。
2. IRQ编号
系统中每一个注册的中断源,都会分配一个唯一的编号用于识别该中断,我们称之为IRQ编号。IRQ编号贯穿在整个Linux的通用中断子系统中。在移动设备中,每个中断源的IRQ编号都会在arch相关的一些头文件中,例如arch/xxx/mach-xxx/include/irqs.h。驱动程序在请求中断服务时,它会使用IRQ编号注册该中断,中断发生时,cpu通常会从中断控制器中获取相关信息,然后计算出相应的IRQ编号,然后把该IRQ编号传递到相应的驱动程序中。
3. 在驱动程序中申请中断
Linux中断子系统向驱动程序提供了一系列的API,其中的一个用于向系统申请中断:
[cpp] view plain copy
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
其中,
irq是要申请的IRQ编号,
handler是中断处理服务函数,该函数工作在中断上下文中,如果不需要,可以传入NULL,但是不可以和thread_fn同时为NULL;
thread_fn是中断线程的回调函数,工作在内核进程上下文中,如果不需要,可以传入NULL,但是不可以和handler同时为NULL;
irqflags是该中断的一些标志,可以指定该中断的电气类型,是否共享等信息;
devname指定该中断的名称;
dev_id用于共享中断时的cookie data,通常用于区分共享中断具体由哪个设备发起;
关于该API的详细工作机理我们后面再讨论。
4. 通用中断子系统(Generic irq)的软件抽象
在通用中断子系统(generic irq)出现之前,内核使用__do_IRQ处理所有的中断,这意味着__do_IRQ中要处理各种类型的中断,这会导致软件的复杂性增加,层次不分明,而且代码的可重用性也不好。事实上,到了内核版本2.6.38,__do_IRQ这种方式已经彻底在内核的代码中消失了。通用中断子系统的原型最初出现于ARM体系中,一开始内核的开发者们把3种中断类型区分出来,他们是:
电平触发中断(level type)
边缘触发中断(edge type)
简易的中断(simple type)
后来又针对某些需要回应eoi(end of interrupt)的中断控制器,加入了fast eoi type,针对smp加入了per cpu type。把这些不同的中断类型抽象出来后,成为了中断子系统的流控层。要使所有的体系架构都可以重用这部分的代码,中断控制器也被进一步地封装起来,形成了中断子系统中的硬件封装层。我们可以用下面的图示表示通用中断子系统的层次结构:
图 4.1 通用中断子系统的层次结构
硬件封装层 它包含了体系架构相关的所有代码,包括中断控制器的抽象封装,arch相关的中断初始化,以及各个IRQ的相关数据结构的初始化工作,cpu的中断入口也会在arch相关的代码中实现。中断通用逻辑层通过标准的封装接口(实际上就是struct irq_chip定义的接口)访问并控制中断控制器的行为,体系相关的中断入口函数在获取IRQ编号后,通过中断通用逻辑层提供的标准函数,把中断调用传递到中断流控层中。我们看看irq_chip的部分定义:
struct irq_chip {
const char *name;
unsigned int (*irq_startup)(struct irq_data *data);
void (*irq_shutdown)(struct irq_data *data);
void (*irq_enable)(struct irq_data *data);
void (*irq_disable)(struct irq_data *data);
void (*irq_ack)(struct irq_data *data);
void (*irq_mask)(struct irq_data *data);
void (*irq_mask_ack)(struct irq_data *data);
void (*irq_unmask)(struct irq_data *data);
void (*irq_eoi)(struct irq_data *data);
int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
int (*irq_retrigger)(struct irq_data *data);
int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
int (*irq_set_wake)(struct irq_data *data, unsigned int on);
......
};看到上面的结构定义,很明显,它实际上就是对中断控制器的接口抽象,我们只要对每个中断控制器实现以上接口(不必全部),并把它和相应的irq关联起来,上层的实现即可通过这些接口访问中断控制器。而且,同一个中断控制器的代码可以方便地被不同的平台所重用。
中断流控层 所谓中断流控是指合理并正确地处理连续发生的中断,比如一个中断在处理中,同一个中断再次到达时如何处理,何时应该屏蔽中断,何时打开中断,何时回应中断控制器等一系列的操作。该层实现了与体系和硬件无关的中断流控处理操作,它针对不同的中断电气类型(level,edge......),实现了对应的标准中断流控处理函数,在这些处理函数中,最终会把中断控制权传递到驱动程序注册中断时传入的处理函数或者是中断线程中。目前内核提供了以下几个主要的中断流控函数的实现(只列出部分):
handle_simple_irq();
handle_level_irq(); 电平中断流控处理程序
handle_edge_irq(); 边沿触发中断流控处理程序
handle_fasteoi_irq(); 需要eoi的中断处理器使用的中断流控处理程序
handle_percpu_irq(); 该irq只有单个cpu响应时使用的流控处理程序
中断通用逻辑层 该层实现了对中断系统几个重要数据的管理,并提供了一系列的辅助管理函数。同时,该层还实现了中断线程的实现和管理,共享中断和嵌套中断的实现和管理,另外它还提供了一些接口函数,它们将作为硬件封装层和中断流控层以及驱动程序API层之间的桥梁,例如以下API:
generic_handle_irq();
irq_to_desc();
irq_set_chip();
irq_set_chained_handler();
驱动程序API 该部分向驱动程序提供了一系列的API,用于向系统申请/释放中断,打开/关闭中断,设置中断类型和中断唤醒系统的特性等操作。驱动程序的开发者通常只会使用到这一层提供的这些API即可完成驱动程序的开发工作,其他的细节都由另外几个软件层较好地“隐藏”起来了,驱动程序开发者无需再关注底层的实现,这看起来确实是一件美妙的事情,不过我认为,要想写出好的中断代码,还是花点时间了解一下其他几层的实现吧。其中的一些API如下:
enable_irq();
disable_irq();
disable_irq_nosync();
request_threaded_irq();
irq_set_affinity();
这里不再对每一层做详细的介绍,我将会在本系列的其他几篇文章中做深入的探讨。
5. irq描述结构:struct irq_desc
整个通用中断子系统几乎都是围绕着irq_desc结构进行,系统中每一个irq都对应着一个irq_desc结构,所有的irq_desc结构的组织方式有两种:
基于数组方式 平台相关板级代码事先根据系统中的IRQ数量,定义常量:NR_IRQS,在kernel/irq/irqdesc.c中使用该常量定义irq_desc结构数组:
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
[0 ... NR_IRQS-1] = {
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};基于基数树方式 当内核的配置项CONFIG_SPARSE_IRQ被选中时,内核使用基数树(radix tree)来管理irq_desc结构,这一方式可以动态地分配irq_desc结构,对于那些具备大量IRQ数量或者IRQ编号不连续的系统,使用该方式管理irq_desc对内存的节省有好处,而且对那些自带中断控制器管理设备自身多个中断源的外部设备,它们可以在驱动程序中动态地申请这些中断源所对应的irq_desc结构,而不必在系统的编译阶段保留irq_desc结构所需的内存。
下面我们看一看irq_desc的部分定义:
struct irq_data {
unsigned int irq;
unsigned long hwirq;
unsigned int node;
unsigned int state_use_accessors;
struct irq_chip *chip;
struct irq_domain *domain;
void *handler_data;
void *chip_data;
struct msi_desc *msi_desc;
#ifdef CONFIG_SMP
cpumask_var_t affinity;
#endif
};struct irq_desc {
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
irq_preflow_handler_t preflow_handler;
#endif
struct irqaction *action; /* IRQ action list */
unsigned int status_use_accessors;
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
raw_spinlock_t lock;
struct cpumask *percpu_enabled;
#ifdef CONFIG_SMP
const struct cpumask *affinity_hint;
struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask;
#endif
#endif
wait_queue_head_t wait_for_threads;
const char *name;
} ____cacheline_internodealigned_in_smp;对于irq_desc中的主要字段做一个解释:
irq_data 这个内嵌结构在2.6.37版本引入,之前的内核版本的做法是直接把这个结构中的字段直接放置在irq_desc结构体中,然后在调用硬件封装层的chip->xxx()回调中传入IRQ编号作为参数,但是底层的函数经常需要访问->handler_data,->chip_data,->msi_desc等字段,这需要利用irq_to_desc(irq)来获得irq_desc结构的指针,然后才能访问上述字段,者带来了性能的降低,尤其在配置为sparse irq的系统中更是如此,因为这意味着基数树的搜索操作。为了解决这一问题,内核开发者把几个低层函数需要使用的字段单独封装为一个结构,调用时的参数则改为传入该结构的指针。实现同样的目的,那为什么不直接传入irq_desc结构指针?因为这会破坏层次的封装性,我们不希望低层代码可以看到不应该看到的部分,仅此而已。
kstat_irqs 用于irq的一些统计信息,这些统计信息可以从proc文件系统中查询。
action 中断响应链表,当一个irq被触发时,内核会遍历该链表,调用action结构中的回调handler或者激活其中的中断线程,之所以实现为一个链表,是为了实现中断的共享,多个设备共享同一个irq,这在外围设备中是普遍存在的。
status_use_accessors 记录该irq的状态信息,内核提供了一系列irq_settings_xxx的辅助函数访问该字段,详细请查看kernel/irq/settings.h
depth 用于管理enable_irq()/disable_irq()这两个API的嵌套深度管理,每次enable_irq时该值减去1,每次disable_irq时该值加1,只有depth==0时才真正向硬件封装层发出关闭irq的调用,只有depth==1时才会向硬件封装层发出打开irq的调用。disable的嵌套次数可以比enable的次数多,此时depth的值大于1,随着enable的不断调用,当depth的值为1时,在向硬件封装层发出打开irq的调用后,depth减去1后,此时depth为0,此时处于一个平衡状态,我们只能调用disable_irq,如果此时enable_irq被调用,内核会报告一个irq失衡的警告,提醒驱动程序的开发人员检查自己的代码。
lock 用于保护irq_desc结构本身的自旋锁。
affinity_hit 用于提示用户空间,作为优化irq和cpu之间的亲缘关系的依据。
pending_mask 用于调整irq在各个cpu之间的平衡。
wait_for_threads 用于synchronize_irq(),等待该irq所有线程完成。
irq_data结构中的各字段:
irq 该结构所对应的IRQ编号。
hwirq 硬件irq编号,它不同于上面的irq;
node 通常用于hwirq和irq之间的映射操作;
state_use_accessors 硬件封装层需要使用的状态信息,不要直接访问该字段,内核定义了一组函数用于访问该字段:irqd_xxxx(),参见include/linux/irq.h。
chip 指向该irq所属的中断控制器的irq_chip结构指针
handler_data 每个irq的私有数据指针,该字段由硬件封转层使用,例如用作底层硬件的多路复用中断。
chip_data 中断控制器的私有数据,该字段由硬件封转层使用。
msi_desc 用于PCIe总线的MSI或MSI-X中断机制。
affinity 记录该irq与cpu之间的亲缘关系,它其实是一个bit-mask,每一个bit代表一个cpu,置位后代表该cpu可能处理该irq。
这是通用中断子系统系列文章的第一篇,这里不会详细介绍各个软件层次的实现原理,但是有必要对整个架构做简要的介绍:
系统启动阶段,取决于内核的配置,内核会通过数组或基数树分配好足够多的irq_desc结构;
根据不同的体系结构,初始化中断相关的硬件,尤其是中断控制器;
为每个必要irq的irq_desc结构填充默认的字段,例如irq编号,irq_chip指针,根据不同的中断类型配置流控handler;
设备驱动程序在初始化阶段,利用request_threaded_irq() api申请中断服务,两个重要的参数是handler和thread_fn;
当设备触发一个中断后,cpu会进入事先设定好的中断入口,它属于底层体系相关的代码,它通过中断控制器获得irq编号,在对irq_data结构中的某些字段进行处理后,会将控制权传递到中断流控层(通过irq_desc->handle_irq);
中断流控处理代码在作出必要的流控处理后,通过irq_desc->action链表,取出驱动程序申请中断时注册的handler和thread_fn,根据它们的赋值情况,或者只是调用handler回调,或者启动一个线程执行thread_fn,又或者两者都执行;
至此,中断最终由驱动程序进行了响应和处理。
6. 中断子系统的proc文件接口
在/proc目录下面,有两个与中断子系统相关的文件和子目录,它们是:
/proc/interrupts:文件
/proc/irq:子目录
读取interrupts会依次显示irq编号,每个cpu对该irq的处理次数,中断控制器的名字,irq的名字,以及驱动程序注册该irq时使用的名字,以下是一个例子:
/proc/irq目录下面会为每个注册的irq创建一个以irq编号为名字的子目录,每个子目录下分别有以下条目:
smp_affinity irq和cpu之间的亲缘绑定关系;
smp_affinity_hint 只读条目,用于用户空间做irq平衡只用;
spurious 可以获得该irq被处理和未被处理的次数的统计信息;
handler_name 驱动程序注册该irq时传入的处理程序的名字;
根据irq的不同,以上条目不一定会全部都出现,以下是某个设备的例子:
# cd /proc/irq
# ls
ls
332
248
......
......
12
11
default_smp_affinity
# ls 332
bcmsdh_sdmmc
spurious
node
affinity_hint
smp_affinity
# cat 332/smp_affinity
3
可见,以上设备是一个使用双核cpu的设备,因为smp_affinity的值是3,系统默认每个中断可以由两个cpu进行处理。
队列的意思应该大家都清楚,不过还有有一些细节的地方不知道,下面是一个队列的源码,因该说这是队列的一部分,不是全部。而且是linux中队列,其他各种OS中队列大同小异。
/*
* POSIX message queues filesystem for Linux.
*
* Copyright (C) 2003,2004 Krzysztof Benedyczak ([email protected])
* Michal Wronski ([email protected])
*
* Spinlocks: Mohamed Abbas ([email protected])
* Lockless receive & send, fd based notify:
* Manfred Spraul ([email protected])
*
* Audit: George Wilson ([email protected])
*
* This file is released under the GPL.
*/
#include <linux/capability.h>
#include <linux/init.h>
#include <linux/pagemap.h>
#include <linux/file.h>
#include <linux/mount.h>
#include <linux/fs_context.h>
#include <linux/namei.h>
#include <linux/sysctl.h>
#include <linux/poll.h>
#include <linux/mqueue.h>
#include <linux/msg.h>
#include <linux/skbuff.h>
#include <linux/vmalloc.h>
#include <linux/netlink.h>
#include <linux/syscalls.h>
#include <linux/audit.h>
#include <linux/signal.h>
#include <linux/mutex.h>
#include <linux/nsproxy.h>
#include <linux/pid.h>
#include <linux/ipc_namespace.h>
#include <linux/user_namespace.h>
#include <linux/slab.h>
#include <linux/sched/wake_q.h>
#include <linux/sched/signal.h>
#include <linux/sched/user.h>
#include <net/sock.h>
#include "util.h"
struct mqueue_fs_context {
struct ipc_namespace *ipc_ns;
};
#define MQUEUE_MAGIC 0x19800202
#define DIRENT_SIZE 20
#define FILENT_SIZE 80
#define SEND 0
#define RECV 1
#define STATE_NONE 0
#define STATE_READY 1
struct posix_msg_tree_node {
struct rb_node rb_node;
struct list_head msg_list;
int priority;
};
struct ext_wait_queue { /* queue of sleeping tasks */
struct task_struct *task;
struct list_head list;
struct msg_msg *msg; /* ptr of loaded message */
int state; /* one of STATE_* values */
};
struct mqueue_inode_info {
spinlock_t lock;
struct inode vfs_inode;
wait_queue_head_t wait_q;
struct rb_root msg_tree;
struct rb_node *msg_tree_rightmost;
struct posix_msg_tree_node *node_cache;
struct mq_attr attr;
struct sigevent notify;
struct pid *notify_owner;
struct user_namespace *notify_user_ns;
struct user_struct *user; /* user who created, for accounting */
struct sock *notify_sock;
struct sk_buff *notify_cookie;
/* for tasks waiting for free space and messages, respectively */
struct ext_wait_queue e_wait_q[2];
unsigned long qsize; /* size of queue in memory (sum of all msgs) */
};
static struct file_system_type mqueue_fs_type;
static const struct inode_operations mqueue_dir_inode_operations;
static const struct file_operations mqueue_file_operations;
static const struct super_operations mqueue_super_ops;
static const struct fs_context_operations mqueue_fs_context_ops;
static void remove_notification(struct mqueue_inode_info *info);
static struct kmem_cache *mqueue_inode_cachep;
static struct ctl_table_header *mq_sysctl_table;
static inline struct mqueue_inode_info *MQUEUE_I(struct inode *inode)
{
return container_of(inode, struct mqueue_inode_info, vfs_inode);
}
/*
* This routine should be called with the mq_lock held.
*/
static inline struct ipc_namespace *__get_ns_from_inode(struct inode *inode)
{
return get_ipc_ns(inode->i_sb->s_fs_info);
}
static struct ipc_namespace *get_ns_from_inode(struct inode *inode)
{
struct ipc_namespace *ns;
spin_lock(&mq_lock);
ns = __get_ns_from_inode(inode);
spin_unlock(&mq_lock);
return ns;
}
/* Auxiliary functions to manipulate messages' list */
static int msg_insert(struct msg_msg *msg, struct mqueue_inode_info *info)
{
struct rb_node **p, *parent = NULL;
struct posix_msg_tree_node *leaf;
bool rightmost = true;
p = &info->msg_tree.rb_node;
while (*p) {
parent = *p;
leaf = rb_entry(parent, struct posix_msg_tree_node, rb_node);
if (likely(leaf->priority == msg->m_type))
goto insert_msg;
else if (msg->m_type < leaf->priority) {
p = &(*p)->rb_left;
rightmost = false;
} else
p = &(*p)->rb_right;
}
if (info->node_cache) {
leaf = info->node_cache;
info->node_cache = NULL;
} else {
leaf = kmalloc(sizeof(*leaf), GFP_ATOMIC);
if (!leaf)
return -ENOMEM;
INIT_LIST_HEAD(&leaf->msg_list);
}
leaf->priority = msg->m_type;
if (rightmost)
info->msg_tree_rightmost = &leaf->rb_node;
rb_link_node(&leaf->rb_node, parent, p);
rb_insert_color(&leaf->rb_node, &info->msg_tree);
insert_msg:
info->attr.mq_curmsgs++;
info->qsize += msg->m_ts;
list_add_tail(&msg->m_list, &leaf->msg_list);
return 0;
}
static inline void msg_tree_erase(struct posix_msg_tree_node *leaf,
struct mqueue_inode_info *info)
{
struct rb_node *node = &leaf->rb_node;
if (info->msg_tree_rightmost == node)
info->msg_tree_rightmost = rb_prev(node);
rb_erase(node, &info->msg_tree);
if (info->node_cache) {
kfree(leaf);
} else {
info->node_cache = leaf;
}
}
static inline struct msg_msg *msg_get(struct mqueue_inode_info *info)
{
struct rb_node *parent = NULL;
struct posix_msg_tree_node *leaf;
struct msg_msg *msg;
try_again:
/*
* During insert, low priorities go to the left and high to the
* right. On receive, we want the highest priorities first, so
* walk all the way to the right.
*/
parent = info->msg_tree_rightmost;
if (!parent) {
if (info->attr.mq_curmsgs) {
pr_warn_once("Inconsistency in POSIX message queue, "
"no tree element, but supposedly messages "
"should exist!\n");
info->attr.mq_curmsgs = 0;
}
return NULL;
}
leaf = rb_entry(parent, struct posix_msg_tree_node, rb_node);
if (unlikely(list_empty(&leaf->msg_list))) {
pr_warn_once("Inconsistency in POSIX message queue, "
"empty leaf node but we haven't implemented "
"lazy leaf delete!\n");
msg_tree_erase(leaf, info);
goto try_again;
} else {
msg = list_first_entry(&leaf->msg_list,
struct msg_msg, m_list);
list_del(&msg->m_list);
if (list_empty(&leaf->msg_list)) {
msg_tree_erase(leaf, info);
}
}
info->attr.mq_curmsgs--;
info->qsize -= msg->m_ts;
return msg;
}
static struct inode *mqueue_get_inode(struct super_block *sb,
struct ipc_namespace *ipc_ns, umode_t mode,
struct mq_attr *attr)
{
struct user_struct *u = current_user();
struct inode *inode;
int ret = -ENOMEM;
inode = new_inode(sb);
if (!inode)
goto err;
inode->i_ino = get_next_ino();
inode->i_mode = mode;
inode->i_uid = current_fsuid();
inode->i_gid = current_fsgid();
inode->i_mtime = inode->i_ctime = inode->i_atime = current_time(inode);
if (S_ISREG(mode)) {
struct mqueue_inode_info *info;
unsigned long mq_bytes, mq_treesize;
inode->i_fop = &mqueue_file_operations;
inode->i_size = FILENT_SIZE;
/* mqueue specific info */
info = MQUEUE_I(inode);
spin_lock_init(&info->lock);
init_waitqueue_head(&info->wait_q);
INIT_LIST_HEAD(&info->e_wait_q[0].list);
INIT_LIST_HEAD(&info->e_wait_q[1].list);
info->notify_owner = NULL;
info->notify_user_ns = NULL;
info->qsize = 0;
info->user = NULL; /* set when all is ok */
info->msg_tree = RB_ROOT;
info->msg_tree_rightmost = NULL;
info->node_cache = NULL;
memset(&info->attr, 0, sizeof(info->attr));
info->attr.mq_maxmsg = min(ipc_ns->mq_msg_max,
ipc_ns->mq_msg_default);
info->attr.mq_msgsize = min(ipc_ns->mq_msgsize_max,
ipc_ns->mq_msgsize_default);
if (attr) {
info->attr.mq_maxmsg = attr->mq_maxmsg;
info->attr.mq_msgsize = attr->mq_msgsize;
}
/*
* We used to allocate a static array of pointers and account
* the size of that array as well as one msg_msg struct per
* possible message into the queue size. That's no longer
* accurate as the queue is now an rbtree and will grow and
* shrink depending on usage patterns. We can, however, still
* account one msg_msg struct per message, but the nodes are
* allocated depending on priority usage, and most programs
* only use one, or a handful, of priorities. However, since
* this is pinned memory, we need to assume worst case, so
* that means the min(mq_maxmsg, max_priorities) * struct
* posix_msg_tree_node.
*/
ret = -EINVAL;
if (info->attr.mq_maxmsg <= 0 || info->attr.mq_msgsize <= 0)
goto out_inode;
if (capable(CAP_SYS_RESOURCE)) {
if (info->attr.mq_maxmsg > HARD_MSGMAX ||
info->attr.mq_msgsize > HARD_MSGSIZEMAX)
goto out_inode;
} else {
if (info->attr.mq_maxmsg > ipc_ns->mq_msg_max ||
info->attr.mq_msgsize > ipc_ns->mq_msgsize_max)
goto out_inode;
}
ret = -EOVERFLOW;
/* check for overflow */
if (info->attr.mq_msgsize > ULONG_MAX/info->attr.mq_maxmsg)
goto out_inode;
mq_treesize = info->attr.mq_maxmsg * sizeof(struct msg_msg) +
min_t(unsigned int, info->attr.mq_maxmsg, MQ_PRIO_MAX) *
sizeof(struct posix_msg_tree_node);
mq_bytes = info->attr.mq_maxmsg * info->attr.mq_msgsize;
if (mq_bytes + mq_treesize < mq_bytes)
goto out_inode;
mq_bytes += mq_treesize;
spin_lock(&mq_lock);
if (u->mq_bytes + mq_bytes < u->mq_bytes ||
u->mq_bytes + mq_bytes > rlimit(RLIMIT_MSGQUEUE)) {
spin_unlock(&mq_lock);
/* mqueue_evict_inode() releases info->messages */
ret = -EMFILE;
goto out_inode;
}
u->mq_bytes += mq_bytes;
spin_unlock(&mq_lock);
/* all is ok */
info->user = get_uid(u);
} else if (S_ISDIR(mode)) {
inc_nlink(inode);
/* Some things misbehave if size == 0 on a directory */
inode->i_size = 2 * DIRENT_SIZE;
inode->i_op = &mqueue_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
}
return inode;
out_inode:
iput(inode);
err:
return ERR_PTR(ret);
}
static int mqueue_fill_super(struct super_block *sb, struct fs_context *fc)
{
struct inode *inode;
struct ipc_namespace *ns = sb->s_fs_info;
sb->s_iflags |= SB_I_NOEXEC | SB_I_NODEV;
sb->s_blocksize = PAGE_SIZE;
sb->s_blocksize_bits = PAGE_SHIFT;
sb->s_magic = MQUEUE_MAGIC;
sb->s_op = &mqueue_super_ops;
inode = mqueue_get_inode(sb, ns, S_IFDIR | S_ISVTX | S_IRWXUGO, NULL);
if (IS_ERR(inode))
return PTR_ERR(inode);
sb->s_root = d_make_root(inode);
if (!sb->s_root)
return -ENOMEM;
return 0;
}
static int mqueue_get_tree(struct fs_context *fc)
{
struct mqueue_fs_context *ctx = fc->fs_private;
return get_tree_keyed(fc, mqueue_fill_super, ctx->ipc_ns);
}
static void mqueue_fs_context_free(struct fs_context *fc)
{
struct mqueue_fs_context *ctx = fc->fs_private;
put_ipc_ns(ctx->ipc_ns);
kfree(ctx);
}
static int mqueue_init_fs_context(struct fs_context *fc)
{
struct mqueue_fs_context *ctx;
ctx = kzalloc(sizeof(struct mqueue_fs_context), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
ctx->ipc_ns = get_ipc_ns(current->nsproxy->ipc_ns);
put_user_ns(fc->user_ns);
fc->user_ns = get_user_ns(ctx->ipc_ns->user_ns);
fc->fs_private = ctx;
fc->ops = &mqueue_fs_context_ops;
return 0;
}
static struct vfsmount *mq_create_mount(struct ipc_namespace *ns)
{
struct mqueue_fs_context *ctx;
struct fs_context *fc;
struct vfsmount *mnt;
fc = fs_context_for_mount(&mqueue_fs_type, SB_KERNMOUNT);
if (IS_ERR(fc))
return ERR_CAST(fc);
ctx = fc->fs_private;
put_ipc_ns(ctx->ipc_ns);
ctx->ipc_ns = get_ipc_ns(ns);
put_user_ns(fc->user_ns);
fc->user_ns = get_user_ns(ctx->ipc_ns->user_ns);
mnt = fc_mount(fc);
put_fs_context(fc);
return mnt;
}
static void init_once(void *foo)
{
struct mqueue_inode_info *p = (struct mqueue_inode_info *) foo;
inode_init_once(&p->vfs_inode);
}
static struct inode *mqueue_alloc_inode(struct super_block *sb)
{
struct mqueue_inode_info *ei;
ei = kmem_cache_alloc(mqueue_inode_cachep, GFP_KERNEL);
if (!ei)
return NULL;
return &ei->vfs_inode;
}
static void mqueue_free_inode(struct inode *inode)
{
kmem_cache_free(mqueue_inode_cachep, MQUEUE_I(inode));
}
static void mqueue_evict_inode(struct inode *inode)
{
struct mqueue_inode_info *info;
struct user_struct *user;
struct ipc_namespace *ipc_ns;
struct msg_msg *msg, *nmsg;
LIST_HEAD(tmp_msg);
clear_inode(inode);
if (S_ISDIR(inode->i_mode))
return;
ipc_ns = get_ns_from_inode(inode);
info = MQUEUE_I(inode);
spin_lock(&info->lock);
while ((msg = msg_get(info)) != NULL)
list_add_tail(&msg->m_list, &tmp_msg);
kfree(info->node_cache);
spin_unlock(&info->lock);
list_for_each_entry_safe(msg, nmsg, &tmp_msg, m_list) {
list_del(&msg->m_list);
free_msg(msg);
}
user = info->user;
if (user) {
unsigned long mq_bytes, mq_treesize;
/* Total amount of bytes accounted for the mqueue */
mq_treesize = info->attr.mq_maxmsg * sizeof(struct msg_msg) +
min_t(unsigned int, info->attr.mq_maxmsg, MQ_PRIO_MAX) *
sizeof(struct posix_msg_tree_node);
mq_bytes = mq_treesize + (info->attr.mq_maxmsg *
info->attr.mq_msgsize);
spin_lock(&mq_lock);
user->mq_bytes -= mq_bytes;
/*
* get_ns_from_inode() ensures that the
* (ipc_ns = sb->s_fs_info) is either a valid ipc_ns
* to which we now hold a reference, or it is NULL.
* We can't put it here under mq_lock, though.
*/
if (ipc_ns)
ipc_ns->mq_queues_count--;
spin_unlock(&mq_lock);
free_uid(user);
}
if (ipc_ns)
put_ipc_ns(ipc_ns);
}
static int mqueue_create_attr(struct dentry *dentry, umode_t mode, void *arg)
{
struct inode *dir = dentry->d_parent->d_inode;
struct inode *inode;
struct mq_attr *attr = arg;
int error;
struct ipc_namespace *ipc_ns;
spin_lock(&mq_lock);
ipc_ns = __get_ns_from_inode(dir);
if (!ipc_ns) {
error = -EACCES;
goto out_unlock;
}
if (ipc_ns->mq_queues_count >= ipc_ns->mq_queues_max &&
!capable(CAP_SYS_RESOURCE)) {
error = -ENOSPC;
goto out_unlock;
}
ipc_ns->mq_queues_count++;
spin_unlock(&mq_lock);
inode = mqueue_get_inode(dir->i_sb, ipc_ns, mode, attr);
if (IS_ERR(inode)) {
error = PTR_ERR(inode);
spin_lock(&mq_lock);
ipc_ns->mq_queues_count--;
goto out_unlock;
}
put_ipc_ns(ipc_ns);
dir->i_size += DIRENT_SIZE;
dir->i_ctime = dir->i_mtime = dir->i_atime = current_time(dir);
d_instantiate(dentry, inode);
dget(dentry);
return 0;
out_unlock:
spin_unlock(&mq_lock);
if (ipc_ns)
put_ipc_ns(ipc_ns);
return error;
}
static int mqueue_create(struct inode *dir, struct dentry *dentry,
umode_t mode, bool excl)
{
return mqueue_create_attr(dentry, mode, NULL);
}
static int mqueue_unlink(struct inode *dir, struct dentry *dentry)
{
struct inode *inode = d_inode(dentry);
dir->i_ctime = dir->i_mtime = dir->i_atime = current_time(dir);
dir->i_size -= DIRENT_SIZE;
drop_nlink(inode);
dput(dentry);
return 0;
}
/*
* This is routine for system read from queue file.
* To avoid mess with doing here some sort of mq_receive we allow
* to read only queue size & notification info (the only values
* that are interesting from user point of view and aren't accessible
* through std routines)
*/
static ssize_t mqueue_read_file(struct file *filp, char __user *u_data,
size_t count, loff_t *off)
{
struct mqueue_inode_info *info = MQUEUE_I(file_inode(filp));
char buffer[FILENT_SIZE];
ssize_t ret;
spin_lock(&info->lock);
snprintf(buffer, sizeof(buffer),
"QSIZE:%-10lu NOTIFY:%-5d SIGNO:%-5d NOTIFY_PID:%-6d\n",
info->qsize,
info->notify_owner ? info->notify.sigev_notify : 0,
(info->notify_owner &&
info->notify.sigev_notify == SIGEV_SIGNAL) ?
info->notify.sigev_signo : 0,
pid_vnr(info->notify_owner));
spin_unlock(&info->lock);
buffer[sizeof(buffer)-1] = '\0';
ret = simple_read_from_buffer(u_data, count, off, buffer,
strlen(buffer));
if (ret <= 0)
return ret;
file_inode(filp)->i_atime = file_inode(filp)->i_ctime = current_time(file_inode(filp));
return ret;
}
static int mqueue_flush_file(struct file *filp, fl_owner_t id)
{
struct mqueue_inode_info *info = MQUEUE_I(file_inode(filp));
spin_lock(&info->lock);
if (task_tgid(current) == info->notify_owner)
remove_notification(info);
spin_unlock(&info->lock);
return 0;
}
static __poll_t mqueue_poll_file(struct file *filp, struct poll_table_struct *poll_tab)
{
struct mqueue_inode_info *info = MQUEUE_I(file_inode(filp));
__poll_t retval = 0;
poll_wait(filp, &info->wait_q, poll_tab);
spin_lock(&info->lock);
if (info->attr.mq_curmsgs)
retval = EPOLLIN | EPOLLRDNORM;
if (info->attr.mq_curmsgs < info->attr.mq_maxmsg)
retval |= EPOLLOUT | EPOLLWRNORM;
spin_unlock(&info->lock);
return retval;
}
/* Adds current to info->e_wait_q[sr] before element with smaller prio */
static void wq_add(struct mqueue_inode_info *info, int sr,
struct ext_wait_queue *ewp)
{
struct ext_wait_queue *walk;
list_for_each_entry(walk, &info->e_wait_q[sr].list, list) {
if (walk->task->prio <= current->prio) {
list_add_tail(&ewp->list, &walk->list);
return;
}
}
list_add_tail(&ewp->list, &info->e_wait_q[sr].list);
}
/*
* Puts current task to sleep. Caller must hold queue lock. After return
* lock isn't held.
* sr: SEND or RECV
*/
static int wq_sleep(struct mqueue_inode_info *info, int sr,
ktime_t *timeout, struct ext_wait_queue *ewp)
__releases(&info->lock)
{
int retval;
signed long time;
wq_add(info, sr, ewp);
for (;;) {
__set_current_state(TASK_INTERRUPTIBLE);
spin_unlock(&info->lock);
time = schedule_hrtimeout_range_clock(timeout, 0,
HRTIMER_MODE_ABS, CLOCK_REALTIME);
if (ewp->state == STATE_READY) {
retval = 0;
goto out;
}
spin_lock(&info->lock);
if (ewp->state == STATE_READY) {
retval = 0;
goto out_unlock;
}
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
if (time == 0) {
retval = -ETIMEDOUT;
break;
}
}
list_del(&ewp->list);
out_unlock:
spin_unlock(&info->lock);
out:
return retval;
}
/*
* Returns waiting task that should be serviced first or NULL if none exists
*/
static struct ext_wait_queue *wq_get_first_waiter(
struct mqueue_inode_info *info, int sr)
{
struct list_head *ptr;
ptr = info->e_wait_q[sr].list.prev;
if (ptr == &info->e_wait_q[sr].list)
return NULL;
return list_entry(ptr, struct ext_wait_queue, list);
}
static inline void set_cookie(struct sk_buff *skb, char code)
{
((char *)skb->data)[NOTIFY_COOKIE_LEN-1] = code;
}
/*
* The next function is only to split too long sys_mq_timedsend
*/
static void __do_notify(struct mqueue_inode_info *info)
{
/* notification
* invoked when there is registered process and there isn't process
* waiting synchronously for message AND state of queue changed from
* empty to not empty. Here we are sure that no one is waiting
* synchronously. */
if (info->notify_owner &&
info->attr.mq_curmsgs == 1) {
struct kernel_siginfo sig_i;
switch (info->notify.sigev_notify) {
case SIGEV_NONE:
break;
case SIGEV_SIGNAL:
/* sends signal */
clear_siginfo(&sig_i);
sig_i.si_signo = info->notify.sigev_signo;
sig_i.si_errno = 0;
sig_i.si_code = SI_MESGQ;
sig_i.si_value = info->notify.sigev_value;
/* map current pid/uid into info->owner's namespaces */
rcu_read_lock();
sig_i.si_pid = task_tgid_nr_ns(current,
ns_of_pid(info->notify_owner));
sig_i.si_uid = from_kuid_munged(info->notify_user_ns, current_uid());
rcu_read_unlock();
kill_pid_info(info->notify.sigev_signo,
&sig_i, info->notify_owner);
break;
case SIGEV_THREAD:
set_cookie(info->notify_cookie, NOTIFY_WOKENUP);
netlink_sendskb(info->notify_sock, info->notify_cookie);
break;
}
/* after notification unregisters process */
put_pid(info->notify_owner);
put_user_ns(info->notify_user_ns);
info->notify_owner = NULL;
info->notify_user_ns = NULL;
}
wake_up(&info->wait_q);
}
static int prepare_timeout(const struct __kernel_timespec __user *u_abs_timeout,
struct timespec64 *ts)
{
if (get_timespec64(ts, u_abs_timeout))
return -EFAULT;
if (!timespec64_valid(ts))
return -EINVAL;
return 0;
}
static void remove_notification(struct mqueue_inode_info *info)
{
if (info->notify_owner != NULL &&
info->notify.sigev_notify == SIGEV_THREAD) {
set_cookie(info->notify_cookie, NOTIFY_REMOVED);
netlink_sendskb(info->notify_sock, info->notify_cookie);
}
put_pid(info->notify_owner);
put_user_ns(info->notify_user_ns);
info->notify_owner = NULL;
info->notify_user_ns = NULL;
}
static int prepare_open(struct dentry *dentry, int oflag, int ro,
umode_t mode, struct filename *name,
struct mq_attr *attr)
{
static const int oflag2acc[O_ACCMODE] = { MAY_READ, MAY_WRITE,
MAY_READ | MAY_WRITE };
int acc;
if (d_really_is_negative(dentry)) {
if (!(oflag & O_CREAT))
return -ENOENT;
if (ro)
return ro;
audit_inode_parent_hidden(name, dentry->d_parent);
return vfs_mkobj(dentry, mode & ~current_umask(),
mqueue_create_attr, attr);
}
/* it already existed */
audit_inode(name, dentry, 0);
if ((oflag & (O_CREAT|O_EXCL)) == (O_CREAT|O_EXCL))
return -EEXIST;
if ((oflag & O_ACCMODE) == (O_RDWR | O_WRONLY))
return -EINVAL;
acc = oflag2acc[oflag & O_ACCMODE];
return inode_permission(d_inode(dentry), acc);
}
static int do_mq_open(const char __user *u_name, int oflag, umode_t mode,
struct mq_attr *attr)
{
struct vfsmount *mnt = current->nsproxy->ipc_ns->mq_mnt;
struct dentry *root = mnt->mnt_root;
struct filename *name;
struct path path;
int fd, error;
int ro;
audit_mq_open(oflag, mode, attr);
if (IS_ERR(name = getname(u_name)))
return PTR_ERR(name);
fd = get_unused_fd_flags(O_CLOEXEC);
if (fd < 0)
goto out_putname;
ro = mnt_want_write(mnt); /* we'll drop it in any case */
inode_lock(d_inode(root));
path.dentry = lookup_one_len(name->name, root, strlen(name->name));
if (IS_ERR(path.dentry)) {
error = PTR_ERR(path.dentry);
goto out_putfd;
}
path.mnt = mntget(mnt);
error = prepare_open(path.dentry, oflag, ro, mode, name, attr);
if (!error) {
struct file *file = dentry_open(&path, oflag, current_cred());
if (!IS_ERR(file))
fd_install(fd, file);
else
error = PTR_ERR(file);
}
path_put(&path);
out_putfd:
if (error) {
put_unused_fd(fd);
fd = error;
}
inode_unlock(d_inode(root));
if (!ro)
mnt_drop_write(mnt);
out_putname:
putname(name);
return fd;
}
SYSCALL_DEFINE4(mq_open, const char __user *, u_name, int, oflag, umode_t, mode,
struct mq_attr __user *, u_attr)
{
struct mq_attr attr;
if (u_attr && copy_from_user(&attr, u_attr, sizeof(struct mq_attr)))
return -EFAULT;
return do_mq_open(u_name, oflag, mode, u_attr ? &attr : NULL);
}
SYSCALL_DEFINE1(mq_unlink, const char __user *, u_name)
{
int err;
struct filename *name;
struct dentry *dentry;
struct inode *inode = NULL;
struct ipc_namespace *ipc_ns = current->nsproxy->ipc_ns;
struct vfsmount *mnt = ipc_ns->mq_mnt;
name = getname(u_name);
if (IS_ERR(name))
return PTR_ERR(name);
audit_inode_parent_hidden(name, mnt->mnt_root);
err = mnt_want_write(mnt);
if (err)
goto out_name;
inode_lock_nested(d_inode(mnt->mnt_root), I_MUTEX_PARENT);
dentry = lookup_one_len(name->name, mnt->mnt_root,
strlen(name->name));
if (IS_ERR(dentry)) {
err = PTR_ERR(dentry);
goto out_unlock;
}
inode = d_inode(dentry);
if (!inode) {
err = -ENOENT;
} else {
ihold(inode);
err = vfs_unlink(d_inode(dentry->d_parent), dentry, NULL);
}
dput(dentry);
out_unlock:
inode_unlock(d_inode(mnt->mnt_root));
if (inode)
iput(inode);
mnt_drop_write(mnt);
out_name:
putname(name);
return err;
}
/* Pipelined send and receive functions.
*
* If a receiver finds no waiting message, then it registers itself in the
* list of waiting receivers. A sender checks that list before adding the new
* message into the message array. If there is a waiting receiver, then it
* bypasses the message array and directly hands the message over to the
* receiver. The receiver accepts the message and returns without grabbing the
* queue spinlock:
*
* - Set pointer to message.
* - Queue the receiver task for later wakeup (without the info->lock).
* - Update its state to STATE_READY. Now the receiver can continue.
* - Wake up the process after the lock is dropped. Should the process wake up
* before this wakeup (due to a timeout or a signal) it will either see
* STATE_READY and continue or acquire the lock to check the state again.
*
* The same algorithm is used for senders.
*/
/* pipelined_send() - send a message directly to the task waiting in
* sys_mq_timedreceive() (without inserting message into a queue).
*/
static inline void pipelined_send(struct wake_q_head *wake_q,
struct mqueue_inode_info *info,
struct msg_msg *message,
struct ext_wait_queue *receiver)
{
receiver->msg = message;
list_del(&receiver->list);
wake_q_add(wake_q, receiver->task);
/*
* Rely on the implicit cmpxchg barrier from wake_q_add such
* that we can ensure that updating receiver->state is the last
* write operation: As once set, the receiver can continue,
* and if we don't have the reference count from the wake_q,
* yet, at that point we can later have a use-after-free
* condition and bogus wakeup.
*/
receiver->state = STATE_READY;
}
/* pipelined_receive() - if there is task waiting in sys_mq_timedsend()
* gets its message and put to the queue (we have one free place for sure). */
static inline void pipelined_receive(struct wake_q_head *wake_q,
struct mqueue_inode_info *info)
{
struct ext_wait_queue *sender = wq_get_first_waiter(info, SEND);
if (!sender) {
/* for poll */
wake_up_interruptible(&info->wait_q);
return;
}
if (msg_insert(sender->msg, info))
return;
list_del(&sender->list);
wake_q_add(wake_q, sender->task);
sender->state = STATE_READY;
}
static int do_mq_timedsend(mqd_t mqdes, const char __user *u_msg_ptr,
size_t msg_len, unsigned int msg_prio,
struct timespec64 *ts)
{
struct fd f;
struct inode *inode;
struct ext_wait_queue wait;
struct ext_wait_queue *receiver;
struct msg_msg *msg_ptr;
struct mqueue_inode_info *info;
ktime_t expires, *timeout = NULL;
struct posix_msg_tree_node *new_leaf = NULL;
int ret = 0;
DEFINE_WAKE_Q(wake_q);
if (unlikely(msg_prio >= (unsigned long) MQ_PRIO_MAX))
return -EINVAL;
if (ts) {
expires = timespec64_to_ktime(*ts);
timeout = &expires;
}
audit_mq_sendrecv(mqdes, msg_len, msg_prio, ts);
f = fdget(mqdes);
if (unlikely(!f.file)) {
ret = -EBADF;
goto out;
}
inode = file_inode(f.file);
if (unlikely(f.file->f_op != &mqueue_file_operations)) {
ret = -EBADF;
goto out_fput;
}
info = MQUEUE_I(inode);
audit_file(f.file);
if (unlikely(!(f.file->f_mode & FMODE_WRITE))) {
ret = -EBADF;
goto out_fput;
}
if (unlikely(msg_len > info->attr.mq_msgsize)) {
ret = -EMSGSIZE;
goto out_fput;
}
/* First try to allocate memory, before doing anything with
* existing queues. */
msg_ptr = load_msg(u_msg_ptr, msg_len);
if (IS_ERR(msg_ptr)) {
ret = PTR_ERR(msg_ptr);
goto out_fput;
}
msg_ptr->m_ts = msg_len;
msg_ptr->m_type = msg_prio;
/*
* msg_insert really wants us to have a valid, spare node struct so
* it doesn't have to kmalloc a GFP_ATOMIC allocation, but it will
* fall back to that if necessary.
*/
if (!info->node_cache)
new_leaf = kmalloc(sizeof(*new_leaf), GFP_KERNEL);
spin_lock(&info->lock);
if (!info->node_cache && new_leaf) {
/* Save our speculative allocation into the cache */
INIT_LIST_HEAD(&new_leaf->msg_list);
info->node_cache = new_leaf;
new_leaf = NULL;
} else {
kfree(new_leaf);
}
if (info->attr.mq_curmsgs == info->attr.mq_maxmsg) {
if (f.file->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
} else {
wait.task = current;
wait.msg = (void *) msg_ptr;
wait.state = STATE_NONE;
ret = wq_sleep(info, SEND, timeout, &wait);
/*
* wq_sleep must be called with info->lock held, and
* returns with the lock released
*/
goto out_free;
}
} else {
receiver = wq_get_first_waiter(info, RECV);
if (receiver) {
pipelined_send(&wake_q, info, msg_ptr, receiver);
} else {
/* adds message to the queue */
ret = msg_insert(msg_ptr, info);
if (ret)
goto out_unlock;
__do_notify(info);
}
inode->i_atime = inode->i_mtime = inode->i_ctime =
current_time(inode);
}
out_unlock:
spin_unlock(&info->lock);
wake_up_q(&wake_q);
out_free:
if (ret)
free_msg(msg_ptr);
out_fput:
fdput(f);
out:
return ret;
}
static int do_mq_timedreceive(mqd_t mqdes, char __user *u_msg_ptr,
size_t msg_len, unsigned int __user *u_msg_prio,
struct timespec64 *ts)
{
ssize_t ret;
struct msg_msg *msg_ptr;
struct fd f;
struct inode *inode;
struct mqueue_inode_info *info;
struct ext_wait_queue wait;
ktime_t expires, *timeout = NULL;
struct posix_msg_tree_node *new_leaf = NULL;
if (ts) {
expires = timespec64_to_ktime(*ts);
timeout = &expires;
}
audit_mq_sendrecv(mqdes, msg_len, 0, ts);
f = fdget(mqdes);
if (unlikely(!f.file)) {
ret = -EBADF;
goto out;
}
inode = file_inode(f.file);
if (unlikely(f.file->f_op != &mqueue_file_operations)) {
ret = -EBADF;
goto out_fput;
}
info = MQUEUE_I(inode);
audit_file(f.file);
if (unlikely(!(f.file->f_mode & FMODE_READ))) {
ret = -EBADF;
goto out_fput;
}
/* checks if buffer is big enough */
if (unlikely(msg_len < info->attr.mq_msgsize)) {
ret = -EMSGSIZE;
goto out_fput;
}
/*
* msg_insert really wants us to have a valid, spare node struct so
* it doesn't have to kmalloc a GFP_ATOMIC allocation, but it will
* fall back to that if necessary.
*/
if (!info->node_cache)
new_leaf = kmalloc(sizeof(*new_leaf), GFP_KERNEL);
spin_lock(&info->lock);
if (!info->node_cache && new_leaf) {
/* Save our speculative allocation into the cache */
INIT_LIST_HEAD(&new_leaf->msg_list);
info->node_cache = new_leaf;
} else {
kfree(new_leaf);
}
if (info->attr.mq_curmsgs == 0) {
if (f.file->f_flags & O_NONBLOCK) {
spin_unlock(&info->lock);
ret = -EAGAIN;
} else {
wait.task = current;
wait.state = STATE_NONE;
ret = wq_sleep(info, RECV, timeout, &wait);
msg_ptr = wait.msg;
}
} else {
DEFINE_WAKE_Q(wake_q);
msg_ptr = msg_get(info);
inode->i_atime = inode->i_mtime = inode->i_ctime =
current_time(inode);
/* There is now free space in queue. */
pipelined_receive(&wake_q, info);
spin_unlock(&info->lock);
wake_up_q(&wake_q);
ret = 0;
}
if (ret == 0) {
ret = msg_ptr->m_ts;
if ((u_msg_prio && put_user(msg_ptr->m_type, u_msg_prio)) ||
store_msg(u_msg_ptr, msg_ptr, msg_ptr->m_ts)) {
ret = -EFAULT;
}
free_msg(msg_ptr);
}
out_fput:
fdput(f);
out:
return ret;
}
SYSCALL_DEFINE5(mq_timedsend, mqd_t, mqdes, const char __user *, u_msg_ptr,
size_t, msg_len, unsigned int, msg_prio,
const struct __kernel_timespec __user *, u_abs_timeout)
{
struct timespec64 ts, *p = NULL;
if (u_abs_timeout) {
int res = prepare_timeout(u_abs_timeout, &ts);
if (res)
return res;
p = &ts;
}
return do_mq_timedsend(mqdes, u_msg_ptr, msg_len, msg_prio, p);
}
SYSCALL_DEFINE5(mq_timedreceive, mqd_t, mqdes, char __user *, u_msg_ptr,
size_t, msg_len, unsigned int __user *, u_msg_prio,
const struct __kernel_timespec __user *, u_abs_timeout)
{
struct timespec64 ts, *p = NULL;
if (u_abs_timeout) {
int res = prepare_timeout(u_abs_timeout, &ts);
if (res)
return res;
p = &ts;
}
return do_mq_timedreceive(mqdes, u_msg_ptr, msg_len, u_msg_prio, p);
}
/*
* Notes: the case when user wants us to deregister (with NULL as pointer)
* and he isn't currently owner of notification, will be silently discarded.
* It isn't explicitly defined in the POSIX.
*/
static int do_mq_notify(mqd_t mqdes, const struct sigevent *notification)
{
int ret;
struct fd f;
struct sock *sock;
struct inode *inode;
struct mqueue_inode_info *info;
struct sk_buff *nc;
audit_mq_notify(mqdes, notification);
nc = NULL;
sock = NULL;
if (notification != NULL) {
if (unlikely(notification->sigev_notify != SIGEV_NONE &&
notification->sigev_notify != SIGEV_SIGNAL &&
notification->sigev_notify != SIGEV_THREAD))
return -EINVAL;
if (notification->sigev_notify == SIGEV_SIGNAL &&
!valid_signal(notification->sigev_signo)) {
return -EINVAL;
}
if (notification->sigev_notify == SIGEV_THREAD) {
long timeo;
/* create the notify skb */
nc = alloc_skb(NOTIFY_COOKIE_LEN, GFP_KERNEL);
if (!nc)
return -ENOMEM;
if (copy_from_user(nc->data,
notification->sigev_value.sival_ptr,
NOTIFY_COOKIE_LEN)) {
ret = -EFAULT;
goto free_skb;
}
/* TODO: add a header? */
skb_put(nc, NOTIFY_COOKIE_LEN);
/* and attach it to the socket */
retry:
f = fdget(notification->sigev_signo);
if (!f.file) {
ret = -EBADF;
goto out;
}
sock = netlink_getsockbyfilp(f.file);
fdput(f);
if (IS_ERR(sock)) {
ret = PTR_ERR(sock);
goto free_skb;
}
timeo = MAX_SCHEDULE_TIMEOUT;
ret = netlink_attachskb(sock, nc, &timeo, NULL);
if (ret == 1) {
sock = NULL;
goto retry;
}
if (ret)
return ret;
}
}
f = fdget(mqdes);
if (!f.file) {
ret = -EBADF;
goto out;
}
inode = file_inode(f.file);
if (unlikely(f.file->f_op != &mqueue_file_operations)) {
ret = -EBADF;
goto out_fput;
}
info = MQUEUE_I(inode);
ret = 0;
spin_lock(&info->lock);
if (notification == NULL) {
if (info->notify_owner == task_tgid(current)) {
remove_notification(info);
inode->i_atime = inode->i_ctime = current_time(inode);
}
} else if (info->notify_owner != NULL) {
ret = -EBUSY;
} else {
switch (notification->sigev_notify) {
case SIGEV_NONE:
info->notify.sigev_notify = SIGEV_NONE;
break;
case SIGEV_THREAD:
info->notify_sock = sock;
info->notify_cookie = nc;
sock = NULL;
nc = NULL;
info->notify.sigev_notify = SIGEV_THREAD;
break;
case SIGEV_SIGNAL:
info->notify.sigev_signo = notification->sigev_signo;
info->notify.sigev_value = notification->sigev_value;
info->notify.sigev_notify = SIGEV_SIGNAL;
break;
}
info->notify_owner = get_pid(task_tgid(current));
info->notify_user_ns = get_user_ns(current_user_ns());
inode->i_atime = inode->i_ctime = current_time(inode);
}
spin_unlock(&info->lock);
out_fput:
fdput(f);
out:
if (sock)
netlink_detachskb(sock, nc);
else
free_skb:
dev_kfree_skb(nc);
return ret;
}
SYSCALL_DEFINE2(mq_notify, mqd_t, mqdes,
const struct sigevent __user *, u_notification)
{
struct sigevent n, *p = NULL;
if (u_notification) {
if (copy_from_user(&n, u_notification, sizeof(struct sigevent)))
return -EFAULT;
p = &n;
}
return do_mq_notify(mqdes, p);
}
static int do_mq_getsetattr(int mqdes, struct mq_attr *new, struct mq_attr *old)
{
struct fd f;
struct inode *inode;
struct mqueue_inode_info *info;
if (new && (new->mq_flags & (~O_NONBLOCK)))
return -EINVAL;
f = fdget(mqdes);
if (!f.file)
return -EBADF;
if (unlikely(f.file->f_op != &mqueue_file_operations)) {
fdput(f);
return -EBADF;
}
inode = file_inode(f.file);
info = MQUEUE_I(inode);
spin_lock(&info->lock);
if (old) {
*old = info->attr;
old->mq_flags = f.file->f_flags & O_NONBLOCK;
}
if (new) {
audit_mq_getsetattr(mqdes, new);
spin_lock(&f.file->f_lock);
if (new->mq_flags & O_NONBLOCK)
f.file->f_flags |= O_NONBLOCK;
else
f.file->f_flags &= ~O_NONBLOCK;
spin_unlock(&f.file->f_lock);
inode->i_atime = inode->i_ctime = current_time(inode);
}
spin_unlock(&info->lock);
fdput(f);
return 0;
}
SYSCALL_DEFINE3(mq_getsetattr, mqd_t, mqdes,
const struct mq_attr __user *, u_mqstat,
struct mq_attr __user *, u_omqstat)
{
int ret;
struct mq_attr mqstat, omqstat;
struct mq_attr *new = NULL, *old = NULL;
if (u_mqstat) {
new = &mqstat;
if (copy_from_user(new, u_mqstat, sizeof(struct mq_attr)))
return -EFAULT;
}
if (u_omqstat)
old = &omqstat;
ret = do_mq_getsetattr(mqdes, new, old);
if (ret || !old)
return ret;
if (copy_to_user(u_omqstat, old, sizeof(struct mq_attr)))
return -EFAULT;
return 0;
}
#ifdef CONFIG_COMPAT
struct compat_mq_attr {
compat_long_t mq_flags; /* message queue flags */
compat_long_t mq_maxmsg; /* maximum number of messages */
compat_long_t mq_msgsize; /* maximum message size */
compat_long_t mq_curmsgs; /* number of messages currently queued */
compat_long_t __reserved[4]; /* ignored for input, zeroed for output */
};
static inline int get_compat_mq_attr(struct mq_attr *attr,
const struct compat_mq_attr __user *uattr)
{
struct compat_mq_attr v;
if (copy_from_user(&v, uattr, sizeof(*uattr)))
return -EFAULT;
memset(attr, 0, sizeof(*attr));
attr->mq_flags = v.mq_flags;
attr->mq_maxmsg = v.mq_maxmsg;
attr->mq_msgsize = v.mq_msgsize;
attr->mq_curmsgs = v.mq_curmsgs;
return 0;
}
static inline int put_compat_mq_attr(const struct mq_attr *attr,
struct compat_mq_attr __user *uattr)
{
struct compat_mq_attr v;
memset(&v, 0, sizeof(v));
v.mq_flags = attr->mq_flags;
v.mq_maxmsg = attr->mq_maxmsg;
v.mq_msgsize = attr->mq_msgsize;
v.mq_curmsgs = attr->mq_curmsgs;
if (copy_to_user(uattr, &v, sizeof(*uattr)))
return -EFAULT;
return 0;
}
COMPAT_SYSCALL_DEFINE4(mq_open, const char __user *, u_name,
int, oflag, compat_mode_t, mode,
struct compat_mq_attr __user *, u_attr)
{
struct mq_attr attr, *p = NULL;
if (u_attr && oflag & O_CREAT) {
p = &attr;
if (get_compat_mq_attr(&attr, u_attr))
return -EFAULT;
}
return do_mq_open(u_name, oflag, mode, p);
}
COMPAT_SYSCALL_DEFINE2(mq_notify, mqd_t, mqdes,
const struct compat_sigevent __user *, u_notification)
{
struct sigevent n, *p = NULL;
if (u_notification) {
if (get_compat_sigevent(&n, u_notification))
return -EFAULT;
if (n.sigev_notify == SIGEV_THREAD)
n.sigev_value.sival_ptr = compat_ptr(n.sigev_value.sival_int);
p = &n;
}
return do_mq_notify(mqdes, p);
}
COMPAT_SYSCALL_DEFINE3(mq_getsetattr, mqd_t, mqdes,
const struct compat_mq_attr __user *, u_mqstat,
struct compat_mq_attr __user *, u_omqstat)
{
int ret;
struct mq_attr mqstat, omqstat;
struct mq_attr *new = NULL, *old = NULL;
if (u_mqstat) {
new = &mqstat;
if (get_compat_mq_attr(new, u_mqstat))
return -EFAULT;
}
if (u_omqstat)
old = &omqstat;
ret = do_mq_getsetattr(mqdes, new, old);
if (ret || !old)
return ret;
if (put_compat_mq_attr(old, u_omqstat))
return -EFAULT;
return 0;
}
#endif
#ifdef CONFIG_COMPAT_32BIT_TIME
static int compat_prepare_timeout(const struct old_timespec32 __user *p,
struct timespec64 *ts)
{
if (get_old_timespec32(ts, p))
return -EFAULT;
if (!timespec64_valid(ts))
return -EINVAL;
return 0;
}
SYSCALL_DEFINE5(mq_timedsend_time32, mqd_t, mqdes,
const char __user *, u_msg_ptr,
unsigned int, msg_len, unsigned int, msg_prio,
const struct old_timespec32 __user *, u_abs_timeout)
{
struct timespec64 ts, *p = NULL;
if (u_abs_timeout) {
int res = compat_prepare_timeout(u_abs_timeout, &ts);
if (res)
return res;
p = &ts;
}
return do_mq_timedsend(mqdes, u_msg_ptr, msg_len, msg_prio, p);
}
SYSCALL_DEFINE5(mq_timedreceive_time32, mqd_t, mqdes,
char __user *, u_msg_ptr,
unsigned int, msg_len, unsigned int __user *, u_msg_prio,
const struct old_timespec32 __user *, u_abs_timeout)
{
struct timespec64 ts, *p = NULL;
if (u_abs_timeout) {
int res = compat_prepare_timeout(u_abs_timeout, &ts);
if (res)
return res;
p = &ts;
}
return do_mq_timedreceive(mqdes, u_msg_ptr, msg_len, u_msg_prio, p);
}
#endif
static const struct inode_operations mqueue_dir_inode_operations = {
.lookup = simple_lookup,
.create = mqueue_create,
.unlink = mqueue_unlink,
};
static const struct file_operations mqueue_file_operations = {
.flush = mqueue_flush_file,
.poll = mqueue_poll_file,
.read = mqueue_read_file,
.llseek = default_llseek,
};
static const struct super_operations mqueue_super_ops = {
.alloc_inode = mqueue_alloc_inode,
.free_inode = mqueue_free_inode,
.evict_inode = mqueue_evict_inode,
.statfs = simple_statfs,
};
static const struct fs_context_operations mqueue_fs_context_ops = {
.free = mqueue_fs_context_free,
.get_tree = mqueue_get_tree,
};
static struct file_system_type mqueue_fs_type = {
.name = "mqueue",
.init_fs_context = mqueue_init_fs_context,
.kill_sb = kill_litter_super,
.fs_flags = FS_USERNS_MOUNT,
};
int mq_init_ns(struct ipc_namespace *ns)
{
struct vfsmount *m;
ns->mq_queues_count = 0;
ns->mq_queues_max = DFLT_QUEUESMAX;
ns->mq_msg_max = DFLT_MSGMAX;
ns->mq_msgsize_max = DFLT_MSGSIZEMAX;
ns->mq_msg_default = DFLT_MSG;
ns->mq_msgsize_default = DFLT_MSGSIZE;
m = mq_create_mount(ns);
if (IS_ERR(m))
return PTR_ERR(m);
ns->mq_mnt = m;
return 0;
}
void mq_clear_sbinfo(struct ipc_namespace *ns)
{
ns->mq_mnt->mnt_sb->s_fs_info = NULL;
}
void mq_put_mnt(struct ipc_namespace *ns)
{
kern_unmount(ns->mq_mnt);
}
static int __init init_mqueue_fs(void)
{
int error;
mqueue_inode_cachep = kmem_cache_create("mqueue_inode_cache",
sizeof(struct mqueue_inode_info), 0,
SLAB_HWCACHE_ALIGN|SLAB_ACCOUNT, init_once);
if (mqueue_inode_cachep == NULL)
return -ENOMEM;
/* ignore failures - they are not fatal */
mq_sysctl_table = mq_register_sysctl_table();
error = register_filesystem(&mqueue_fs_type);
if (error)
goto out_sysctl;
spin_lock_init(&mq_lock);
error = mq_init_ns(&init_ipc_ns);
if (error)
goto out_filesystem;
return 0;
out_filesystem:
unregister_filesystem(&mqueue_fs_type);
out_sysctl:
if (mq_sysctl_table)
unregister_sysctl_table(mq_sysctl_table);
kmem_cache_destroy(mqueue_inode_cachep);
return error;
}
device_initcall(init_mqueue_fs);特点
1.向 Posix 消息队列中写入消息时并不需要有读者进程存在,与 pipes 与 FIFOs 的行为不同
2.消息队列具有内核持久性,一个消息只有在被读取后才会释放,并不会因为写入消息的进程死亡而释放
3.在一个 Posix 消息队列上读取永远返回优先级最高的消息类型中最老的消息,而 System V 消息队列则能够返回任何目标优先级的消息
4.Posix 消息队列允许当有一个消息被放进了空的队列时产生信号、初始化一个线程来通知消费者,System V 消息队列没有此功能
消息队列的释放
Posix 消息队列内部维护了一个引用计数,当引用计数大于 0 的时候目标消息队列能够从系统中移除,但是队列的释放仅在最后一次 mq_close 发生时才会触发。
mq_notify 函数
mq_notify 函数为 Posix 消息队列提供了一种异步通知机制,当消息被放到队列中时通知消费者进程, System V 消息队列就不具备这样的能力。
在调用 msgrcv 函数从 System V 消息队列中接收消息时进程可以挂起等待消息,但是在挂起期间不能执行任何其他任务。如果指定 NONBLOCK 标志调用 msgrcv 函数,进程不再阻塞但是却要持续调用此函数以确定队列中是否有数据到来,会浪费 cpu 时间。
Posix 消息队列支持通过如下两种方式来异步通知一个空的队列中有新的消息到来:
发送一个信号
创建一个线程来执行指定的函数
这两种机制通过指定不同的参数调用 mq_notify 函数来选择,mq_notify 函数的原型如下:
int mq_notify(mqd_t mqdes, const struct sigevent *sevp);
1
mq_notify 使用规则如下:
如果 sevp 参数非空,那么当前进程希望在空队列中有新的消息到达时被通知。我们说“该进程被注册为接收该队列的通知”。
如果 sevp 参数为空指针且当前进程已经使用 mq_notify 注册接收队列通知,己存在的注册将被移除。
同一时刻只支持单个进程调用 mq_notify 注册为接收某个特定队列的通知时间。
当有一个消息到达某个先前为空的队列,而且己有一个进程被注册为接收该队列的通知时,只有在没有任何线程阻塞在该队列的 mq_receive 调用中的前提下,通知才会发出。这就是说,在mq_reveive 调用中的阻塞比任何注册的通知具有更高优先级。
当该通知被发送给它的注册进程时,其注册事件被移除。该进程必须再次调用 mq_notify 以重新注册。
UNPV2 中提供了这两种不同方案的示例代码,我分别描述下关键的流程。
mq_notify 使用信号通知消息到达
直接在信号处理函数中调用 mq_notify 与 mq_receive 函数来接收数据
int main(int argc, char *argv[])
{
....................................
Signal(SIGUSR1, sig_usr1);
sigev.sigev_notify = SIGEV_SIGNAL;
sigev.sigev_signo = SIGUSR1;
Mq_notify(mqd, &sigev);
....................................
}
static void
sig_usr1(int signo)
{
ssize_t n;
Mq_notify(mqd, &sigev); /* reregister first */
n = Mq_receive(mqd, buff, attr.mq_msgsize, NULL);
printf("SIGUSR1 received, read %ld bytes\n", (long) n);
return;
}
上述代码实现了 SIGUSR1 的信号处理函数并配置 mq_notify 使用信号通知机制,通知信号为 SIGUSR。
main 函数中注册了 SIGUSR1 信号的处理函数 sig_usr1,此函数的逻辑如下:
调用 Mq_notify 重新注册通知事件
调用 Mq_receive 接收消息然后打印接收到的字节数
此实现存在的问题为不应该在信号处理函数中调用 mq_notify、mq_receive、printf 函数,这些函数并不是异步信号安全的函数。
在信号处理函数中设置标志在程序主逻辑中调用 mq_notify 与 mq_receive 函数来接收数据
核心代码如下:
for ( ; ; ) {
Sigprocmask(SIG_BLOCK, &newmask, &oldmask); /* block SIGUSR1 */
while (mqflag == 0)
sigsuspend(&zeromask);
mqflag = 0; /* reset flag */
Mq_notify(mqd, &sigev); /* reregister first */
n = Mq_receive(mqd, buff, attr.mq_msgsize, NULL);
printf("read %ld bytes\n", (long) n);
Sigprocmask(SIG_UNBLOCK, &newmask, NULL); /* unblock SIGUSR1 */
}
static void
sig_usr1(int signo)
{
mqflag = 1;
return;
}SIGUSR1 信号处理程序中仅仅设置一个全局变量 mqflag 的值,在程序主逻辑中调用 mq_notify 与 mq_receive 来接收消息。
上述代码首先修改当前线程的信号掩码,临时关闭 SIGUSR1,然后执行 sigsuspend 等待 SIGUSR1 信好到来。
sigsuspend 函数会使用 zeromask 表示的 signal mask 修改当前线程的 signal mask,然后挂起当前线程,直到有一个会执行 signal handler、终止进程的目标信号产生。当收到信号并终止进程时,sigsuspend 将不会返回。如果成功捕获到信号,sigsuspend 将会在信号处理函数执行后返回,signal 将会被恢复为调用 sigsuspend 函数之前的状态。
在 sigsuspend 返回后,程序重置 mqflag 标志并调用 Mq_notify 与 Mq_receive 接收消息并打印接收的字节数,最后调用 Sigprocmask unblock SIGUSR1 信号。
此实现存在如下问题:
由于通知消息仅在有一条新的消息被放到空的队列时产生,如果在我们能够读取第一个消息前有两个消息达到,那么只有一个通知事件产生,于是我们读取第一个消息并调用 sigsuspend 等待另一个消息,而后续可能没有新的消息产生,这样我们就会漏掉第二个消息。
为了解决这个问题,我们可以在 Mq_receive 的时候多次读取队列,这样就不会漏掉消息。示例代码如下:
for ( ; ; ) {
Sigprocmask(SIG_BLOCK, &newmask, &oldmask); /* block SIGUSR1 */
while (mqflag == 0)
sigsuspend(&zeromask);
mqflag = 0; /* reset flag */
Mq_notify(mqd, &sigev); /* reregister first */
while ( (n = mq_receive(mqd, buff, attr.mq_msgsize, NULL)) >= 0) {
printf("read %ld bytes\n", (long) n);
}
if (errno != EAGAIN)
err_sys("mq_receive error");
Sigprocmask(SIG_UNBLOCK, &newmask, NULL); /* unblock SIGUSR1 */
}在信号处理函数中设置标志在程序主逻辑中调用 sigwait 等待信号然后调用 Mq_notify 与 Mq_receive 接收数据
上文描述了在信号处理函数中设置标志的方式,一个更简单的方式是在一个函数中阻塞等待内核发送目标信号,可以通过 sigwait 函数来实现。
新的代码如下:
for ( ; ; ) {
Sigwait(&newmask, &signo);
if (signo == SIGUSR1) {
Mq_notify(mqd, &sigev); /* reregister first */
while ( (n = mq_receive(mqd, buff, attr.mq_msgsize, NULL)) >= 0) {
printf("read %ld bytes\n", (long) n);
}
if (errno != EAGAIN)
err_sys("mq_receive error");
}
}sigwait 函数将会挂起当前线程直到 signal set 中指定的信号到来,此函数会接收这个信号(将信号从信号 pending list 中移除),然后通过第二个参数返回信号值。
上面的代码进一步简化,只调用 Sigwait,然后调用 Mq_notify、mq_receive,比使用 sigsuspend 更简单。
使用 select 监听 Posix 消息队列
Posix 消息队列描述符并不是一个普通的描述符不能使用 select、epoll 函数监控此描述符。可以使用 mq_notify + pipe 的方式,mq_notify 注册监控消息队列事件,通知方式为信号,在程序初始化时创建一个 pipe,在信号处理函数中调用 write 向这个管道的 fd 中写入数据,在主程序循环中 select pipe 的 fd 来间接的监听 Posix 消息队列。 write 系统调用是异步信号安全的函数,在信号处理函数中调用不会产生问题。
示例代码如下:
Pipe(pipefd);
/* 4establish signal handler, enable notification */
Signal(SIGUSR1, sig_usr1);
sigev.sigev_notify = SIGEV_SIGNAL;
sigev.sigev_signo = SIGUSR1;
Mq_notify(mqd, &sigev);
FD_ZERO(&rset);
for ( ; ; ) {
FD_SET(pipefd[0], &rset);
nfds = Select(pipefd[0] + 1, &rset, NULL, NULL, NULL);
if (FD_ISSET(pipefd[0], &rset)) {
Read(pipefd[0], &c, 1);
Mq_notify(mqd, &sigev); /* reregister first */
while ( (n = mq_receive(mqd, buff, attr.mq_msgsize, NULL)) >= 0) {
printf("read %ld bytes\n", (long) n);
}
if (errno != EAGAIN)
err_sys("mq_receive error");
}
}
static void
sig_usr1(int signo)
{
Write(pipefd[1], "", 1); /* one byte of 0 */
return;
}
向 pipe 中写入的数据内容并不重要,重要的是写入这个动作触发 select 系统调用捕获事件,间接的绑定到 Posix 消息队列的通知事件。
创建一个线程的执行函数的方式
示例代码如下:
int
main(int argc, char **argv)
{
if (argc != 2)
err_quit("usage: mqnotifythread1 <name>");
mqd = Mq_open(argv[1], O_RDONLY | O_NONBLOCK);
Mq_getattr(mqd, &attr);
sigev.sigev_notify = SIGEV_THREAD;
sigev.sigev_value.sival_ptr = NULL;
sigev.sigev_notify_function = notify_thread;
sigev.sigev_notify_attributes = NULL;
Mq_notify(mqd, &sigev);
for ( ; ; )
pause(); /* each new thread does everything */
exit(0);
}
static void
notify_thread(union sigval arg)
{
ssize_t n;
void *buff;
printf("notify_thread started\n");
buff = Malloc(attr.mq_msgsize);
Mq_notify(mqd, &sigev); /* reregister */
while ( (n = mq_receive(mqd, buff, attr.mq_msgsize, NULL)) >= 0) {
printf("read %ld bytes\n", (long) n);
}
if (errno != EAGAIN)
err_sys("mq_receive error");
free(buff);
pthread_exit(NULL);
}
sigev 中的 sigev_notify 设置为 SIGEV_THREAD 表示通过创建一个线程执行函数方式监听处理消息队列事件,sigev_notify_function 中设置了需要执行的函数指针为 notify_thread,此函数的主要逻辑如下:
申请一块 buff 用以接收消息
重新执行 Mq_notify 函数重新监听事件
调用 mq_receive 函数从队列中接收消息
在这种实现中,主线程可以做其它的任务,在示例程序中主线程啥也不干。这种创建线程执行函数的机制表面上看上去挺简单,可 mq_notify 注册的 notify_thread 是一个用户态虚拟内存空间的代码地址,它不能在内核态执行,意味着线程的创建与回调的执行都在用户态完成,那内核又是如何将事件投递到新创建的线程,让此线程执行回调来处理消息呢?
在进一步探讨前,先在我的本地 linux 环境上运行下示例程序,运行 log 如下:
[longyu@debian] pxmsg $ ./mqcreate /test1
[longyu@debian] pxmsg $ ./mqnotifythread1 /test1
notify_thread started
read 50 bytes
notify_thread started
read 50 bytes
notify_thread started
read 1024 bytes
mqnotifythread demo 能够正常接收消息。
1.信号量概述
信号量也叫信号灯,用于进程/线程同步或互斥的机制
信号灯的类型 : Posix无名信号量 主要用于主要用于线程间的同步和互斥
Posix有名信号量 主要用于进程间同步和互斥
System V 信号量
信号量与其他进程间通信机制不大相同,它主要提供对进程共享资源访问控制机制,相当于内存中的标志,进程可以根据它判定是否能访问某些共享资源,同时,进程也可以修改该标志。除了同于访问控制外,可以用于进程同步。信号量本质上是一个非负的整数计数器。
System V信号量不是用来在进程间传输数据的,而是用来同步进程的动作。
信号量的一个常见用途是同步对一块共享内存的访问以防止出现一个进程在访问共享内存的同时另一个进程更新这块内存的情况,
在控制进程的动作方面,信号量本身并没有任何意义,它的意义仅由使用信号量的进程赋予其的关联关系来确定。
进程之间会达成协议将一个信号量与一种共享资源关联起来,如一块共享内存区域。信号量还有其他用途,如在 fork()之后同步父进程和子进程。
使用Svstem V信号量的常规操作步骤:
使用semget()创建或者打开一个信号量集。
使用semctl() SETVAL 或 SETALL 操作初始化集合中的信号量(只有一个进程需要完成这个任务)。
使用semop()操作信号量的值。使用信号量的进程通常会使用这些操作来标识一种共享资源的获取和释放。
当所有进程都不再需要使用信号量集之后semctl() IPC_RMID操作删除这个集合(只有一个进程需要完成这个任务)。
System V信号量是以分配被称为信号量集的组为单位进行的。在使用semget()系统调用创建集合的时候需要指定集合中的信号量数量。
虽然同一时刻通常只会操作一个信号量,但通过semop()系统调用可以原子的在同一个集合中的多个信号量之上执行一组操作。
二、使用Svstem V信号量
1.信号量数据结构
信号量数据结构是信号量程序设计中经常使用的数据结构,由于在之后的函数经常用到,这里将结构的原型列出来,便于查找:
//每个信号量集,内核维护如下信息结构:<sys/sem.h>
struct semid_ds
{
struct ipc_perm sem_perm; /* 操作权限结构 */
struct sem *sem_base; //指向sem结构数组指针
time_t sem_otime; /* 上次semop()时间 */
time_t sem_ctime; /* 上次由semctl()更改的时间 */
ushort sem_nsems; /* 集合中的信号量数量 */
};
//ipc_perm含有当前信号量访问权限
struct ipc_perm{
uid_t uid;
gid_t gid;
uid_t cuid;
gid_t cgid;
mode_t mode;
ulong_t seq;
key_t key;
}
//维护某个信号量的一组的内部数据结构,一个信号量集的每个成员如下这个结构描述:
struct sem{
ushort_t semval;
short sempid;
ushort_t semncnt;
ushort_t semzcnt;
}
除维护一 个信号量集内每个信号量的实际值之外,内核还给该集合中每个信号量维护另外三个信息:对其值执行最后 一 次操作的进程的进程ID 、等待其值增长的进程数计数以及等待其值变为0的进程数计数。
2.新建信号量semget()
semget()函数用于创建一个新的信号量集合,或者访问现有的集合。
其原型如下,其中第1个参数key是ftok生成键值,第2个参数nsems参数可以指定新的集合中应该创建的信号量的数目,第3个参数semflsg是打开信号的方式。
#include<sys/types.h>
#include<sys/sem.h>
int semget(key_t key,int nsems,int semflg);
//key是ftok生成键值
//nsems参数可以指定新的集合中应该创建的信号量的数目
//semflsg是打开信号的方式semflsg属性值:
IPC_CREAT: 如果内核中不存在这样的信号量集合,则把它创建出来。
IPC_EXCL:
当与IPC_CREAT一起使用时,如果信号量集合早已存在,则操作将失败。
单独使用IPC _ CREAT, semget()或者返回新创建的信号量集合的信号量集合标识符;或者返回早已存在的具有同 个关键字值的集合的标识符。
同时使用IPC_EXCL和IPC_CREAT, 那么将有两种可能的结果:如果集合不存在,则创建一个新的集合;如果集合早已存在,则调用失败,并返回-1。
IPC_EXCL本身是没有什么用处的,但当与IPC_CREAT组合使用时,它可以用于防止为了访问而打开现有的信号量集合。
//CreateSem()函数按照用户的键值生成一个信号量,把信号量的初始值设为用户输入的value。
typedef int sem_t;
union semun{ //信号量操作的联合结构
int val; //整型变量
struct semid_ds *buf;//semid_ds结构指针
unsigned short *array;//数组类型
}arg;//全局变量
sem_t CreateSem(key_t key,int value)//建立信号量,魔数key和信号量的初始值value
{
union semun sem; //信号量结构变量
sem_t semid;//信号量ID
sem.val = val;//设置初值
semid = semget(key,0,IPC_CREAT|0666);//获得信号量ID
if(-1 == semid)
{
printf("create semaphore error\n");
return -1;
}
semctl(semid,0,SETVAL,sem);//发送命令,建立value个初始值的信号量
return semid;
}3.控制信号量参数semctl()函数
与文件操作的ioctl()函数类似,信号量的其他操作是通过函数semctl()来完成。函数semctl()的原型如下:
#include<sys/types.h>
#include<sys/ipc.h>
#inlcude<sys/sem.h>
int semctl(int semid,int semnum,int cmd,/*union semun arg*/...);
//第4个参数指向如下联合体:
union semun{
int val;//当执行SETVAL命令时将用到这个成员,它用于指定要把信号量设置成什么值。
struct semid_ds *buf;//在命令IPC_STAT/IPC_SET中使用。它代表内核中所使用的内部信号量数据结构的一 个复制。
ushort *array;//用在GETALL/SET ALL命令中的 一 个指针。它应当指向整数值的一 个数组。在设置或获取集合中所有信号量的值的过程中,将会用到该数组。
struct seminfo *buf;//信号量内部结构
}; 函数semctl()用于在信号量集合上执行控制操作。这个调用类似于函数msgctl(), msgctl() 函数是用于消息队列上的操作。
第1个参数是关键字的值(在我们的例子中它是调用semget()函数所返回的值)。
第2个参数(semun)是将要执行操作的信号量的编号,它是信号量集合的 一 个索引值,对于集合中的第 1个信号量 (有可能只有这一 个信号量)来说,它的索引值将是 一 个为0 的值。
cmd 参数代表将要在集合上执行的命令。其取值如下所述:
IPC_STAT: 获取某个集合的semid_ds结构,并把它存储在semun联合体的buf参数所指定的地址中。
IPC_SET:设置某个集合的semid_ds结构的ipc_perm成员的值。该命令所取的值是从semun联合体的buf参数中取到的。
IPC_RMID : 从内核删除该集合。
GETALL: 用于获取集合中所有信号量的值。整数值存放在无符号短整数的一个数组中,该数组由联合体的array成员所指定。
GETNCNT: 返回当前正在等待资源的进程的数目。
GETPID: 返回最后一 次执行 semop 调用的进程的PID 。
GETVAL: 返回集合中某个信号量的值。
GETZCNT: 返回正在等待资源利用率达到百分之百的进程的数目。
SETALL: 把集合中所有信号量的值,设置为联合体的array成员所包含的对应值。
SETVAL: 把集合中单个信号量的值设置为联合体的val成员的值。
例:利用semctl()函数设置和获得信号量的值构建同用的函数:
void SetvalueSem(sem_t semid,int value)//设置信号的值,通过SETVAL实现,所设置的值通过联合变量sem的val域实现
{
union semun sem;//信号量操作的结构
sem.val = value;//初始化
semctl(semid,0,SETVAL,sem);//设置信号量的值
}
int GetvalueSem(sem_t semid)//获得信号量的值
{
union semun sem;//信号量操作的结构
return semctl(semid,0,GETVAL,sem);//获得信号量的值,通过GETVAL会是其返回给定信号量的当前值
}
void DestroySem(sem_t semid)//销毁信号量
{
union semun sem;//信号量操作的结构
sem.val = 0;//初始化
semctl(semid,0,IPC_RMID,sem);//设置信号量
}4.信号量操作函数semop()
信号量的P、V操作是通过向语已经建立好的信号量(使用semget()函数),发送命令来完成的。向信号量发送命令的函数是semop(),原型如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
int semop(int semid,struct sembuf *sops,unsigned nsops);
int semtimedop(int semid, struct sembuf *sops, unsigned nsops,
struct timespec *timeout);参数sops是一个指针,指向将要在信号量集合上执行操作的一个数组,参数nsops则是该数组中操作的个数。
sops参数指向的是类型为sembuf结构的一个数组
struct sembuf{
ushort sem_num;//信号量的编号
short sem_op;//信号量的操作;将要执行的操作(正、负或者零)。
short sem_flag;//信号量的操作标志;如果sem_op为负,则从信号量中减掉一个值。为正,则从信号量中加上值。如果为0,则将进程设置为睡眠状态,直到信号量的值为0为止。
};
//例如
struct sembuf sem= {O, +1, NOWAJT};
//表示对信号量 0,进行加1的操作。例:用函数semop()可以构建基本的P、V操作,代码如下所示。Sem_P构建{0, +1, NOWAJT} 的sembuf结构来进行增加1个信号量值的操作; Sem_V构建{0, -1, NOWAJT}的sembuf结构来进行减少1个信号量的操作,所对应的信号量由函数传入(semid)。
int Sem_P(sem_t semid) //增加信号量
{
struct sembuf sops={0,+1,IPC_NOWAIT};//建立信号量结构值
return (semop(semid,&sops,1));//发送命令
}
int Sem_V(sem_t semid) //减小信号量值
{
struct sembuf sops = {0,-1,IPC_NOWAIT};//建立信号量结构值
return (semop(semid,&sops,1));//发送信号量操作方法
}5.信号例子
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
typedef int sem_t;
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
}arg;
sem_t CreateSem(key_t key,int value)
{
union semun sem;
sem_t semid;
sem.val = value;
semid = semget(key,0,IPC_CREAT|0666);
if(-1 == semid)
{
printf("create semaphore error\n");
return -1;
}
semctl(semid,0,SETVAL,sem);
return semid;
}
int Sem_P(sem_t semid)
{
struct sembuf sops = {0,+1,IPC_NOWAIT};
return (semop(semid,&sops,1));
}
int Sem_V(sem_t semid)
{
struct sembuf sops = {0,-1,IPC_NOWAIT};
return (semop(semid,&sops,1));
}
void SetvalueSem(sem_t semid,int value)
{ union semun sem;
sem.val = value;
semctl(semid,0,SETVAL,sem);
}
int GetvalueSem(sem_t semid)
{
union semun sem;
return semctl(semid,0,GETVAL,sem);
}
void DestroySem(sem_t semid)
{
union semun sem;
sem.val = 0;
semctl(semid,0,IPC_RMID,sem);
}
int main(void)
{
key_t key;
int semid;
char i;
int value = 0;
key = ftok("/ipc/sem",'a');
semid = CreateSem(key,100);
for(i=0;i<=3;i++){
Sem_P(semid);
Sem_V(semid);
}
value = GetvalueSem(semid);
printf("信号量为:%d\n",value);
DestroySem(semid);
return 0;
}四、多个阻塞信号量操作的处理
因减少一个信号量值而发生阻塞的进程对该信号量减去的值是一样的,那么当条件允许是到底哪个进程会首先被允许执行操作是不确定的。
若多个因减少一个信号量而发生阻塞的进程对该信号量减去的值是不同的,那么会按照先满足条件先满足的顺序来进行:
1. 若一个信号量的当值为 0,进程 A 请求将信号量值减去 2,然后进程 B 请求将信号量值减去 1。如果第三个进程将信号量值加上了 1,那么进程 B 首先会被解除阻塞并执行它的操作
2. 这种场景可能会导致饿死情况的发生,即一个进程因信号量的状态无法满足所请求的操作继续往前执行的条件而永远保持阻塞。当一个进程因试图在多个信号量上执行操作而发生阻塞时也可能会出现饿死的情况。考虑下面的这些在一组信号量上执行的操作,两个信号量的初始值都为 0
1. 进程 A 请求将信号量 0 和 1 的值减去 1(阻塞)。
2. 进程 B 请求将信号量 0 的值减去 1(阻塞)
3. 进程 C 将信号量 0 的值加上 1五、信号量撤销值
问题:若进程在调用完信号量值之后主动或被动的终止了,可能会给其他使用这个信号量的进程带来问题,因为它们可能因等待这个信号量而被阻塞着——即等待已经被终止的进程撤销对信号量所做的变更。
解决:在通过semop()修改一个信号量值时可以使用SEM_UNDO标记。当指定这个标记时,内核会记录信号量操作的效果,然后在进程终止(主动或被动)时撤销这个操作。
使用semctl SETVAL或者SETALL操作设置一个信号量时,所有使用这个信号量的进程中相应的 semadj 会被清空(即设置为 0)。
fork()创建的子进程不会继承其父进程的 semadj 值,因为对于子进程来讲撤销其父进程的信号量操作毫无意义。另一方面,semadj 值会在 exec()中得到保留。这样就能在使用SEM_UNDO 调整一个信号量值之后通过 exec()执行一个不操作该信号量的程序,同时在进程终止时原子地调整该信号量。(这项技术可以允许另一个进程发现这个进程何时终止。)
六、信号量初始化竞争条件
当实际操作创建一个新的信号量集时,semget会将该集合中各个信号量的值初始化为0,但有些系统却不能保证做到。
早期的System V实现根本不对信号量值进行初始化,存放新创建信号量集的那部分内存空间最近一次使用时的值就是各个信号量的初始值。
X/Open XPG3可移植性指南和Unix98纠正了这个忽略行为,明确地陈述semget并不初始化各个信号量的值,这个初始化必须通过以SET_VAL命令(设置集合中一个值)或SETALL命令(设置集合中所有值)调用semctl来完成。
System V信号量的设计中,创建一个信号量集(semget)并将它初始化(semctl)需两次函数调用是一个致命的缺陷,容易产生竞争状态。
一个不完备的解决方案是:
在调用semget时指定IPC_CREAT| IPC_EXCL标志,这样只有一个进程(首先调用semget的那个进程)创建所需信号量。
该进程随后初始化该信号量,其他进程会收到来自semget的一个EEXIST错误,于是再次调用semge,不过这次调用既不指定IPC_CREAT标志,也不指定IPC_EXCL标志。
但是竞争状态任然存在。两个进程几乎同时尝试创建初始化一个只有单个成员的信号量集,执行代码如下:
oflag = IPC_CREAT|IPC_EXCL|SVSEM_MODE;
if((semid = semget(key,1,oflag))>0){
arg.val = 1;
semctl(semid,0,SETVAL,arg);
}else if(errno == EEXIST){
semid = semget(key,1,SVSEM_MODE);
}else
err_sys("semget error");
semop(semid,...);发生情形:
第一个进程执行1-3行,然后内核阻止指行。
内核启动第二个进程,执行1、2、5、6、9行。
尽管成功创建该信号量的第一个进程将是初始化该信号量的唯一进程,但是由于它完成创建和初始化操作需花两个步骤,因此内核有可能在这两个步骤之间把上下文切换到另一个进程。
切换的进程随后可以使用该信号量,但是该信号量的值尚未由第一个进程初始化。当第二个进程执行第9行时,该信号量的值是不确定的。
绕过这个竞争状态的方法:
当semget创建一个新的信号量集时,其semid_ds结构的s_otime成员保证被置为0。该成员只是在semop调用成功时才被设置为当前值。
因此,上面例子中的第二个进程再次成功地调用semget 后,必须以IPC_STAT命令调用semctl。
然后等待sem_otime变为非零值,到时就可断定该信号赁已被初始化,而且对它进行初始化的郑个进程已成功地调用sernop。
意味着创建该信号量的那个进程必须初始化它的值,而且必须在任何其他进程可以使用该信号量之前调用semop。
例:若一个应用程序由多个平等的进程构成,这些进程使用一个信号量来协调相互之间的动作。由于无法保证哪个进程会首先使用信号量量(这就是地位平等的含义),因此每个进程都要做好信号量不存在时创建和初始化信号量的准备。错误的演示:
#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <zconf.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <errno.h>
#include <cstdarg>
#include <sys/types.h> /* For portability */
#include <sys/sem.h>
#include <time.h>
#include <memory.h>
union semun { /* Used in calls to semctl() */
int val;
struct semid_ds * buf;
unsigned short * array;
#if defined(__linux__)
struct seminfo * __buf;
#endif
};
int main(int argc, char *argv[])
{
int semid, key, perms;
struct sembuf sops[2];
key = 12345;
perms = S_IRUSR | S_IWUSR;
semid = semget(key, 1, IPC_CREAT | IPC_EXCL | perms);
if (semid != -1) { /* Successfully created the semaphore */
union semun arg;
/* XXXX */
arg.val = 0; /* So initialize it */
if (semctl(semid, 0, SETVAL, arg) == -1){
perror("semctl");
exit(EXIT_FAILURE);
}
} else { /* We didn't create semaphore set */
if (errno != EEXIST) { /* Unexpected error from semget() */
perror("semget 1");
exit(EXIT_FAILURE);
} else { /* Someone else already created it */
semid = semget(key, 1, perms); /* So just get ID */
if (semid == -1){
perror("semget 2");
exit(EXIT_FAILURE);
}
}
}
/* Now perform some operation on the semaphore */
sops[0].sem_op = 1; /* Add 1 */
sops[0].sem_num = 0; /* ... to semaphore 0 */
sops[0].sem_flg = 0;
if (semop(semid, sops, 1) == -1){
perror("semop");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}上面代码问题在于:首先,进程 B在一个未初始化的信号量(即其值是一个任意值)上执行了一个 semop()。其次,进程 A 中的 semctl()调用覆盖了进程 B 所做出的变更。
解决方案依赖于一个现已成为标准的特性,即与这个信号量集相关联的semid_ds 数据结构中的 sem_otime 字段的初始化。
在一个信号量集首次被创建时,sem_otime字段会被初始化为 0,并且只有后续的 semop()调用才会修改这个字段的值。
根据这个特性来消除上面描述的竞争条件,即只需要插入额外的代码来强制第二个进程(即没有创建信号量的那个进程)等待,直到第一个进程既初始化了信号量又执行了一个更新 sem_otime字段但不修改信号量的值的 semop()调用为止。
#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <zconf.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <errno.h>
#include <cstdarg>
#include <sys/types.h> /* For portability */
#include <sys/sem.h>
#include <time.h>
#include <memory.h>
union semun { /* Used in calls to semctl() */
int val;
struct semid_ds * buf;
unsigned short * array;
#if defined(__linux__)
struct seminfo * __buf;
#endif
};
int
main(int argc, char *argv[])
{
int semid, key, perms;
struct sembuf sops[2];
if (argc != 2 || strcmp(argv[1], "--help") == 0){
printf("%s sem-op\n", argv[0]);
exit(EXIT_FAILURE);
}
key = 12345;
perms = S_IRUSR | S_IWUSR;
semid = semget(key, 1, IPC_CREAT | IPC_EXCL | perms);
if (semid != -1) { /* Successfully created the semaphore */
union semun arg;
struct sembuf sop;
sleep(5);
printf("%ld: created semaphore\n", (long) getpid());
arg.val = 0; /* So initialize it to 0 */
if (semctl(semid, 0, SETVAL, arg) == -1){
printf("semctl 1");
exit(EXIT_FAILURE);
}
printf("%ld: initialized semaphore\n", (long) getpid());
/* Perform a "no-op" semaphore operation - changes sem_otime
so other processes can see we've initialized the set. */
sop.sem_num = 0; /* Operate on semaphore 0 */
sop.sem_op = 0; /* Wait for value to equal 0 */
sop.sem_flg = 0;
if (semop(semid, &sop, 1) == -1){
printf("semop");
exit(EXIT_FAILURE);
}
printf("%ld: completed dummy semop()\n", (long) getpid());
} else { /* We didn't create the semaphore set */
if (errno != EEXIST) { /* Unexpected error from semget() */
printf("semget 1");
exit(EXIT_FAILURE);
} else { /* Someone else already created it */
const int MAX_TRIES = 10;
int j;
union semun arg;
struct semid_ds ds;
semid = semget(key, 1, perms); /* So just get ID */
if (semid == -1){
printf("semget 2");
exit(EXIT_FAILURE);
}
printf("%ld: got semaphore key\n", (long) getpid());
/* Wait until another process has called semop() */
arg.buf = &ds;
for (j = 0; j < MAX_TRIES; j++) {
printf("Try %d\n", j);
if (semctl(semid, 0, IPC_STAT, arg) == -1){
printf("semctl 2");
exit(EXIT_FAILURE);
}
if (ds.sem_otime != 0) /* Semop() performed? */
break; /* Yes, quit loop */
sleep(1); /* If not, wait and retry */
}
if (ds.sem_otime == 0) /* Loop ran to completion! */
{
printf("Existing semaphore not initialized");
exit(EXIT_FAILURE);
}
}
}
/* Now perform some operation on the semaphore */
sops[0].sem_num = 0; /* Operate on semaphore 0... */
sops[0].sem_op = atoi(argv[1]);
sops[0].sem_flg = 0;
if (semop(semid, sops, 1) == -1){
printf("semop");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}共享内存允许两个或多个进程共享物理内存的同一块区域(通常被称为段)。由于一个共享内存段会成为一个进程用户空间内存的一部分,因此这种IPC 机制无需内核介入。所有需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他所有共享同一个段的进程可用。。速度比起管道或者消息队列更快
另一方面,共享内存这种IPC机制不由内核控制意味着需要通过某些同步方法使得不会出现同时访问共享内存的情况。System V信号量就是天生用来实现这种同步的方法。当然,还可以使用其他方法,比如POSIX信号量和文件锁
在 mmap()术语中,一块内存区域会被映射到一个地址,而在 System V 术语中,一个共享内存段是被附加到一个地址上的。这些术语是等价的,它们在术语上之所以存在差异是因为这两组 API 的起源不同
概述
为使用一个共享内存段通常需要执行下面的步骤:
调用shmget()创建一个新共享内存段或者取得一个既有共享内存段的标识符。
使用shmat()来附上共享内存段,即令该段成为调用进程的虚拟内存的一部分
此时在程序中就可以向对待其他可用内存那样对待这个共享内存段。为引用这块共享内存,程序需要使用由shmat()调用返回的addr值。它是一个指向进程的虚拟地址空间中该共享内存段的起点的指针
调用shmdt()来分离这个共享段。在这个调用之后,进程就无法再引用这块共享内存了。这一步是可选的,并且在进程终止时会自动完成这一步
调用shmctl()来删除共享内存段。只有当当前所有附加内存段的进程都与之分离之后内存段才会被销毁。只有一个进程需要执行这一步。
shmget:创建和打开一个共享内存段
shmget()系统调用创建一个新共享内存或获取一个既有段的标识符。新创建的内存段中的内容会被初始化为0:
NAME
shmget - allocates a System V shared memory segment
SYNOPSIS
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg); shmflg:
包含9个比特的权限标志,它们的作用与创建文件时使用的mode标志是一样。
权限标志对共享内存非常有用,因为它允许一个进程创建的共享内存可以被共享内存的创建者所拥有的进程写入,同时其它用户创建的进程只能读取共享内存。我们可以利用这个功能来提供一种有效的对数据进行只读访问的方法,通过将数据放共享内存并设置它的权限,就可以避免数据被其他用户修改。
可以对下列标记中的零个或多个取 OR 来控制 shmget()的操作
IPC_CREAT :如果不存在与指定的 key 对应的段,那么就创建一个新的。
IPC_EXCL :如果同时指定了IPC_CREAT 并且与指定的key 对应的段已经存在,那么返回EEXIST 错误
SHM_HUGETLB(自 Linux 2.6 起):
特权(CAP_IPC_LOCK)进程能够使用这个标记创建一个使用巨页(huge page)的共享内存段。
巨页是很多现代硬件架构提供的一项特性用来管理使用超大分页尺寸的内存。(如 x86-32 允许使用 4MB 的分页大小来替代 4KB 的分页大小。)
在那些拥有大量内存的系统上并且应用程序需要大量内存块时,使用巨页可以降低硬件内存管理单元的超前转换缓冲区器(translation look-aside buffer,TLB)的数量
SHM_NORESERVE(自 Linux 2.6.15 起):这个标记在 shmget()中所起的作用与MAP_NORESERVE 标记在 mmap()中所起的作用一样
返回值:
创建成功,则返回一个非负整数,即共享内存标识;
共享内存标识,它唯一的标识了一个IPC对象,这个IPC对象可以是消息队列或信号量或共享内存中的任意一种类型(IPC对象是活动在内核级别的一种进程间通信的工具)
这个标识符是一个非负整数,在Linux系统中标识符被声明成整数,所以可能存在的最大标识符为65535。
这里标识符与文件描述符有所不同,使用open函数打开一个文件时,返回的文件描述符的值为当前进程最小可用的文件描述符数组的下标。IPC对象删除或创建时相应的标识符的值会不断增加到最大的值,归零循环分配使用。
如果失败,则返回-1.
创建完毕之后,我们可以通过 ipcs 命令查看这个共享内存。
#ipcs --shmems
------ Shared Memory Segments ------
key shmid owner perms bytes nattch status
0x00000000 19398656 marc 600 1048576 2 dest
shmat:加载共享内存
NAME
shmat, shmdt - System V shared memory operations
SYNOPSIS
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg); 如果一个进程想要访问这一段共享内存,需要将这个内存加载到自己的虚拟地址空间的某个位置,通过 shmat 函数,就是 attach 的意思
将共享内存端挂载到自己地址空间
第一次创建共享内存段时,它不能被任何进程访问。要想启动对该内存的访问,必须将其连接到一个进程的地址空间
参数:
shmid : 是由shmget函数返回的共享内存标识。
shmaddr :
shmaddr 就是要指定 attach 到这个地方。
但是这个地址的设定难度比较大,除非对于内存布局非常熟悉,否则可能会 attach 到一个非法地址。
所以,通常的做法是将 shmaddr 设为 NULL,让内核选一个合适的地址。
shmflg : 是一组标志位,通常为0。它还可取:
SHM_RND,用以决定是否将当前共享内存段连接到指定的shmaddr上。该参数和shm_addr联合使用,用来控制共享内存连接的地址,除非只计划在一种硬件上运行应用程序,否则不要这样指定。填0让操作系统自己选择是更好的方式。
SHM_RDONLY:
如果设置了,那么标识共享内存只读访问。试图更新只读段中的内容会导致段错误(SIGSEGV 信号)的发生。
否则以读写方式连接此内存段
SHM_REMAP。在指定了这个标记之后shmaddr的值必须为非NULL。
这个标记要求 shmat()调用替换起点在 shmaddr 处长度为共享内存段的长度的任何既有共享内存段或内存映射。
一般来讲,如果试图将一个共享内存段附加到一个已经在用的地址范围时将会导致 EINVAL 错误的发生。
SHM_REMAP 是一个非标准的 Linux 扩展
返回值:
成功:真正被 attach 的地方。
开发人员可以像对待普通的 C 指针那样对待这个值,段与进程的虚拟内存的其他部分看起来毫无差异。
通常会将 shmat()的返回值赋给一个指向某个由程序员定义的结构的指针以便在该段上设定该结构。
失败返回NULL
下表对 shmat()的 shmflg 参数中能取 OR 的常量进行了总结
值 描 述
SHM_RDONLY 附加只读段
SHM_REMAP 替换位于 shmaddr 处的任意既有映射
SHM_RND 将 shmaddr 四舍五入为 SHMLBA 字节的倍数
shmdt:解除绑定共享内存
NAME
shmat, shmdt - System V shared memory operations
SYNOPSIS
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr); 如果共享内存使用完毕,可以通过 shmdt 解除绑定
当一个进程不再需要访问一个共享内存段时就可以调用shmdt()来讲该段分离出其虚拟地址空间了。
注意:仅仅是共享内存分离但并未删除它,其标识符及其相关数据结构都在;直到某个进程的IPC_RMID命令的调用shmctl特地删除它为止,
shmaddr参数标识出了待分离的段,它应该是之前由shmat()调用返回的一个值。
返回:
成功时,返回0
失败时,返回-1.
通过fork()创建的子进程会继承其父进程附加的共享内存段。因此,共享内存为父进程和子进程之间的通信提供了一种简单的IPC方法。
在一个exec()中,所有附加的共享内存段都会被分离。在进程终止后共享内存段也会自动被分离
共享内存在虚拟内存中的位置
段被附加在向上增长的堆和向下增长的栈之间未被分配的空间中。为给栈和堆的增长腾出空间,附加共享内存段的虚拟地址从0x4000000开始。内存映射和共享库也是被放置在这个区域中的(共享内存映射和内存段默认被放置的位置可能会有些不同,这依赖于内核版本和进程的 RLIMIT_STACK 资源限制的设置。)
通过 Linux 特有的/proc/PID/maps 文件能够看到一个程序映射的共享内存段和共享库的位置
从内核 2.6.14 开始,Linux 还提供了/proc/PID/smaps 文件,它给出了有关一个进程中各个映射的内存消耗方面的更多信息。更多细节可参考 proc(5)手册
在共享内存中存储指针
每个进程可能会用到不同的共享库和内存映射,并且可能附加不同的共享内存段集。因此如果遵循推荐的做法,让内核来选择将共享内存段附加到何处,那么一个段在各个进程中可能会被附加到不同的地址上。正因为这个原因,在共享内存段中存储指向段中其他地址的引用时应该使用(相对)偏移量,而不是(绝对)指针
例如,,假设一个共享内存段的起始地址为 baseaddr(即baseaddr 的值为 shmat()的返回值)。再假设需要在 p 指向的位置处存储一个指针,该指针指向的位置与 target 指向的位置相同。如下图所示:
那么设置*p:
*p = target; //error
这段代码存在的问题是当共享内存段被附加到另一个进程中时target指向的位置可能会位于一个不同的虚拟地址处,这意味着在那个进程中那个策划中存储在p 中的值是是无意义的。正确的做法是在p 中存储一个偏移量,如下所示。
*p = (target - baseaddr);
在解引用这种指针时需要颠倒上面的步骤
target = baseaddr + *p;
shmctl:共享内存控制操作
shmctl()系统调用在shmid标识的共享内存段上执行一组控制操作:
NAME
shmctl - System V shared memory control
SYNOPSIS
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf); cmd参数规定了待执行的控制操作:
(1)常规控制操作
IPC_RMID:删除共享内存(如果共享内存不需要用了,那么就一定要去主动去删除它)
标记这个共享内存段以及其关联的shmid_ds数据结构以便删除。
如果当前没有进程附加该段,那么就会执行删除操作,否则就在所有进程都已经与该段分离(即当 shmid_ds 数据结构中 shm_nattch字段的值为 0 时)之后再执行删除操作
在 Linux 上,如果已经使用 IPC_RMID 将一个共享段标记为删除,但因为还存在一些进程仍然附加了该段而没有删除该段,那么其他进程还能够附加该段。但这种行为是不可移植的:
大多数 UNIX 实现会阻止进程将被标记为删除的段附加到自己的地址空间中。(SUSv3 并没有对这种情况的处理方式进行规定。)一些 Linux 应用程序已经依赖了这种行为,这也是 Linux 为何不改变这种行为以与其他 UNIX 实现匹配的原因
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET :如果进程有足够的权限就把共享内存的当前关联值设置为shmid_ds结构中给出的值
(2)加锁和解锁共享内存
一个共享内存段可以被锁进RAM中,这样它永远不会被交换出去了。这种做法能带来性能上的提升,因为一旦段中的所有分页都驻留在内存中,就能确保一个应用程序在访问分页时永远不会因发生分页故障而被延迟。通过shmctl()可以完成两种锁操作
SHM_LOCK 操作将一个共享内存段锁进内存
SHM_UNLOCK 操作为共享内存段解锁以允许它被交换出去。
在版本号小于 2.6.10 的 Linux 上只有特权(CAP_IPC_LOCK)进程才能够将一个共享内存段锁进内存。自 Linux 2.6.10 开始,非特权进程能够在一个共享内存段上执行加锁和解锁操作,其前提是进程的有效用户 ID 与段的所有者或创建者的用户 ID 匹配并且(在执行 SHM_LOCK 操作的情况下)进程具备足够高的 RLIMIT_MEMLOCK 资源限制
作为给内存加锁的一种替代方法,可以使用 mlock()
buf:
是一个指针,包含共享内存模式和访问权限的结构。
buf 参数是 IPC_STAT 和 IPC_SET 操作会用到的,并且在执行其他操作时需要将这个参数的值指定为 NULL
共享内存关联数据结构
每个共享内存段都有一个关联的 shmid_ds 数据结构,其形式如下。
struct shmid_ds
{
struct ipc_perm shm_perm; /* operation permission struct */
size_t shm_segsz; /* size of segment in bytes */
__time_t shm_atime; /* time of last shmat() */
#ifndef __x86_64__
unsigned long int __unused1;
#endif
__time_t shm_dtime; /* time of last shmdt() */
#ifndef __x86_64__
unsigned long int __unused2;
#endif
__time_t shm_ctime; /* time of last change by shmctl() */
#ifndef __x86_64__
unsigned long int __unused3;
#endif
__pid_t shm_cpid; /* pid of creator */
__pid_t shm_lpid; /* pid of last shmop */
shmatt_t shm_nattch; /* number of current attaches */
__syscall_ulong_t __unused4;
__syscall_ulong_t __unused5;
};SUSv3 要求实现提供上面给出的所有字段。其他一些 UNIX 实现在 shmid_ds 结构中包含了额外的非标准字段。
各种共享内存系统调用会隐式地更新 shmid_ds 结构中的字段,使用 shmctl() IPC_SET 操作可以显式地更新 shm_perm 字段中的特定子字段
共享内存的限制
大多数 UNIX 实现会对 System V 共享内存施加各种各样的限制。下面是一份 Linux 共享内存的限制列表。括号中列出了当限制达到时受影响的系统调用及其返回的错误。
SHMMNI:这是一个系统级别的限制,它限制了所能创建的共享内存标识符(换句话说是共享内存段)的数量。(shmget(), ENOSPC)
SHMMIN :这个一个共享内存段的最小大小(字节数)。这个限制的值被定义成了 1(无法修改这个值),但实际的限制是系统分页大小(shmget(), EINVAL)。
SHMMAX :这个是一个共享内存段的最大大小(字节数)。SHMMAX 的实际上限依赖于可用的 RAM和交换空间。(shmget(), EINVAL)
SHMALL
这是一个系统级别的限制,它限制了共享内存中的分页总数。其他大多数 UNIX 实现并没有提供这个限制
SHMALL 的实际上限依赖于可用的 RAM 和交换空间。(shmget(), ENOSPC)
其他一些 UNIX 实现还施加了下列限制(Linux 并没有实现这些限制)。
SHMSEG :这个是进程级别的限制,它限制了所能附加的共享内存段数量。
实验
shmget怎么使用?
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>
#include <string.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <mqueue.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
static void usageError(const char *progName, const char *msg)
{
if (msg != NULL)
fprintf(stderr, "%s", msg);
fprintf(stderr, "Usage: %s [-cx] {-f pathname | -k key | -p} "
"seg-size [octal-perms]\n", progName);
fprintf(stderr, " -c Use IPC_CREAT flag\n");
fprintf(stderr, " -x Use IPC_EXCL flag\n");
fprintf(stderr, " -f pathname Generate key using ftok()\n");
fprintf(stderr, " -k key Use 'key' as key\n");
fprintf(stderr, " -p Use IPC_PRIVATE key\n");
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
/* Parse command-line options and arguments */
int numKeyFlags = 0; /* Counts -f, -k, and -p options */
int flags = 0;
long lkey;
key_t key;
int opt; /* Option character from getopt() */
while ((opt = getopt(argc, argv, "cf:k:px")) != -1) {
switch (opt) {
case 'c':
flags |= IPC_CREAT;
break;
case 'f': /* -f pathname */
key = ftok(optarg, 1);
if (key == -1){
printf("ftok");
exit(EXIT_FAILURE);
}
numKeyFlags++;
break;
case 'k': /* -k key (octal, decimal or hexadecimal) */
if (sscanf(optarg, "%li", &lkey) != 1){
printf("-k option requires a numeric argument\n");
exit(EXIT_FAILURE);
}
key = lkey;
numKeyFlags++;
break;
case 'p':
key = IPC_PRIVATE;
numKeyFlags++;
break;
case 'x':
flags |= IPC_EXCL;
break;
default:
usageError(argv[0], NULL);
}
}
if (numKeyFlags != 1)
usageError(argv[0], "Exactly one of the options -f, -k, "
"or -p must be supplied\n");
if (optind >= argc)
usageError(argv[0], "Size of segment must be specified\n");
int segSize = atoi(argv[optind]);
unsigned int perms = (argc <= optind + 1) ? (S_IRUSR | S_IWUSR) :
atoi(argv[optind + 1]);
int shmid = shmget(key, segSize, flags | perms);
if (shmid == -1){
perror("shmget");
exit(EXIT_FAILURE);
}
printf("%d\n", shmid); /* On success, display shared mem. id */
exit(EXIT_SUCCESS);
}shmat
// svshm_attach.c
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>
#include <string.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <mqueue.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
static void
usageError(char *progName)
{
fprintf(stderr, "Usage: %s [shmid:address[rR]]...\n", progName);
fprintf(stderr, " r=SHM_RND; R=SHM_RDONLY\n");
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
printf("SHMLBA = %ld (%#lx), PID = %ld\n",
(long) SHMLBA, (unsigned long) SHMLBA, (long) getpid());
for (int j = 1; j < argc; j++) {
char *p;
int shmid = strtol(argv[j], &p, 0);
if (*p != ':')
usageError(argv[0]);
void *addr = (void *) strtol(p + 1, NULL, 0);
int flags = (strchr(p + 1, 'r') != NULL) ? SHM_RND : 0;
if (strchr(p + 1, 'R') != NULL)
flags |= SHM_RDONLY;
char *retAddr = (char *)shmat(shmid, addr, flags);
if (retAddr == (void *) -1){
printf("shmat: %s", argv[j]);
exit(EXIT_FAILURE);
}
printf("%d: %s ==> %p\n", j, argv[j], retAddr);
}
printf("Sleeping 5 seconds\n");
sleep(5);
exit(EXIT_SUCCESS);
}
shmctl
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>
#include <string.h>
#include <semaphore.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <mqueue.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <time.h>
static void
printShmDS(const struct shmid_ds *ds)
{
printf("Size: %ld\n", (long) ds->shm_segsz);
printf("# of attached processes: %ld\n", (long) ds->shm_nattch);
printf("Mode: %lo",
(unsigned long) ds->shm_perm.mode);
#ifdef SHM_DEST
printf("%s", (ds->shm_perm.mode & SHM_DEST) ? " [DEST]" : "");
#endif
#ifdef SHM_LOCKED
printf("%s", (ds->shm_perm.mode & SHM_LOCKED) ? " [LOCKED]" : "");
#endif
printf("\n");
printf("Last shmat(): %s", ctime(&ds->shm_atime));
printf("Last shmdt(): %s", ctime(&ds->shm_dtime));
printf("Last change: %s", ctime(&ds->shm_ctime));
printf("Creator PID: %ld\n", (long) ds->shm_cpid);
printf("PID of last attach/detach: %ld\n", (long) ds->shm_lpid);
}
int main(int argc, char *argv[])
{
if (argc != 2 || strcmp(argv[1], "--help") == 0){
printf("%s shmid\n", argv[0]);
exit(EXIT_FAILURE);
}
struct shmid_ds ds;
if (shmctl(atoi(argv[1]), IPC_STAT, &ds) == -1){
printf("shmctl");
exit(EXIT_FAILURE);
}
printShmDS(&ds);
exit(EXIT_SUCCESS);
}// svshm_info.c
#define _GNU_SOURCE
#include <sys/shm.h>
int
main(int argc, char *argv[])
{
struct shm_info info;
int s = shmctl(0, SHM_INFO, (struct shmid_ds *) &info);
if (s == -1){
perror("shmctl");
exit(EXIT_FAILURE);
}
printf("Maximum ID index = %d\n", s);
printf("shm_tot = %ld\n", (long) info.shm_tot);
printf("shm_rss = %ld\n", (long) info.shm_rss);
printf("shm_swp = %ld\n", (long) info.shm_swp);
printf("swap_attempts = %ld\n", (long) info.swap_attempts);
printf("swap_successes = %ld\n", (long) info.swap_successes);
exit(EXIT_SUCCESS);
} // svshm_lock
int
main(int argc, char *argv[])
{
for (int j = 1; j < argc; j++)
if (shmctl(atoi(argv[j]), SHM_LOCK, NULL) == -1){
perror("shmctl");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}// svshm_rm.c
int
main(int argc, char *argv[])
{
for (int j = 1; j < argc; j++)
if (shmctl(atoi(argv[j]), IPC_RMID, NULL) == -1){
perror("shmctl");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
// svshm_unlock 解锁
int
main(int argc, char *argv[])
{
for (int j = 1; j < argc; j++)
if (shmctl(atoi(argv[j]), SHM_UNLOCK, NULL) == -1){
perror("shmctl");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}两进程间交换数据
用共享内存来两进程间交换数据,比如交换一个结构体
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "memory.h"
typedef struct Stu{
int age;
char name[10];
}Stu;
int main(int argc,char *argv[]){
Stu s;
strcpy(s.name, "jack");
//创建共享内存段
int id = shmget(1234, 8, IPC_CREAT | 0644);
if (id == -1){
perror("shmget");
exit(1);
}
//挂载到进程的地址空间
Stu *p = (Stu *)shmat(id, nullptr, 0);
int i = 0;
while (1){
s.age = i++;
memcpy(p, &s, sizeof(Stu)); //写到共享段中
sleep(2);
}
return 0;
} #include <iostream>
#include <string>
#include "unistd.h"
#include <sys/ipc.h>
#include <sys/shm.h>
typedef struct Stu{
int age;
char name[10];
}Stu;
int main() {
int id = shmget(1234, 8, 0);
if (id == -1){
perror("shmget");
exit(1);
}
//挂载到进程的地址空间
Stu *p = (Stu *)shmat(id, nullptr, 0);
while (1){
std::cout << "age = " << p->age << ", name = " << p->name << "\n";
sleep(2);
}
return 0;
}如果进程中的数据改变了,另一个进程只要主动去读了会马上知道。
总结
共享内存允许两个或者多个进程共享内存的同一个分页。通过共享内存交换数据无需内核干涉。一旦一个进程将数据复制进一个共享内存段之后,数据将会立即对其他进程可见。
共享内存是一种快速的 IPC 机制,尽管这种速度上的提升通常会因必须要使用某种同步技术而被抵消掉一部分,如使用一个 System V 信号量来同步对共享内存的访问。
在附加一个共享内存段时推荐的做法是允许内核选择将段附加在进程的虚拟地址空间的何处。这意味着段在不同进程中虚拟地址可能是不同的。正因为这个原因,所有对段中地址的引用都应该表示成为相对偏移量,而不是一个绝对指针
通用数据结构
//总控结构的数据结构
struct ipc_ids {
int size; /*队列数组的容量
int in_use; /*使用中的队列数
int max_id; /*队列数组的最大不为空下标
unsigned short seq; /*序列号
unsigned short seq_max; /*最大序列号
struct semaphore sem; /*用于控制进程间互斥的信号量
spinlock_t ary; /*用于防止多个CPU同时操作的锁
struct ipc_id* entries; /*队列数组的入口
};
//信号量的数据结构
struct semaphore {
atomic_t count; //
int sleepers; //在该信号量上面睡眠的进程数
wait_queue_head_t wait; //等待的队列
#if WAITQUEUE_DEBUG
long __magic; //模数
#endif
};
struct ipc_id {
struct kern_ipc_perm* p;
};
//权限控制信息数据结构
struct kern_ipc_perm
{
key_t key; //关键字
//对象拥有者对应进程的有效用户识别号和有效组识别号
uid_t uid;
gid_t gid;
//对象创建者对应进程的有效用户识别号和有效组识别号
uid_t cuid;
gid_t cgid;
mode_t mode; //存取模式
unsigned long seq; // 序列号
};
通用函数
1.初始化
start_kernel(void)
ipc_init (void)
sem_init();
msg_init();
shm_init();
ipc_init_ids(struct ipc_ids* ids, int size)
2.创建/取得队列 ——返回队列标识号
newque (key_t key, int msgflg)
newary (key_t key, int nsems, int semflg)
shm_addid(struct shmid_kernel *shp)
ipc_addid(struct ipc_ids* ids, struct kern_ipc_perm* new, int size) //队列加入总控结构
grow_ary(struct ipc_ids* ids, int newsize) //增加总控结构的队列数
初始化三种队列中其他成员
int ipc_buildid(struct ipc_ids* ids, int id, int seq) //生成队列标识号
增加一个队列时 ipc_ids.seq++ 并且所增加的队列的kern_ipc_perm.seq = ipc_ids.seq
但在删除该队列时 ipc_ids.seq 并不减小
这就保证了SEQ_MULTIPLIER*seq + id 的唯一性
当消息调用者引用一个队列之后,原先处于那个下标的队列可能已经别删除了,而且在同一个下标出创建了一个新的队列,但旧队列和新队列的标号不同,这样避免了错误操作队列的可能性。
3.查找队列
long sys_msgget (key_t key, int msgflg)
long sys_semget (key_t key, int nsems, int semflg)
long sys_shmget (key_t key, size_t size, int shmflg)
ipc_findkey(struct ipc_ids* ids, key_t key)
//根据系统调用参数传递过来的key来查找队列
kern_ipc_perm* ipc_get(struct ipc_ids* ids, int id) //根据队列标识号返回队列的下标
(只在共享内存中使用)
kern_ipc_perm* ipc_lock(struct ipc_ids* ids, int id)
//根据队列标识号返回队列的的kern_ipc_perm结构指针
int ipc_checkid(struct ipc_ids* ids, struct kern_ipc_perm* ipcp, int uid)
//根据队列标识号检查队列的序号
4.队列访问控制
kern_ipc_perm* ipc_lock(struct ipc_ids* ids, int id) //根据队列标识号返回队列的下标,
并对总控结构上锁
spin_lock(&ids->ary); //上锁
extern inline void ipc_unlock(struct ipc_ids* ids, int id)
{
spin_unlock(&ids->ary);
}
//在对三种队列的操作之前,都会对取得的队列进行访问权限检查。
ipcperms (struct kern_ipc_perm *ipcp, short flag) //对进程的 IPC 访问权限进行检查
extern inline int ipc_checkid(struct ipc_ids* ids, struct kern_ipc_perm* ipcp, int uid)
{
if(uid/SEQ_MULTIPLIER != ipcp->seq)
return 1;
return 0;
}
5.删除队列
kern_ipc_perm* ipc_rmid(struct ipc_ids* ids, int id)
//删除数组中下标为id % SEQ_MULTIPLIER的队列
代码部分:
void __init ipc_init_ids(struct ipc_ids* ids, int size)
{
int i;
sema_init(&ids->sem,1); //信号量初始化
if(size > IPCMNI)
size = IPCMNI;
ids->size = size;
ids->in_use = 0;
ids->max_id = -1;
ids->seq = 0;
{ //设置最大序列号
int seq_limit = INT_MAX/SEQ_MULTIPLIER;
/*#define IPCMNI 32768
/ * #define INT_MAX ((int)(~0U>>1)) ?
/ *#define SEQ_MULTIPLIER (IPCMNI)
if(seq_limit > USHRT_MAX)
ids->seq_max = USHRT_MAX;
/*#define USHRT_MAX 0xffff
else
ids->seq_max = seq_limit;
}
ids->entries = ipc_alloc(sizeof(struct ipc_id)*size);//分配空间
if(ids->entries == NULL) {
printk(KERN_ERR "ipc_init_ids() failed, ipc service disabled.\n");
ids->size = 0;
}
ids->ary = SPIN_LOCK_UNLOCKED;
for(i=0;i<ids->size;i++)
ids->entries[i].p = NULL;
} 返回
extern inline struct kern_ipc_perm* ipc_lock(struct ipc_ids* ids, int id)
{ //根据队列标识号返回队列的kern_ipc_perm结构指针
struct kern_ipc_perm* out;
int lid = id % SEQ_MULTIPLIER; // SEQ_MULTIPLIER=队列数组的最大维数32768
if(lid >= ids->size)
return NULL;
spin_lock(&ids->ary); //上锁
out = ids->entries[lid].p;
if(out==NULL)
spin_unlock(&ids->ary); //如果队列已经为空,则解锁
return out;
} 返回
int ipcperms (struct kern_ipc_perm *ipcp, short flag)
{ /* flag will most probably be 0 or S_...UGO from <linux/stat.h> */
int requested_mode, granted_mode;
requested_mode = (flag >> 6) | (flag >> 3) | flag;
granted_mode = ipcp->mode;
if (current->euid == ipcp->cuid || current->euid == ipcp->uid)
granted_mode >>= 6;
else if (in_group_p(ipcp->cgid) || in_group_p(ipcp->gid))
granted_mode >>= 3;
/* is there some bit set in requested_mode but not in granted_mode? */
if ((requested_mode & ~granted_mode & 0007) &&
!capable(CAP_IPC_OWNER))
return -1;
return 0;
} 返回
int ipc_addid(struct ipc_ids* ids, struct kern_ipc_perm* new, int size)
{ //找到一个空位置然后加入
int id;
size = grow_ary(ids,size); //按照新的标准扩大队列数组的容量
for (id = 0; id < size; id++) {
if(ids->entries[id].p == NULL) //找到一个空位置
goto found;
}
return -1;
found:
ids->in_use++;
if (id > ids->max_id)
ids->max_id = id;
new->cuid = new->uid = current->euid;
new->gid = new->cgid = current->egid;
new->seq = ids->seq++; //序号增加,直到为最大时,再重新从0开始
if(ids->seq > ids->seq_max)
ids->seq = 0;
spin_lock(&ids->ary); //联入新的队列之前先锁队列
ids->entries[id].p = new;
return id;
} 返回
static int grow_ary(struct ipc_ids* ids, int newsize)
{
struct ipc_id* new;
struct ipc_id* old;
int i;
if(newsize > IPCMNI)
newsize = IPCMNI;
if(newsize <= ids->size)
return newsize;
new = ipc_alloc(sizeof(struct ipc_id)*newsize);
if(new == NULL)
return ids->size;
memcpy(new, ids->entries, sizeof(struct ipc_id)*ids->size);
for(i=ids->size;i<newsize;i++) {
new[i].p = NULL;
}
spin_lock(&ids->ary);
old = ids->entries;
ids->entries = new;
i = ids->size;
ids->size = newsize;
spin_unlock(&ids->ary);
ipc_free(old, sizeof(struct ipc_id)*i);
return ids->size;
} 返回
extern inline int ipc_buildid(struct ipc_ids* ids, int id, int seq)
{
return SEQ_MULTIPLIER*seq + id;
} 返回
extern inline int ipc_checkid(struct ipc_ids* ids, struct kern_ipc_perm* ipcp, int uid)
{
if(uid/SEQ_MULTIPLIER != ipcp->seq)
return 1;
return 0;
} 返回
extern inline struct kern_ipc_perm* ipc_lock(struct ipc_ids* ids, int id)
{
struct kern_ipc_perm* out;
int lid = id % SEQ_MULTIPLIER;
if(lid >= ids->size)
return NULL;
spin_lock(&ids->ary);
out = ids->entries[lid].p;
if(out==NULL)
spin_unlock(&ids->ary);
return out;
} 返回
static inline void spin_lock(spinlock_t *lock)
{
#if SPINLOCK_DEBUG
__label__ here;
here:
if (lock->magic != SPINLOCK_MAGIC) {
printk("eip: %p\n", &&here);
BUG();
}
#endif
__asm__ __volatile__(
spin_lock_string
:"=m" (lock->lock) : : "memory"); //
} 返回
typedef struct {
volatile unsigned int lock;
#if SPINLOCK_DEBUG
unsigned magic;
#endif
} spinlock_t;
#define SPIN_LOCK_UNLOCKED (spinlock_t) { 1 SPINLOCK_MAGIC_INIT }
#define spin_lock_string \
"\n1:\t" \
"lock ; decb %0\n\t" \
"js 2f\n" \
".subsection 1\n" \
".ifndef _text_lock_" __stringify(KBUILD_BASENAME) "\n" \
"_text_lock_" __stringify(KBUILD_BASENAME) ":\n" \
".endif\n" \
"2:\t" \
"cmpb $0,%0\n\t" \
"rep;nop\n\t" \
"jle 2b\n\t" \
"jmp 1b\n" \
".subsection 0\n"
extern inline void ipc_unlock(struct ipc_ids* ids, int id)
{
spin_unlock(&ids->ary);
} 返回
struct kern_ipc_perm* ipc_rmid(struct ipc_ids* ids, int id)
{
struct kern_ipc_perm* p;
int lid = id % SEQ_MULTIPLIER; //id是计算出来的唯一标志,lid是数组下标
if(lid >= ids->size)
BUG();
p = ids->entries[lid].p;
ids->entries[lid].p = NULL;
if(p==NULL)
BUG();
ids->in_use--;
if (lid == ids->max_id) {
//如果删除的是下标最大的那个队列,则找到删除后下标最大且不为空的队列的下标跟新总控结构ipc_ids.max_id
do {
lid--;
if(lid == -1)
break;
} while (ids->entries[lid].p == NULL);
ids->max_id = lid;
}
return p;
} 返回 System V消息队列是传统的Linux消息队列机制,它使用一组系统调用来创建、发送和接收消息。它的特点是可以在不同进程之间共享消息队列,但是在使用时需要手动管理消息队列的创建和删除。
优点:
可以实现异步通信:发送进程将消息放入消息队列后即可继续执行,不需要等待接收进程的响应,接收进程可以在合适的时候去读取消息。
支持多对多通信:多个进程可以同时向同一个消息队列发送消息,多个进程也可以同时从同一个消息队列接收消息。
可以实现进程解耦:发送进程和接收进程之间通过消息队列通信,不需要直接的共享内存或者使用管道等方式,从而实现了进程解耦。
消息队列中的消息可以按照优先级进行处理,这样可以实现一些特殊的消息处理逻辑。
缺点:
消息队列是基于内核的,因此涉及到用户态和内核态的切换,可能会引入一定的性能开销。
消息队列的容量有限,当消息队列满了之后,发送进程将无法再发送消息,接收进程也无法再接收消息,可能会引发消息丢失的问题。
System V消息队列是Linux特有的IPC机制,因此在不同的操作系统上可能不具备可移植性。
多进程与多线程
使用有名管道实现双向通信时,由于读管道是阻塞读的,为了不让“读操作”阻塞“写操作”,使用了父子进程来多线操作,
1)父进程这条线:读管道1
2)子进程这条线:写管道2
线程和进程都是并发运行的,但是线程和进程各自的使用的场合有所不同
多线使用多线程更省计算机cpu和内存的开销
创建出并发运行的线程目的-----------多线操作
程序必须要去运行一个新程序时,此时必须涉及到多进程
这里并发运行的主要目的并不是为了多线操作,而是为了单独的去执行新程序
执行新程序时,我们只能使用多进程来操作,因为线程是不可能去执行一个新程序的
System V IPC
无名管道和有名管道都是UNIX系统早期提供的比较原始的一种进程间通信方式
后来Unix系统升级到第5版本时,又提供了三种新的IPC通信方式
消息队列
信号量
共享内存
System V就是系统第5版本的意思
后来的Linux也继承了unix的这三个通信方式
管道的机制
管道的本质就是一段缓存
Linux OS内核是以文件的形式来管理管道
我们都是使用文件描述符以文件的形式来操作:无名管道和有名管道
操作管道时:
除了pipe和mkfifo这两个函数外,其它的像read、write、open都是文件io函数
System V IPC
System V IPC与管道有所不同:
它完全使用了不同的实现机制,与文件没任何的关系
也就是说内核不再以文件的形式来管理System V IPC
对于System V IPC,OS内核提供了全新的API
System V IPC时,不存在亲缘进程一说,任何进程之间都可以使用System V IPC来通信
System V IPC标识符
这个“标识符”就是文件描述符的替代者,但是它是专门给System V IPC使用的
不能使用文件IO函数来操作“标识符”,只能使用System V IPC的特有API才能操作
如何得到ipc标识符
调用某API创建好某个“通信结构”以后,API就会返回一个唯一的“标识符”
比如创建好了一个“消息队列”后,创建的API就会返回一个唯一标识消息队列的“标识符”
ipc标识符作用
如果创建的是消息队列的话:
进程通过消息队列唯一的标识符,就能找到创建好的“消息队列”
使用这个消息队列,进程就能读写数据,从而实现进程间通信
可以读写数据就是实现了通信
也就是标识符就是可是识别传建好的system V IPC
消息队列
本质
消息队列的本质:由内核创建的用于存放消息的链表
由于是存放消息的,所以把这个链表称为了消息队列
如何存放消息
消息队列这个链表有很多的节点,链表上的每一个节点就是一个消息
注意是一个双向链表
每个消息由两部分组成
1)消息编号:识别消息用
2)消息正文:真正的信息内容
发送接收消息过程
1.发送消息
(a)进程先封装一个消息包
(b)调用相应的API发送消息
这个消息包其实就是如下类型的一个结构体变量:
----封包时将消息编号和消息正文写到结构体的成员中
struct msgbuf{
long mtype; /* 放消息编号,必须> 0 */
char mtext[msgsz]; /* 消息内容(消息正文) */
}; b过程:
1.调用API时通过“消息队列的标识符”找到对应的消息队列
2.将消息包发送给消息队列,消息包会被作为一个链表节点插入链表
2.接收消息
调用API接收消息时,必须传递两个重要的信息
消息队列标识符
你要接收消息的编号
有了这两个信息:
API就可以找到对应的消息队列,然后从消息队列中取出你所要编号的消息
收到了别人所发送的信息,实现了通信
“消息队列”有点像信息公告牌:
--------发送信息的人把某编号的消息挂到公告牌上
--------接收消息的人自己到公告牌上去取对应编号的消息
如此,发送者和接受者之间就实现了通信
----使用消息队列实现网状交叉通信很容易
----消息队列作为媒介,一个往双向链表发数据,一个根据编号取出数据
消息队列使用步骤
使用msgget函数
消息队列不存在:创建新的消息队列
消息队列存在:获取已存在的某个消息队列,并返回唯一标识消息队列的标识符(msqID)
后续收发消息就是使用msqID这个标识符来实现的
收发消息
发送消息:使用msgsnd函数,利用消息队列标识符发送某编号的消息
接收消息:使用msgrcv函数,利用消息队列标识符接收某编号的消息
使用msgctl函数,利用消息队列标识符删除消息队列
对于使用消息队列来通信的多个进程来说:
只需要一个进程来创建消息队列就可以了
对于其它要参与通信的进程来说:
直接使用这个创建好的消息队列即可
为了保证消息队列的创建,让每一个进程都包含创建消息队列的代码,谁先运行就由谁创建
后运行的进程如果发现它想用的那个消息队列已经创建好了,就直接使用
当众多进程共享操作同一个消息队列时,即可实现进程间的通信。
消息队列API
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//功能:利用key值创建、或者获取一个消息队列
/*
1.如果key没有对应任何消息队列,那就创建一个新的消息队列
2.如果key已经对应了某个消息队列,说明消息队列已经存在了,那就获取这个消息队列来使用
*/
//msgflg:指定创建时的原始权限,比如0664
int msgget(key_t key, int msgflg);key值--------用于为消息队列生成(计算出)唯一的消息队列ID
第一种:指定为IPC_PRIVATE宏
指定这个宏后,每次调用msgget时都会创建一个新的消息队列
第二种:可以自己指定一个整形数,但是容易重复指定
第三种:使用ftok函数来生成key
#include <sys/types.h>
#include <sys/ipc.h>
/*
ftok通过指定路径名和一个整形数,就可以计算并返回一个唯一对应的key值,
---------只要路径名和整形数不变,所对应的key值就唯一不变的
*/
//ftok只会使用整形数(proj_id)的低8位,因此我们往往会指定为一个ASCII码值
key_t ftok(const char *pathname, int proj_id);创建一个新的消息队列时,除了原始权限,还需要指定IPC_CREAT选项
msgid = msgget(key, 0664|IPC_CREAT);
创建一个新的消息队列,此时就会用到msgflg参数
多个进程如何共享同一个消息队列
创建进程
创建者使用"./file", 'a’生成一个key值
然后调用msgget创建了一个消息队列
key = ftok("./file", 'a');
msgid = msgget(key, 0664|IPC_CREAT);
当创建者得到msgid后,即可操作消息队列
实现共享
只要能拿到别人创建好的消息队列的ID,即可共享操作同一个消息队列,实现进程间通信
获取别人创建好的消息队列的ID,有两个方法:
a)创建者把ID保存到某文件,共享进程读出ID即可
这种情况下,共享进程根本不需要调用msgget函数来返回ID
b)调用msgget获取已在消息队列的ID
使用ftok函数,利用与创建者相同的“路径名”和8位整形数,生成相同的key值
调用msgget函数,利用key找到别人创建好的消息队列,返回ID
key = ftok("./file", 'a');
msgid = msgget(key, 0664|IPC_CREAT);
这种方法是最常用的方法,因为ftok所用到的“路径名”和“8位的整形数”比较好记忆
代码演示
创建一个消息队列
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MSG_FIFE "./msgfile"
int create_or_get_msgque(){
//创建消息队列,返回msgid
int msgId=-1;
key_t key=-1;
int fd=0;
//为了用到文件路径名,用open创建
fd=open(MSG_FIFE,O_RDWR|O_CREAT,0664);
//用生成的路径+asc码生成唯一整数
key=ftok(MSG_FIFE,'a');
msgId=msgget(key,0664|IPC_CREAT);
return msgId;
}
int main(void){
int msgID=-1;
int ret=-1;
msgID=create_or_get_msgque();
/*父子进程收发消息*/
ret=fork();
if(ret>0){//父进程发送消息
}
else if(ret==0){//子进程接收消息
}
return 0;
}
转载:https://blog.csdn.net/weixin_47173597/article/details/127970145?spm=1001.2101.3001.6650.14&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-14-127970145-blog-9269561.235%5Ev43%5Epc_blog_bottom_relevance_base6&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-14-127970145-blog-9269561.235%5Ev43%5Epc_blog_bottom_relevance_base6&utm_relevant_index=24
[ ] Linux dynamic and persistent device naming (userspace devfs) support
翻译:
说明:目前此项的作用仅是开启CONFIG_TMPFS和CONFIG_DEVTMPFS及其所依赖的选项.CONFIG_TMPFS是为了在"/dev
/shm","/run","/sys/fs/cgroup"三个目录中挂载tmpfs文件系统,CONFIG_DEVTMPFS是为了在"/dev"目
录挂载devtmpfs文件系统.建议选"Y"
[ ] Select options required by Portage features
翻译:
说明:自动选中各种Portage特性(FEATURES="cgroup ipc-sandbox network-sandbox")所依赖的内核选项.由于这些FEATURES即将变为默认开启,所以建议选"Y"
Support for init systems, system and service managers --->
翻译:
说明:"init"系统(系统与服务管理器)."init"是内核启动的第一个用户空间程序(PID=1),也是所有用户态进程的"大总管"([提示]所有内核态进程的大总管是PID=2的[kthreadd])
[ ] OpenRC, runit and other script based systems and managers
翻译:
说明:OpenRC是Gentoo传统的"init"系统,使用基于SysVinit的传统启动脚本技术.选"Y"后,所有OpenRC所必需的内核选项(目前仅有CONFIG_BINFMT_SCRIPT)都会被自动选中.不确定的选"Y"
[ ] systemd
翻译:
说明:尽管倍受争议,但是systemd确实是目前风头最劲的"init"系统,大有在Linux世界一统江湖的霸气和潜力.仅在你打算从OpenRC迁移到systemd时选"Y".选"Y"后,内核中所有被systemd依赖的选项都将被自动选中,包括systemd建议的(而非必须的)选项,总计约20项.systemd官方的README文件也列出了必须/推荐/可选的内核选项
[ ] Kernel Self Protection Project ----
翻译:内核自我保护项目
说明:内核自我保护项目(Kernel Self Protection Project,以下简称“KSPP”)是由来自Google的一名开发者Kees Cook于2016年推出的。项目一经推出,KSPP便作为向Linux内核提供多层安全性保障的一种方式,且在KSPP工作的许多领域中都有帮助缓解内存损失风险的保护措施。
[ ] Print firmware information that the kernel attempts to load
翻译:打印内核尝试加载的固件信息
说明:
< > KUnit - Enable support for unit tests ----
翻译:KUnit-启用对单元测试的支持
说明:
< > Notifier error injection
翻译:通知程序错误注入
说明:仅供内核开发者使用
< > PM notifier error injection module
翻译:PM通知程序错误注入模块
说明:
< > Netdev notifier error injection module
翻译:Netdev通知程序错误注入模块
说明:
[ ] Fault-injections of functions
翻译:功能的故障注入
说明:
[ ] Fault-injection framework
翻译:故障注入框架
说明:
[ ] Code coverage for fuzzing
翻译:模糊化的代码覆盖率
说明:
[ ] Runtime Testing --->
翻译:运行时测试
说明:
< > Dhrystone benchmark test
翻译:Dhrystone基准测试
说明:
< > Linux Kernel Dump Test Tool Module
翻译:Linux内核转储测试工具模块
说明:
< > Min heap test
翻译:最小堆测试
说明:
< > 64bit/32bit division and modulo test
翻译:64位/32位除法和模测试
说明:
< > Self test for the backtrace code
翻译:回溯代码的自检
说明:
< > Self test for reference tracker
翻译:参考跟踪器自检
说明:
< > Red-Black tree test
翻译:红黑树试验
说明:
< > Reed-Solomon library test
翻译:Reed-Solomon库测试
说明:
< > Interval tree test
翻译:区间树测试
说明:
< > Per cpu operations test
翻译:单cpu操作测试
说明:
< > Perform an atomic64_t self-test
翻译:执行原子64自检
说明:
< > Self test for hardware accelerated raid6 recovery
翻译:硬件加速raid6恢复的自检
说明:
< > Test functions located in the hexdump module at runtime
翻译:运行时测试位于hexdump模块中的函数
说明:
< > Test string functions at runtime
翻译:在运行时测试字符串函数
说明:
< > Test functions located in the string_helpers module at runtime
翻译:运行时测试位于string_helpers模块中的函数
说明:
< > Test kstrto () family of functions at runtime
翻译:在运行时测试kstrto()函数族
说明:
< > Test printf() family of functions at runtime
翻译:
说明:仅供内核开发者使用
< > Test scanf() family of functions at runtime
翻译:
说明:仅供内核开发者使用
< > Test bitmap_ () family of functions at runtime
翻译:
说明:仅供内核开发者使用
< > Test functions located in the uuid module at runtime
翻译:在运行时测试位于uuid模块中的函数
说明:仅供内核开发者使用
< > Test the XArray code at runtime
翻译:
说明:仅供内核开发者使用
< > Test the Maple Tree code at runtime or module load
翻译:
说明:仅供内核开发者使用
< > Perform selftest on resizable hash table
翻译:
说明:仅供内核开发者使用
< > Perform selftest on IDA functions
翻译:
说明:仅供内核开发者使用
< > Perform selftest on priority array manager
翻译:
说明:仅供内核开发者使用
< > Test module loading with 'hello world' module
翻译:
说明:仅供内核开发者使用
< > Test module for compilation of bitops operations
翻译:
说明:仅供内核开发者使用
< > Test module for stress/performance analysis of vmalloc allocator
翻译:
说明:仅供内核开发者使用
< > Test user/kernel boundary protections
翻译:
说明:仅供内核开发者使用
< > Test BPF filter functionality
翻译:
说明:仅供内核开发者使用
< > Test blackhole netdev functionality
翻译:
说明:仅供内核开发者使用
< > Test find_bit functions
翻译:
说明:仅供内核开发者使用
< > Test firmware loading via userspace interface
翻译:
说明:仅供内核开发者使用
< > sysctl test driver
翻译:
说明:仅供内核开发者使用
< > udelay test driver
翻译:
说明:仅供内核开发者使用
< > Test static keys
翻译:
说明:仅供内核开发者使用
< > Test DYNAMIC_DEBUG
翻译:
说明:仅供内核开发者使用
< > kmod stress tester
翻译:
说明:仅供内核开发者使用
< > Test memcat_p() helper function
翻译:
说明:仅供内核开发者使用
< > Test livepatching
翻译:
说明:仅供内核开发者使用
< > Perform selftest on object aggreration manager
翻译:
说明:仅供内核开发者使用
< > Test heap/page initialization
翻译:
说明:仅供内核开发者使用
< > Test HMM (Heterogeneous Memory Management)
翻译:
说明:仅供内核开发者使用
< > Test freeing pages
翻译:
说明:仅供内核开发者使用
< > Test floating point operations in kernel space
翻译:
说明:仅供内核开发者使用
< > Test clocksource watchdog in kernel space
翻译:
说明:仅供内核开发者使用
[ ] Memtest
翻译:
说明:
[ ] Enable verbose x86 bootup info messages
翻译:
说明:在启动时显示额外bzimage解压消息,显示详细的内核引导信息.建议选"N"使引导过程更安静(依然会显示错误信息)
- - Early printk
翻译:
说明:将内核日志直接输出到VGA缓冲或串口.这有助于调试那些在控制台尚未完成初始化之前就造成系统崩溃的bug
[ ] Early printk via EHCI debug port
翻译:
说明:支持将内核日志直接通过EHCI调试端口输出.选"N",除非你想调试内核
[ ] Early printk via the xHCI debug port
翻译:
说明:支持将内核日志直接通过xHCI调试端口输出.选"N",除非你想调试内核
[ ] Dump the EFI pagetable
翻译:
说明:仅供内核开发者使用
[ ] Set upper limit of TLB entries to flush one-by-one
翻译:
说明:仅供内核开发者使用
[ ] Enable IOMMU debugging
翻译:
说明:仅供内核开发者使用
[ ] x86 instruction decoder selftest
翻译:
说明:仅供内核开发者使用
IO delay type (port 0x80 based port-IO delay [recommended]) --->
翻译:
说明:传统的Linux IO延迟方式,久经考验,也是最安全的默认值
[ ] Debug boot parameters
翻译:
说明:仅供内核开发者使用
[ ] CPA self-test code
翻译:
说明:仅供内核开发者使用
[ ] Debug low-level entry code
翻译:调试低级入口代码
说明:
[ ] NMI Selftest
翻译:
说明:对不可屏蔽中断(Non Maskable Interrupt)进行自检,仅供内核开发者使用
[ ] Debug the x86 FPU code
翻译:调试x86 FPU代码
说明:
< > ATOM Punit debug driver
翻译:
说明:仅供内核开发者使用
Choose kernel unwinder (ORC unwinder) --->
翻译:选择内核解卷器
说明:
[ ] Boot-time Tracing support
翻译:启动时跟踪支持
说明:
- - Kernel Function Tracer
翻译:内核函数跟踪器
说明:
[ ] Kernel Function Graph Tracer
翻译:核函数图跟踪器
说明:
[ ] Kernel Function Graph Return Value Value
翻译:核函数图返回值
说明:
[ ] enable/disable function tracing dynamically
翻译:动态启用/禁用功能跟踪
说明:
[ ] Kernel Function Probe (fprobe)
翻译:内核函数探针(fprobe)
说明:
[ ] Kernel function profiler
翻译:内核函数探查器
说明:
[ ] Trace max stack
翻译:跟踪最大堆栈
说明:
[ ] Interrupts-off Latency Tracer
翻译:中断关闭延迟跟踪器
说明:
[ ] Preemption-off Latency Tracer
翻译:抢占关闭延迟跟踪器
说明:
[ ] Scheduling Latency Tracer
翻译:计划延迟跟踪器
说明:
[ ] Tracer to detect hardware latencies (like SMIs)
翻译:用于检测硬件延迟的跟踪器(如SMI)
说明:
[ ] OS Noise tracer
翻译:OS噪声跟踪器
说明:
[ ] Timerlat tracer
翻译:Timerlat跟踪器
说明:
[ ] Memory mapped IO tracing
翻译:内存映射IO跟踪
说明:
[ ] Trace syscalls
翻译:跟踪系统调用
说明:
- - Create a snapshot trace buffer
翻译:创建快照跟踪缓冲区
说明:
[ ] Allow snapshot to swap per CPU
翻译:允许每个CPU交换快照
说明:
Branch Profiling (No branch profiling) --->
翻译:分支分析(无分支分析)
说明:
[ ] Support for tracing block IO actions
翻译:支持跟踪块IO操作
说明:
[ ] Enable kprobes-based dynamic events
翻译:启用基于kprobes的动态事件
说明:
[ ] Do NOT protect notrace function from kprobe events
翻译:不要保护notrace功能不受kprobe事件的影响
说明:
[ ] Enable uprobes-based dynamic events
翻译:启用基于uprobes的动态事件
说明:
[ ] Enable BPF programs to override a kprobed function
翻译:启用BPF程序以覆盖kprobed函数
说明:
- - Synthetic trace events
翻译:合成跟踪事件
说明:
[ ] User trace events
翻译:用户跟踪事件
说明:
[ ] Histogram triggers
翻译:直方图触发器
说明:
[ ] Trace event injection
翻译:跟踪事件注入
说明:
[ ] Add tracepoint that benchmarks tracepoints
翻译:添加对跟踪点进行基准测试的跟踪点
说明:
< > Ring buffer benchmark stress tester
翻译:环形缓冲基准应力测试仪
说明:
[ ] Show eval mappings for trace events
翻译:显示跟踪事件的评估映射
说明:
[ ] Record functions that recurse in function tracing
翻译:记录在函数跟踪中递归的函数
说明:
[ ] Perform a startup test on ftrace
翻译:在ftrace上执行启动测试
说明:
[ ] Verify compile time sorting of ftrace functions
翻译:验证ftrace函数的编译时排序
说明:
[ ] Ring buffer startup self test
翻译:环形缓冲器启动自检
说明:
[ ] Verify ring buffer time stamp deltas
翻译:验证环形缓冲区时间戳增量
说明:
< > Test module for mmiotrace
翻译:mmiotrace的测试模块
说明:
< > Test module to create a preempt / IRQ disable delay thread to test latency tracers
翻译:测试模块,用于创建抢占/IRQ禁用延迟线程以测试延迟跟踪器
说明:
< > Test module for in-kernel synthetic event generation
翻译:内核内合成事件生成的测试模块
说明:
< > Test module for in-kernel kprobe event generation
翻译:内核内kprobe事件生成的测试模块
说明:
[ ] Hist trigger debug support
翻译:历史触发器调试支持
说明:
[ ] Runtime Verification ----
翻译:运行时验证
说明:
< > performance tests for RCU
翻译:RCU的性能测试
说明:
< > torture tests for RCU
翻译:RCU的酷刑测试
说明:
< > Scalability tests for read-side synchronization (RCU and others)
翻译:读端同步的可扩展性测试(RCU和其他)
说明:
(60) RCU CPU stall timeout in seconds
翻译:RCU CPU暂停超时(秒)
说明:
(0) Expedited RCU CPU stall timeout in milliseconds
翻译:加速RCU CPU暂停超时(以毫秒为单位)
说明:
[ ] Provide additional RCU stall debug information
翻译:提供额外的RCU安装调试信息
说明:
[ ] Enable tracing for RCU
翻译:启用RCU跟踪
说明:
[ ] Provide debugging asserts for adding NO_HZ support to an arch
翻译:提供调试断言,用于向拱门添加NO_HZ支持
说明:
[ ] Lock debugging: prove locking correctness
翻译:锁调试:证明锁的正确性
说明:仅供内核开发者使用
[ ] Lock usage statistics
翻译:锁定使用情况统计信息
说明:仅供内核开发者使用
[ ] RT Mutex debugging, deadlock detection
翻译:RT互斥调试、死锁检测
说明:仅供内核开发者使用
[ ] Spinlock and rw-lock debugging: basic checks
翻译:Spinlock和rw-lock调试:基本检查
说明:仅供内核开发者使用
[ ] Mutex debugging: basic checks
翻译:互斥调试:基本检查
说明:仅供内核开发者使用
[ ] Wait/wound mutex debugging: Slowpath testing
翻译:等待/缠绕互斥体调试:Slowpath测试
说明:仅供内核开发者使用
[ ] RW Semaphore debugging: basic checks
翻译:RW信号灯调试:基本检查
说明:仅供内核开发者使用
[ ] Lock debugging: detect incorrect freeing of live locks
翻译:锁调试:检测活动锁的错误释放
说明:仅供内核开发者使用
[ ] Sleep inside atomic section checking
翻译:在原子部分检查内睡眠
说明:仅供内核开发者使用
[ ] Locking API boot-time self-tests
翻译:锁定API启动时自检
说明:在内核启动时运行一个简短的加锁/解锁函数(spinlocks,rwlocks,mutexes,rwsems)自我测试.仅供内核开发者使用
< > torture tests for locking
翻译:上锁的酷刑测试
说明:仅供内核开发者使用
< > Wait/wound mutex selftests
翻译:等待/触发互斥体自检
说明:仅供内核开发者使用
< > torture tests for smp_call_function*()
翻译:smp_call_function的酷刑测试*()
说明:
[ ] Debugging for csd_lock_wait(), called from smp_call_function*()
翻译:调试csd_lock_wait(),从smp_call_function*()调用
说明:
[ ] Panic on Oops
翻译:
说明:当内核oops时,直接panic掉(相当于Windows蓝屏死机),这样可以确保内核停止工作,避免导致无法预料的后果.等价于使用"oops=panic"内核引导参数.不确定的选"Y"
(0) panic timeout
翻译:
说明:如何处理内核崩溃(panic):(1)若设为"0"则表示无限等待,不做任何处理;(2)若设为正整数则表示等待设定的秒数之后重启;(3)若设为负整数则表示立即重启
- - Detect Soft Lockups
翻译:检测软锁定
说明:
[ ] Panic (Reboot) On Soft Lockups
翻译:软锁定时的恐慌(重新启动)
说明:
[ ] Detect Hard Lockups
翻译:检测硬锁定
说明:
[ ] Prefer the buddy CPU hardlockup detector
翻译:首选伙伴CPU硬锁定检测器
说明:
[ ] Panic (Reboot) On Hard Lockups
翻译:硬锁定时的恐慌(重新启动)
说明:
[ ] Detect Hung Tasks
翻译:检测挂起的任务
说明:
(120) Default timeout for hung task detection (in seconds)
翻译:挂起任务检测的默认超时(秒)
说明:
[ ] Panic (Reboot) On Hung Tasks
翻译:挂起的任务引发恐慌(重新启动)
说明:
[ ] Detect Workqueue Stalls
翻译:检测工作队列暂停
说明:
[ ] Report per-cpu work items which hog CPU for too long
翻译:报告占用cpu时间过长的每个cpu工作项
说明:
< > Test module to generate lockups
翻译:生成锁定的测试模块
说明:
[ ] Extend memmap on extra space for more information on page
翻译:在额外空间上扩展memmap以获取页面上的更多信息
说明:
[ ] Debug page memory allocations
翻译:调试页面内存分配
说明:
- - Enable SLUB debugging support
翻译:启用SLUB调试支持
说明:开启SLUB内存分配器调试功能.
[ ] SLUB debugging on by default
翻译:默认情况下在上调试SLUB
说明:默认开启SLUB内存分配器调试功能.仅供调试,切勿用于生产环境
[ ] Track page owner
翻译:跟踪页面所有者
说明:
[ ] Check for invalid mappings in user page tables
翻译:检查用户页表中的无效映射
说明:
[ ] Poison pages after freeing
翻译:释放后的毒页
说明:
[ ] Enable tracepoint to track down page reference manipulation
翻译:启用跟踪点以跟踪页面引用操作
说明:
[ ] Testcase for the marking rodata read-only
翻译:将rodata标记为只读的测试用例
说明:
[ ] Warn on W+X mappings at boot
翻译:启动时对W+X映射发出警告
说明:
[ ] Export kernel pagetable layout to userspace via debugfs
翻译:通过debugfs将内核页面表布局导出到用户空间
说明:仅供内核开发者使用
[ ] Kernel memory leak detector
翻译:内核内存泄漏检测器
说明:仅供内核开发者使用
[ ] Statistics for per-vma locks
翻译:每个vma锁的统计信息
说明:
[ ] Debug object operations
翻译:调试对象操作
说明:
[ ] Enable shrinker debugging support
翻译:启用收缩程序调试支持
说明:
[ ] Stack utilization instrumentation
翻译:堆栈利用率检测
说明:
[ ] Detect stack corruption on calls to schedule()
翻译:在调用schedule()时检测堆栈损坏
说明:
[ ] Debug VM
翻译:调试VM
说明:
[ ] Debug VM maple trees
翻译:调试VM枫树
说明:
[ ] Debug VM red-black trees
翻译:调试VM红黑树
说明:
[ ] Debug page-flags operations
翻译:调试页面标志操作
说明:
[ ] Debug arch page table for semantics compliance
翻译:调试拱形页表以符合语义
说明:
[ ] Debug VM translations
翻译:调试VM翻译
说明:仅供内核开发者使用
< > Memory hotplug notifier error injection module
翻译:内存热插拔通知程序错误注入模块
说明:
[ ] Debug access to per_cpu maps
翻译:调试对per_cpu映射的访问
说明:
[ ] KASAN: dynamic memory safety error detector ----
翻译:KASAN:动态内存安全错误检测器
说明:
[ ] KFENCE: low-overhead sampling-based memory safety error detector ----
翻译:KFENCE:基于低开销采样的内存安全错误检测器
说明:
- - Magic SysRq key
翻译:
说明:开启"魔法键"(SysRq,允许用户按下Alt+PrintScreen后发送给内核特殊的命令)支持(可以通过"echo 0 >
/proc/sys/kernel/sysrq"关闭).由于SysRq会带来安全隐患(允许未经登录的操作),所以你应该仅在确实需要的场合开启.更多
详情参见"Documentation/sysrq.txt"文档
(0x1) Enable magic SysRq key functions by default
翻译:
说明:设置默认开启哪些魔法键.设为"1"表示开启所有魔法键,设为"0"表示禁用所有魔法键.或者按照"Documentation/sysrq.txt"文档的指引设置特定的码位
[ ] Enable magic SysRq key over serial
翻译:通过串行启用魔术SysRq密钥
说明:
() Char sequence that enables magic SysRq over serial
翻译:字符序列,使魔术SysRq通过串行
说明:
- - Debug Filesystem
翻译:
说明:
Debugfs default access (Access normal) --->
翻译:调试默认访问(正常访问)
说明:
[ ] KGDB: kernel debugger --->
翻译:
说明:仅供内核开发者使用
[ ] KGDB: use kprobe blocklist to prohibit unsafe breakpoints
翻译:KGDB:使用kprobe阻止列表来禁止不安全的断点
说明:
< > KGDB: use kgdb over the serial console
翻译:KGDB:在串行控制台上使用KGDB
说明:
[ ] KGDB: internal test suite
翻译:KGDB:内部测试套件
说明:
[ ] KGDB: Run tests on boot
翻译:KGDB:启动时运行测试
说明:
[ ] KGDB: Allow debugging with traps in notifiers
翻译:KGDB:允许使用通知程序中的陷阱进行调试
说明:
[ ] KGDB_KDB: include kdb frontend for kgdb
翻译:KGDB_KDB:包括KGDB的KDB前端
说明:
[ ] Undefined behaviour sanity checker ----
翻译:未定义的行为健全性检查器
说明:
[ ] KCSAN: dynamic data race detector ----
翻译:KCSAN:动态数据竞赛检测器
说明:
Debug information (Disable debug information) --->
翻译:调试信息(禁用调试信息)
说明:
(2048) Warn for stack frames larger than
翻译:
说明:堆栈帧大小警告阈值,设置过小会导致编译时警告太多,设为"0"可以关闭警告,需要GCC-4.4或更高版本
[ ] Strip assembler-generated symbols during link
翻译:
说明:连接时剥离汇编器产生的内部符号(类似'.Lxxx'),这样可以净化get_wchan()之类的输出,同时还可以减小内核尺寸.建议开启
[ ] Generate readable assembler code
翻译:
说明:生成人类易读的汇编输出,以方便内核调试.这会禁用一些编译优化措施,也会降低内核的运行速度
[ ] Install uapi headers to usr/include
翻译:将uapi标头安装到usr/include
说明:
[ ] Enable full Section mismatch analysis
翻译:
说明:在编译时检查无效的引用.仅供内核开发者使用
[ ] Make section mismatch errors non-fatal
翻译:
说明:若选"N",那么一旦出现"section mismatch",将会直接导致编译失败(而不是仅仅抛出警告).建议选"N"
[ ] Force weak per-cpu definitions
翻译:
说明:仅供内核开发者使用,Alpha和s390处理器需要配置下一个特性(Force weak per-cpu definitions)。这个特性修复了一个在这类处理器中普遍存在的寻址问题。其他的处理器无需启用这个特性。
[ ] Show timing information on printks
翻译:
说明:在控制台和syslog()系统调用的输出中包含printk()消息的时间戳,以便于直接显示内核启动过程中各步骤所用的时间.注意:无论此项是否开启,时间戳总会被记录在/dev/kmsg中,开启此项仅相当于使用"printk.time=1"内核引导参数
[ ] Show caller information on printks
翻译:
说明:也可以打印出线程id,但是我们的解决方案打印的更加全面,不仅包括线程id,还可以包含线程名,cpuid和优先级prio,也许也可以考虑把这个patch推到社区让更多人受益
[ ] Show build ID information in stacktraces
翻译:在堆栈中显示生成ID信息
说明:
(7) Default console loglevel (1-15)
翻译:
说明:
(3) quiet console loglevel (1-15)
翻译:
说明:
(4) Default message log level (1-7)
翻译:
说明:
[ ] Delay each boot printk message by N milliseconds
翻译:将每个引导打印k消息延迟N毫秒
说明:仅供内核开发者使用
[ ] Enable dynamic printk() support
翻译:启用动态打印()支持
说明:仅供内核开发者使用
- - Enable core function of dynamic debug support
翻译:启用动态调试支持的核心功能
说明:
[ ] Support symbolic error names in printf
翻译:支持printf中的符号错误名称
说明: