Linux进程有以下几种状态
1、R:运行状态,并不意味着程序在运行中,它表明进程要么是在运行中要么在运行队列里。
2、S:休眠状态,意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠)。
3、D:磁盘休眠状态,有时候也叫做不可中断睡眠状态,在这个状态中进程通常会等待IO结束。
4、T:停止状态,可以通过发送SIGSTOP信号来停止进程,这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行。
5、X:死亡状态,这个状态只是一个返回状态,你不会在列表中看到这个状态。
6、Z:僵尸状态,一种濒临死亡的状态。
接下来本文主要对linux进程的睡眠和唤醒进行分析
在Linux中,进程通常是由CPU调度实际执行的。当进程不能继续执行时,可能是因为它等待某些条件的发生(如I/O操作完成),此时进程会从运行状态转换为睡眠状态(也称为等待状态或阻塞状态)。进程睡眠是通过调用特定的系统调用或者在收到特定的信号时自动进行的。
睡眠的进程会被放入等待队列,并在等待的事件发生时被唤醒。在内核中,进程睡眠通常是通过将进程的状态设置为TASK_INTERRUPTIBLE(可中断睡眠)或TASK_UNINTERRUPTIBLE(不可中断睡眠),然后调用调度器schedule()来放弃CPU使用权,进程状态变为就绪态或者运行态。
当进程等待的条件满足时,通常是由其他进程或者中断服务例程来唤醒它。唤醒进程的常见方法是修改等待条件,或者发送一个特定的信号给睡眠的进程。
进程的睡眠
进程的睡眠是指进程暂时停止执行,直到满足某些条件才会继续执行。睡眠通常发生在以下情况下:
1、I/O 操作:当进程需要等待数据从磁盘、网络或其他设备读取时,它会进入睡眠状态,直到数据准备好。
2、同步操作:如果进程试图获取一个已被其他进程占用的资源,它可能会睡眠,直到资源可用。
3、定时器等待:进程可以使用定时器来等待一段时间,然后唤醒并继续执行。
进程的唤醒
进程的唤醒是指进程从睡眠状态恢复执行。唤醒通常发生在以下情况下:
1、I/O 完成:当进程等待的I/O操作完成时,它会被唤醒以继续执行。
2、信号处理:进程可以通过接收信号而被唤醒,然后执行与信号相关的操作。
3、条件满足:如果进程等待某些条件满足,一旦这些条件满足,它将被唤醒。
以下是一个简单的例子,展示了如何在Linux内核中睡眠和唤醒进程。
c#include <linux/wait.h>
#include <linux/sched.h>
// 定义等待队列和锁
DEFINE_WAIT(wait);
struct semaphore lock;
// 初始化信号量
void init_MUTEX(struct semaphore *sem)
{
sema_init(sem, 1);
}
// 获取信号量
void down(struct semaphore *sem)
{
down_interruptible(sem);
}
// 释放信号量
void up(struct semaphore *sem)
{
up(sem);
}
// 睡眠的进程
void process_sleep(struct semaphore *sem)
{
add_wait_queue(&sem->wait, &wait);
down(sem);
remove_wait_queue(&sem->wait, &wait);
}
// 唤醒进程
void process_wakeup(struct semaphore *sem)
{
up(sem);
}
// 使用示例
void example_usage()
{
init_MUTEX(&lock);
down(&lock); // 获取信号量,进程睡眠
// 在其他进程或中断服务例程中
// 执行必要的操作,然后唤醒睡眠的进程
process_wakeup(&lock);
// 睡眠的进程在被唤醒后继续执行
up(&lock);
}
在这个例子中,我们定义了一个信号量lock,并初始化为未锁定状态。然后,一个进程通过调用process_sleep()进入睡眠状态,它被放入了信号量lock的等待队列。另一个进程或中断服务例程通过调用process_wakeup()来唤醒这个进程,它释放了信号量lock,唤醒了等待的进程。
注意,实际的睡眠和唤醒操作通常是通过宏或者函数封装起来,以便于操作系统内核代码的维护和理解。在用户空间,应用程序通常使用POSIX提供的同步机制,如信号量、互斥锁和条件变量等。
在linux进程中会存在有无效唤醒的情况,这种情况是我们需要去避免的
无效唤醒
几乎在所有的情况下,进程都会在检查了某些条件之后,发现条件不满足才进入睡眠。可是有的时候进程却会在判定条件为真后开始睡眠,如果这样的话进程就会无限期地休眠下去,这就是所谓的无效唤醒问题。
在操作系统中,当多个进程都企图对共享数据进行某种处理,而 最后的结果又取决于进程运行的顺序时,就会发生竞争条件,这是操作系统中一个典型的问题,无效唤醒恰恰就是由于竞争条件导致的。
设想有两个进程A 和B,A 进程正在处理一个链表,它需要检查这个链表是否为空,如果不空就对链表里面的数据进行一些操作,同时B进程也在往这个链表添加节点。当这个链表是空的时候,由于无数据可操作,这时A进程就进入睡眠,当B进程向链表里面添加了节点之后它就唤醒A 进程。
避免无效唤醒
如何避免无效唤醒问题呢?
我们发现无效唤醒主要发生在检查条件之后和进程状态被设置为睡眠状态之前,本来B进程的 wake_up_process() 提供了一次将A进程状态置为 TASK_RUNNING 的机会,可惜这个时候A进程的状态仍然是 TASK_RUNNING,所以 wake_up_process() 将A进程状态从睡眠状态转变为运行状态的努力 没有起到预期的作用。
要解决这个问题,必须使用一种保障机制使得判断链表为空和设置进程状态为睡眠状态成为一个不可分割的步骤才行,也就是必须消除竞争条 件产生的根源,这样在这之后出现的 wake_up_process() 就可以起到唤醒状态是睡眠状态的进程的作用了。