进程唤醒与睡眠

注:本文主要参考自<<现代操作系统>>2.3.4, 2.3.5节

进程唤醒与睡眠

使用睡眠与唤醒避免忙等待

在前一节如何避免多进程(线程)因竞争条件引发的错误?,我们提出了集中能保证多个进程互斥访问临界区,我们所提出的解决方案均使用忙等待策略,即在进程等待进入临界区时,其持续检查,直到能够进入临界区为止.我们能否在进程未满足下一步工作条件时进入休眠状态,当进程满足下一步工作条件时,由其他进程唤醒该进程呢?如果可以实现这种方法,则我们可以避免进程的忙等待,从而节省CPU资源.

操作系统提供的sleep系统调用可以使当前进程进入休眠状态,此时进程将被阻塞,进入阻塞状态,CPU的占有权被交出.如果当前某一进程达到唤醒条件,则可以执行wakeup(pid)唤醒编号为pid的进程.

使用如下生产者-消费者问题解释进程的睡眠与唤醒.为简化问题,我们假定只有一个生产者,一个消费者.如果当前用于存放待消费物品的空间有限,假定其一次最多存放N个物品.因此,如果当前空间满,则生产者应当停止生产,进入睡眠状态.当空间不满时,再唤醒生产者.如果当前空间为空,则消费者进程应当停止消费,进入休眠状态,当空间不为空时,再唤醒消费者进程.我们可以用如下伪代码实现这一算法:

int N = 100;
int count = 0;

void producer(){
	int item = produce_item();
	if(count == N)
		sleep();
	count++;
	if(count == 1)
		wakeup(consumer);
}

void consumer(){
	if(count == 0)
		sleep();
	int item = remove_item();
	count--;
	if(count == N-1)
		wakeup(producer);
	counsume_item(item);
}

以上代码初看起来没有问题,当注意到生产者进程与消费者进程具有共享内存区,且对共享内存区变量的访问并未进行有效保护.在一般情况下,两个进程同时访问共享内存区似乎不会有太大问题,例如假设此时count == 3, 此时如果消费者进程被执行,则count变为2,假设此时还未进行实际消费,由于时钟中断,进程被切换到生成者进程,此时生产者添加生产item, count变为3,随后进程切换回消费者,完成实际的消费.注意到,虽然count变量的增加与实际物品的生产与消费被划分为两个阶段,但并未造成不良后果.

但如果是更为特殊的情形,则可能就会有麻烦了.假设此时count==1, 消费者进程首先执行,假设消费者进程消费完后,count = 0, 消费者程序再次调用判断count == 0,此时发生时钟中断,由于消费者进程被中断,因此并未执行sleep()这条语句,即消费者进程还未进入休眠状态.此时切换到执行生产者进程.生产者完成生产后,count变量变为1,生产者随后执行wakeup(counsumer)系统调用唤醒消费者进程.然而,由于此时消费者进程并未进入休眠状态,因此当前信号将被忽略.等到再次切换会消费者进程时,消费者进程执行sleep()进入休眠状态,但该进程永远不会被唤醒了.因为count变量此后会一直增加到N, 永远不会再次等于1了.当count等于N时,生产者进程也进入睡眠状态.由此,两个进程均进入睡眠状态,无法再次唤醒.

问题出在哪了?问题在于唤醒消费者进程的信号发送的太早了,此时消费者进程并未进入睡眠状态,因此该信号被忽略了.如果我们在接收到唤醒信号时保存起来,当消费者进程准备进入休眠状态时,我们检查是否存在唤醒信号,如果有,则不进入休眠,并将当前唤醒信号清楚.否则,进入休眠状态.如果我们有多个进程,那我们就需要为每一进程保存可能接受到的唤醒信号,有没有其他更合适的方法呢?

信号量

信号量是E.W.Dijkstra在1965年提出的一种方法,它使用一个整型变量来累计唤醒次数,供以后使用。在他的建议中引入了一个新的变量类型,称作信号量(semaphore)。一个信号量的取值可以为0(表示没有保存下来的唤醒操作)或者为正值(表示有一个或多个唤醒操作)。

Dijkstra建议设立两种操作:down和up(分别为一般化后的sleep和wakeup)。对一信号量执行down操作,则是检查其值是否大于0。若该值大于0,则将其值减1(即用掉一个保存的唤醒信号)并继续;若该值为0,则进程将睡眠,而且此时down操作并未结束。检查数值、修改变量值以及可能发生的睡眠操作均作为一个单一的、不可分割的原子操作完成。保证一旦一个信号量操作开始,则在该操作完成或阻塞之前,其他进程均不允许访问该信号量。这种原子性对于解决同步问题和避免竞争条件是绝对必要的。所谓原子操作,是指一组相关联的操作要么都不间断地执行,要么都不执行。原子操作在计算机科学的其他领域也是非常重要的。

确保信号量能正确工作,最重要的是要采用一种不可分割的方式来实现它。通常是将up和down作为系统调用实现,而且操作系统只需在执行以下操作时暂时屏蔽全部中断:测试信号量、更新信号量以及在需要时使某个进程睡眠。由于这些动作只需要几条指令,所以屏蔽中断不会带来什么副作用。如果使用多个CPU,则每个信号量应由一个锁变量进行保护。通过TSL或XCHG指令来确保同一时刻只有一个CPU在对信号量进行操作。

up操作对信号量的值增1。如果一个或多个进程在该信号量上睡眠(在信号量为0时调用了down操作),无法完成一个先前的down操作,则由系统选择其中的一个(如随机挑选)并允许该进程完成它的down操作。于是,对一个有进程在其上睡眠的信号量执行一次up操作之后,该信号量的值仍旧是0,但在其上睡眠的进程却少了一个。信号量的值增1和唤醒一个进程同样也是不可分割的。不会有某个进程因执行up而阻塞,正如在前面的模型中不会有进程因执行wakeup而阻塞一样。

注意到,多个进程可以共用一个信号量.例如,在生产者-消费者问题中,假设存在多个生产者与多个消费者.则我们可以仅使用一个信号量来纪录对消费者进程可能的睡眠与唤醒信号.假设当前信号量为0,此时如果3个消费者进程先后尝试执行,则其无法成功执行down操作,当前信号量上有三个进程进入睡眠状态.此后,如果生产者进程被调用,则其在信号量上执行up操作,由于3个消费者进程在该信号量上被阻塞,因此随机选择其中一个进程唤醒.此时该信号量上的睡眠进程数变为2.为了表示生产者进程的睡眠与唤醒信号,我们需要使用另一个信号量.每次调用消费者进程时,在该信号量上执行up操作,每次调用生产者进程时,在该信号量上执行down操作.

基于信号量的生产者-消费者多进程解决方案

该解决方案使用了三个信号量:一个称为full,用来记录充满的缓冲槽数目;一个称为empty,记录空的缓冲槽总数;一个称为mutex,用来确保生产者和消费者不会同时访问缓冲区。full的初值为0,empty的初值为缓冲区中槽的数目,mutex初值为1。供两个或多个进程使用的信号量,其初值为1,保证同时只有一个进程可以进入临界区,称作二元信号量(binary semaphore)。如果每个进程在进入临界区前都执行一个down操作,并在刚刚退出时执行一个up操作,就能够实现互斥。

#define N 100
typedef int semaphore

semaphore mutex 1;
semaphore full 0;
semaphore empty N;

void producer(){
	int item;
	item = produce_item();
	down(&empty);
	down(&mutex);
	insert_item(item);
	up(&full);
	up(&mutex);
}

void consume(){
	int item = remove_item();
	down(&full);
	down(&mutex);
	consume_item(item);
	up(&empty);
	up(&mutex);
}

信号量的另一种用途是用于实现同步(synchronization)。信号量full和empty用来保证某种事件的顺序发生或不发生。在本例中,它们保证当缓冲区满的时候生产者停止运行,以及当缓冲区空的时候消费者停止运行。这种用法与互斥是不同的。而信号量mutex在本例中的作用则是保证互斥.

注意观察上面使用信号量的程序,如果我们在生产者进程中,将down(&mutex)放在down(&empty)前面执行可能会造成什么问题?

假设当前缓冲区已满,此时如果生产者进程执行down(&mutex)成功,则在执行down(&empty)时,会被阻塞.因此,其无法执行up(&mutex).这导致消费者进程无法执行down(&mutex).因此消费者进程无法消耗缓冲区中的内容.此时两个进程均进入阻塞状态,即死锁.

### 回答1: CentOS 7启动httpd服务失败可能有多种原因,以下是一些常见的解决方法: 1. 检查httpd配置文件是否正确:可以使用命令`httpd -t`检查httpd配置文件是否正确,如果有错误,需要修改配置文件。 2. 检查端口是否被占用:可以使用命令`netstat -tlnp`查看端口是否被占用,如果被占用需要释放端口或修改httpd配置文件中的端口号。 3. 检查httpd服务是否安装:可以使用命令`rpm -qa | grep httpd`查看httpd服务是否安装,如果没有安装需要先安装httpd服务。 4. 检查httpd服务是否启动:可以使用命令`systemctl status httpd`查看httpd服务是否启动,如果没有启动需要使用命令`systemctl start httpd`启动httpd服务。 5. 检查SELinux是否开启:如果SELinux开启,可能会导致httpd服务启动失败,需要使用命令`setenforce 0`关闭SELinux,或者修改SELinux策略。 以上是一些常见的解决方法,如果以上方法都无法解决问题,可以查看httpd服务日志文件,找到具体的错误信息,然后根据错误信息进行解决。 ### 回答2: CentOS 7上的httpd服务启动失败可能有多种原因。以下列出了一些常见问题和解决方法: 1. 端口被占用 当httpd试图占用已被其他程序占用的端口时会启动失败。此时可以通过使用`netstat -tunlp`命令检查端口占用情况,然后杀死占用该端口的进程及时释放端口。或者修改httpd的配置文件,将端口修改为未被占用的端口。 2. 配置文件错误 有时httpd服务的配置文件中可能出现错误,例如语法错误或路径错误等等。在启动httpd服务之前,可以使用`apachectl configtest`命令进行检查,如果输出“Syntax OK”,则表示配置文件没有错误。如果出现错误,则需要根据错误提示进行相应修改。 3. 依赖关系问题 如果httpd依赖的其他程序或库缺失,也会导致启动失败。可以通过使用`systemctl status httpd.service`命令来查看httpd服务状态,如果输出“Failed to start”或“Loaded: failed”,则需要检查依赖关系是否完整。 4. SELinux问题 当SELinux启用时,有时会导致httpd服务启动失败。在这种情况下,可以在SELinux上禁用httpd服务,或者修改httpd配置文件解决SELinux相关的问题。 5. 用户权限问题 httpd服务启动可能需要特定的用户权限。如果使用的用户权限不够,则无法启动。可以尝试使用root用户启动httpd服务,或者根据需要修改相应的用户权限。 ### 回答3: CentOS 7中的Apache HTTP服务器(httpd)是一个常见的Web服务器,如果遇到httpd服务启动失败的情况,可能会影响服务器正常的工作和对外服务的稳定性。本文将提供一些可能会导致httpd服务启动失败的原因,并给出相应的解决方法。 1. 端口被占用 如果端口被其他进程占用,httpd服务就无法启动。可以通过 netstat -tulpn 命令查看端口占用情况,并杀死占用该端口的进程。如果端口被 httpd 服务自身占用,可以通过 systemctl restart httpd 命令重启 httpd 服务;如果是其他进程占用了端口,可以通过 kill 命令杀死该进程或更改 httpd.conf 文件配置,将 httpd 服务的端口改为其他空闲端口,重新启动。 2. 配置文件错误 httpd 服务的配置文件通常是 /etc/httpd/conf/httpd.conf,如果其中存在语法错误、权限问题或者其它配置错误,可能会导致 httpd 服务启动出错。可以通过将 httpd.conf 文件备份后删掉,重新执行 yum install httpd 命令安装 httpd 服务,然后手动修改 httpd.conf 文件,逐个检查每个配置项是否正确,确认无误后重启 httpd 服务。 3. SELinux 问题 SELinux 是 CentOS 7中提供的一种安全模块,它可以对系统文件和应用程序进行安全管控。如果 SELinux 配置不正确,可能会阻止 httpd 服务正常启动。可以通过修改 /etc/selinux/config 文件中 SELINUX=disabled 来暂时关闭 SELinux,然后重新启动 httpd 服务;或者一个更优的方式是,根据日志确定问题原因,使用命令 semanage 或者 setsebool 等工具将相关目录或者配置加入到 SELinux 许可列表中,重新启动 httpd 服务,以恢复服务正常工作。 4. 防火墙问题 如果你的 CentOs 7 服务器启用了防火墙,有可能会导致 httpd 服务启动失败。可以通过检查防火墙相关配置来确定问题原因,解决方案是修改防火墙规则,将端口 80 或者 443 等 httpd 服务需要的端口放行,重新启动 httpd 服务。 总之,当遇到 httpd 服务启动失败时,不要慌张,可以先通过日志或者执行命令查看错误信息,找到错误原因,然后根据错误原因一步一步解决问题。在解决问题过程中注意备份原始配置文件,以免造成不必要的损失。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值