上下文切换,有时也称为进程切换或任务切换,是CPU中央处理单元)从一个进程,或线程切换到另一个进程或线程。
进程,有时也称为任务,是程序的执行实例。在 Linux 中,线程是轻量级进程,可以并行运行,并与其父进程(即创建它们的进程)共享地址空间和其他资源。
上下文是指CPU寄存器和程序计数器在任何时间点的内容。寄存器是 CPU 内部的少量,非常快的内存,用于通过提供对常用值的快速访问,来加速计算机程序的执行,通常是计算的中间值。程序计数器是一种专用寄存器,用于指示 CPU 在其指令序列中的位置,并根据特定系统保存正在执行的指令的地址,或要执行的下一条指令的地址。
上下文切换可以更详细地描述为操作系统的核心对CPU上的进程(包括线程)执行以下活动:
上下文切换有时被描述为内核暂停 CPU 上一个进程的执行,并恢复先前暂停的某个其他进程的执行。
上下文切换只能在内核模式下发生。内核模式是CPU的一种特权模式,其中只有内核运行,并提供对所有内存位置和所有其他系统资源的访问。其他程序,包括应用程序,最初在用户模式下运行,但它们可以通过系统调用,运行部分内核代码。系统调用是类 Unix 操作系统中活动进程(即当前在 CPU 中运行的进程)对内核执行的服务(例如输入/输出 (I/O) 或进程创建)的请求(即,创建一个新进程)。 I/O 可以定义为进出 CPU 和主存储器(即 RAM)组合的任何信息移动,即该组合与计算机用户之间的通信(例如,通过键盘或鼠标),其存储设备(例如,磁盘或磁带驱动器)或其他计算机。
这两种模式在类 Unix 操作系统中的存在,意味着当系统调用导致 CPU 切换到内核模式时,类似但更简单的操作是必要的。这被称为模式切换,而不是上下文切换,因为它不会更改当前进程。
上下文切换是多任务操作系统的基本特征。多任务操作系统是多个进程在单个 CPU 上看似同时执行,且互不干扰的操作系统。这种并发错觉是通过快速连续(每秒数十或数百次)发生的上下文切换来实现的。这些上下文切换的发生是由于进程自愿放弃其在 CPU 中的时间,或调度程序在进程用完其CPU时间片时进行切换。
上下文切换也可以作为硬件中断的结果发生,硬件中断是从硬件设备(例如键盘、鼠标、调制解调器或系统时钟)到内核的信号,表明事件(例如,按键、鼠标移动)或来自网络连接的数据到达)发生。
Intel 80386 和更高版本的 CPU 包含对上下文切换的硬件支持。然而,大多数现代操作系统执行软件上下文切换,它可以在任何 CPU 上使用,而不是硬件上下文切换,以试图获得改进的性能。软件上下文切换首先在 Linux 中,为具有 2.4 内核的 Intel 兼容处理器实现。
软件上下文切换的一个主要优点是,虽然硬件机制保存了几乎所有的 CPU 状态,但软件可以更有选择性,只保存实际需要保存和重新加载的部分。然而,对于这在提高上下文切换效率方面究竟有多重要,存在一些问题。它的拥护者还声称,软件上下文切换允许改进切换代码的可能性,从而进一步提高效率,并允许更好地控制正在加载的数据的有效性。
在任何时候,系统中的每个 CPU 都可以:
这之间有一个排序。最底层的两个可以互相抢占,但在其之上是一个严格的等级制度:每个只能被它上面的人抢占。
当一个软中断在 CPU 上运行时,没有其他软中断会抢占它,但硬件中断可以。但是,系统中的任何其他 CPU 都是独立执行的。
我们将看到用户上下文可以通过多种方式阻止中断,从而成为真正不可抢占的。
计时器滴答声、网卡和键盘是可随时产生中断的真实硬件示例。内核运行中断处理程序,为硬件提供服务。内核保证此处理程序永远不会重新进入:如果相同的中断到达,则将其排队(或丢弃)。因为它禁用中断,所以这个处理程序必须很快:它经常简单地确认中断,标记一个“软件中断”以供执行并退出。
您可以判断您处于硬件中断状态,因为 in_irq() 返回 true。
任何标记为挂起(通常由硬件中断)的“软件中断”都会运行(kernel/softirq.c)。
许多真正的中断处理工作都在这里完成。在过渡到 SMP 的早期,只有“下半部分”(BH),它们没有利用多个CPU。
include/linux/interrupt.h 列出了不同的软中断。一个非常重要的软中断是,计时器软中断(包括/linux/timer.h)。
软中断通常很难处理,因为同一个软中断会在多个CPU上同时运行。出于这个原因,tasklet (include/linux/interrupt.h) 更常用:它们是可动态注册的(意味着您可以拥有任意数量的),并且它们还保证任何 tasklet 只能在一个 CPU 上运行任何时候,尽管不同的小任务可以同时运行。
Softirq | Index (priority) | Description |
---|---|---|
HI_SOFTIRQ | 0 | 处理高优先级的tasklet |
TIMER_SOFTIRQ | 1 | 与定时器中断相关的tasklet |
NET_TX_SOFTIRQ | 2 | 将数据包发送到网卡 |
NET_RX_SOFTIRQ | 3 | 从网卡接收数据包 |
SCSI_SOFTIRQ | 4 | SCSI 命令的中断后处理 |
TASKLET_SOFTIRQ | 5 | 处理常规tasklet |
void __init softirq_init(void)
{
int cpu;
for_each_possible_cpu(cpu) {
per_cpu(tasklet_vec, cpu).tail =
&per_cpu(tasklet_vec, cpu).head;
per_cpu(tasklet_hi_vec, cpu).tail =
&per_cpu(tasklet_hi_vec, cpu).head;
}
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
void add_timer(struct timer_list *timer)
void add_timer_on(struct timer_list *timer, int cpu)
int mod_timer(struct timer_list *timer, unsigned long expires)
int timer_reduce(struct timer_list *timer, unsigned long expires)
int del_timer_sync(struct timer_list *timer)
int try_to_del_timer_sync(struct timer_list *timer)
int del_timer(struct timer_list *timer)
这是PNX010x/PNX4008板上的I2C驱动器的计时器应用。
static void i2c_pnx_timeout(struct timer_list *t)
{
...
}
struct i2c_pnx_mif {
int ret; /* Return value */
int mode; /* Interface mode */
struct completion complete; /* I/O completion */
struct timer_list timer; /* Timeout */
u8 * buf; /* Data buffer */
int len; /* Length of data buffer */
int order; /* RX Bytes to order via TX */
};
struct i2c_pnx_algo_data {
void __iomem *ioaddr;
struct i2c_pnx_mif mif;
int last;
struct clk *clk;
struct i2c_adapter adapter;
int irq;
u32 timeout;
};
static int i2c_pnx_probe(struct platform_device *pdev)
{
struct i2c_pnx_algo_data *alg_data;
...
alg_data = devm_kzalloc(&pdev->dev, sizeof(*alg_data), GFP_KERNEL);
...
timer_setup(&alg_data->mif.timer, i2c_pnx_timeout, 0);
...
}
static inline void i2c_pnx_arm_timer(struct i2c_pnx_algo_data *alg_data)
{
struct timer_list *timer = &alg_data->mif.timer;
unsigned long expires = msecs_to_jiffies(alg_data->timeout);
if (expires <= 1)
expires = 2;
del_timer_sync(timer);
dev_dbg(&alg_data->adapter.dev, "Timer armed at %lu plus %lu jiffies.\n",
jiffies, expires);
timer->expires = jiffies + expires;
add_timer(timer);
}
在一个设备驱动程序中使用一个 tasklet,首先,应该分配一个新的 tasklet_struct 数据结构,而且通过调用 tasklet_init() ,初始化它;此函数的参数包括,tasklet描述符的地址,tasklet函数的地址,以及tasklet函数可选的整数参数。
通过调用 tasklet_disable_nosync() ,或 tasklet_disable(),选择性地禁用 tasklet。这两个函数都增加 tasklet 描述符的计数字段。但tasklet_disable()在tasklet函数已经运行的实例终止之前,不会返回。使用 tasklet_enable(),重新启用tasklet。
根据tasklet的优先级,调用 tasklet_schedule() ,或tasklet_hi_schedule() 函数激活 tasklet。这两个功能非常相似;他们每个人都执行以下操作行动:
atmel serial 代码
。
定义tasklet
struct atmel_uart_port {
...
struct tasklet_struct tasklet_rx;
struct tasklet_struct tasklet_tx;
...
};
tasklet函数
static void atmel_tasklet_rx_func(unsigned long data)
{
struct uart_port *port = (struct uart_port *)data;
struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);
/* The interrupt handler does not take the lock */
spin_lock(&port->lock);
atmel_port->schedule_rx(port);
spin_unlock(&port->lock);
}
static void atmel_tasklet_tx_func(unsigned long data)
{
struct uart_port *port = (struct uart_port *)data;
struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);
/* The interrupt handler does not take the lock */
spin_lock(&port->lock);
atmel_port->schedule_tx(port);
spin_unlock(&port->lock);
}
初始化
static int atmel_startup(struct uart_port *port)
{
...
tasklet_init(&atmel_port->tasklet_rx, atmel_tasklet_rx_func,
(unsigned long)port);
tasklet_init(&atmel_port->tasklet_tx, atmel_tasklet_tx_func,
(unsigned long)port);
...
}
规划运行
static void atmel_tasklet_schedule(struct atmel_uart_port *atmel_port,
struct tasklet_struct *t)
{
if (!atomic_read(&atmel_port->tasklet_shutdown))
tasklet_schedule(t);
}
static void atmel_rx_chars(struct uart_port *port)
{
...
atmel_tasklet_schedule(atmel_port, &atmel_port->tasklet_rx);
...
}
static void atmel_complete_tx_dma(void *arg)
{
...
if (!uart_circ_empty(xmit))
atmel_tasklet_schedule(atmel_port, &atmel_port->tasklet_tx);
...
}
终止tasklet
static void atmel_shutdown(struct uart_port *port)
{
...
/* Prevent spurious interrupts from scheduling the tasklet */
atomic_inc(&atmel_port->tasklet_shutdown);
...
/*
* Clear out any scheduled tasklets before
* we destroy the buffers
*/
tasklet_kill(&atmel_port->tasklet_rx);
tasklet_kill(&atmel_port->tasklet_tx);
...
}
如果软中断与用户上下文共享数据,则有两个问题。
这是使用 spin_lock_bh() (include/linux/spinlock.h) 的地方。它禁用该CPU上的软中断,然后获取锁。 spin_unlock_bh() 反之。
也可以在此处使用 spin_lock_irq() 或 spin_lock_irqsave(),它们也可以停止硬件中断。
这对UP也很有效:自旋锁消失了,这个宏简单地变成 local_bh_disable() (include/linux/interrupt.h),它保护你免受正在运行的软中断。
工作队列不是像tasklet那样提供一次性延迟方案,而是一种通用的延迟机制,其中工作队列的处理程序函数可以睡眠,在tasklet模型中不可能。工作队列的延迟可能比tasklet更高,但包含更丰富的工作延迟 API。
工作队列提供了一种将功能推迟到下半部分的通用方法。核心是工作队列(struct workqueue_struct),它是放置工作的结构。工作由 work_struct 结构表示,该结构标识要延迟的工作和要使用的延迟函数。
struct workqueue_struct {
struct list_head pwqs; /* WR: all pwqs of this wq */
struct list_head list; /* PR: list of all workqueues */
struct mutex mutex; /* protects this wq */
int work_color; /* WQ: current work color */
int flush_color; /* WQ: current flush color */
atomic_t nr_pwqs_to_flush; /* flush in progress */
struct wq_flusher *first_flusher; /* WQ: first flusher */
struct list_head flusher_queue; /* WQ: flush waiters */
struct list_head flusher_overflow; /* WQ: flush overflow list */
struct list_head maydays; /* MD: pwqs requesting rescue */
struct worker *rescuer; /* I: rescue worker */
int nr_drainers; /* WQ: drain in progress */
int saved_max_active; /* WQ: saved pwq max_active */
struct workqueue_attrs *unbound_attrs; /* PW: only for unbound wqs */
struct pool_workqueue *dfl_pwq; /* PW: only for unbound wqs */
#ifdef CONFIG_SYSFS
struct wq_device *wq_dev; /* I: for sysfs interface */
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
char name[WQ_NAME_LEN]; /* I: workqueue name */
/*
* Destruction of workqueue_struct is sched-RCU protected to allow
* walking the workqueues list without grabbing wq_pool_mutex.
* This is used to dump all workqueues from sysrq.
*/
struct rcu_head rcu;
/* hot fields used during command issue, aligned to cacheline */
unsigned int flags ____cacheline_aligned; /* WQ: WQ_* flags */
struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */
struct pool_workqueue __rcu *numa_pwq_tbl[]; /* PWR: unbound pwqs indexed by node */
};
struct pool_workqueue {
struct worker_pool *pool; /* I: the associated pool */
struct workqueue_struct *wq; /* I: the owning workqueue */
int work_color; /* L: current color */
int flush_color; /* L: flushing color */
int refcnt; /* L: reference count */
int nr_in_flight[WORK_NR_COLORS]; /* L: nr of in_flight works */
int nr_active; /* L: nr of active works */
int max_active; /* L: max active works */
struct list_head delayed_works; /* L: delayed works */
struct list_head pwqs_node; /* WR: node on wq->pwqs */
struct list_head mayday_node; /* MD: node on wq->maydays */
/*
* Release of unbound pwq is punted to system_wq. See put_pwq()
* and pwq_unbound_release_workfn() for details. pool_workqueue
* itself is also sched-RCU protected so that the first pwq can be
* determined without grabbing wq->mutex.
*/
struct work_struct unbound_release_work;
struct rcu_head rcu;
} __aligned(1 << WORK_STRUCT_FLAG_BITS);
alloc_workqueue(fmt, flags, max_active, args…)
INIT_WORK(_work, _func)
INIT_DELAYED_WORK(_work, _func)
static inline bool queue_work(struct workqueue_struct *wq, struct work_struct *work)
bool queue_work_on(int cpu, struct workqueue_struct *wq, struct work_struct *work)
bool cancel_work_sync(struct work_struct *work)
bool cancel_delayed_work(struct delayed_work *dwork)
sdio 驱动程序
结构定义
struct if_sdio_card {
...
struct workqueue_struct *workqueue;
struct work_struct packet_worker;
...
};
工作队列函数
static void if_sdio_host_to_card_worker(struct work_struct *work)
{
struct if_sdio_card *card;
struct if_sdio_packet *packet;
int ret;
unsigned long flags;
card = container_of(work, struct if_sdio_card, packet_worker);
while (1) {
spin_lock_irqsave(&card->lock, flags);
packet = card->packets;
if (packet)
card->packets = packet->next;
spin_unlock_irqrestore(&card->lock, flags);
if (!packet)
break;
sdio_claim_host(card->func);
ret = if_sdio_wait_status(card, IF_SDIO_IO_RDY);
if (ret == 0) {
ret = sdio_writesb(card->func, card->ioport,
packet->buffer, packet->nb);
}
if (ret)
pr_err("error %d sending packet to firmware\n", ret);
sdio_release_host(card->func);
kfree(packet);
}
}
初始化
static int if_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id)
{
...
card->workqueue = alloc_workqueue("libertas_sdio", WQ_MEM_RECLAIM, 0);
INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker);
...
}
规划工作队列运行
static int if_sdio_host_to_card(struct lbs_private *priv,
u8 type, u8 *buf, u16 nb)
{
...
queue_work(card->workqueue, &card->packet_worker);
...
}