首页 / 更多教程 / C信号处理程序中的竞争条件
C信号处理程序中的竞争条件
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了C信号处理程序中的竞争条件,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含7906字,纯文字阅读大概需要12分钟。
内容图文
![C信号处理程序中的竞争条件](/upload/InfoBanner/zyjiaocheng/758/43022cf48560465f96521d6e3fc28598.jpg)
我正在做一些课程,我们已经看到了以下代码.一些问题询问代码的各个行是什么,这很好,我理解,但曲线球是“这个程序包含竞争条件.它出现在哪里以及为什么出现?”
代码:
#include <stdio.h>
#include <signal.h>
static void handler(int signo) {
printf("This is the SIGUSR1 signal handler!\n");
}
int main(void) {
sigset_t set;
sigemptyset(&set);
sigset(SIGUSR1, handler);
sigprocmask(SIG_SETMASK, &set, NULL);
while(1) {
printf("This is main()!\n");
}
return 0;
}
我在想,竞争条件是没有办法知道什么顺序“这是主要的”或“这是SIGUSR1”将在信号到达时打印,但如果有人可以确认或澄清这一点我我非常感激.他还询问如何修复(比赛条件),不寻找完整的答案,但任何提示将不胜感激.
解决方法:
显然,课程的目的是修改代码
>使用单独的线程,它接收循环中调用sigwait()或sigwaitinfo()的信号.必须阻止信号(首先,并且一直以来,对于所有线程),或者未指定操作(或者将信号传递到另一个线程).
这样就没有信号处理函数本身,这将仅限于异步信号安全功能.调用sigwait()/ sigwaitinfo()的线程是一个完全正常的线程,并且不受任何与信号或信号处理程序相关的限制.
(还有其他接收信号的方法,例如使用设置全局标志的信号处理程序,以及循环检查.大多数方法导致忙等待,运行无操作循环,无用地烧掉CPU时间:a非常糟糕的解决方案.我在这里描述的方式没有浪费CPU时间:内核会在调用sigwait()/ sigwaitinfo()时将线程置于休眠状态,并且仅在信号到达时将其唤醒.如果要限制持续时间在睡眠中,您可以使用sigtimedwait()代替.)
>自printf()等.不保证是线程安全的,您应该使用pthread_mutex_t来保护输出到标准输出 – 换句话说,这样两个线程就不会尝试在完全相同的时间输出.
在Linux中,这不是必需的,因为GNU C printf()(_unlocked()版本除外)是线程安全的;每次调用这些函数都使用内部互斥锁.
请注意,C库可能会缓存输出,因此要确保输出数据,您需要调用fflush(stdout);.
如果要以原子方式使用多个printf(),fputs()或类似调用,而其他线程无法在其间注入输出,则互斥锁特别有用.因此,即使在Linux上,在简单情况下也不需要互斥锁,因此建议使用互斥锁. (是的,您确实希望在保持互斥锁的同时执行fflush(),但如果输出阻塞,可能会导致互斥锁长时间保持.)
我个人完全不同地解决了整个问题 – 我在信号处理程序中使用write(STDERR_FILENO)输出到标准错误,并将主程序输出到标准输出;没有线程或任何特殊需要,只是信号处理程序中的一个简单的低级写循环.严格来说,我的程序行为会有所不同,但对最终用户而言,结果看起来非常相似. (除了可以将输出重定向到不同的终端窗口,并且并排查看它们;或者将它们重定向到辅助脚本/程序,这些脚本/程序将纳秒的挂钟时间戳添加到每个输入行;以及其他类似的技巧在调查时有用的事情.)
就个人而言,我发现从原始问题跳到“正确的解决方案” – 如果我所描述的确实是正确的解决方案;我相信它是 – 有点拉伸.当Saf提到正确的解决方案预计会使用pthread时,这种方法才对我产生了影响.
我希望你能找到这个信息,但不是一个扰流板.
编辑2013-03-13:
这是我用来安全地将数据从信号处理程序写入描述符的writefd()函数.我还包括了包装函数wrout()和wrerr(),您可以使用它们分别将字符串写入标准输出或标准错误.
#include <unistd.h>
#include <string.h>
#include <errno.h>
/**
* writefd() - A variant of write(2)
*
* This function returns 0 if the write was successful, and the nonzero
* errno code otherwise, with errno itself kept unchanged.
* This function is safe to use in a signal handler;
* it is async-signal-safe, and keeps errno unchanged.
*
* Interrupts due to signal delivery are ignored.
* This function does work with non-blocking sockets,
* but it does a very inefficient busy-wait loop to do so.
*/
int writefd(const int descriptor, const void *const data, const size_t size)
{
const char *head = (const char *)data;
const char *const tail = (const char *)data + size;
ssize_t bytes;
int saved_errno, retval;
/* File descriptor -1 is always invalid. */
if (descriptor == -1)
return EINVAL;
/* If there is nothing to write, return immediately. */
if (size == 0)
return 0;
/* Save errno, so that it can be restored later on.
* errno is a thread-local variable, meaning its value is
* local to each thread, and is accessible only from the same thread.
* If this function is called in an interrupt handler, this stores
* the value of errno for the thread that was interrupted by the
* signal delivery. If we restore the value before returning from
* this function, all changes this function may do to errno
* will be undetectable outside this function, due to thread-locality.
*/
saved_errno = errno;
while (head < tail) {
bytes = write(descriptor, head, (size_t)(tail - head));
if (bytes > (ssize_t)0) {
head += bytes;
} else
if (bytes != (ssize_t)-1) {
errno = saved_errno;
return EIO;
} else
if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
/* EINTR, EAGAIN and EWOULDBLOCK cause the write to be
* immediately retried. Everything else is an error. */
retval = errno;
errno = saved_errno;
return retval;
}
}
errno = saved_errno;
return 0;
}
/**
* wrout() - An async-signal-safe alternative to fputs(string, stdout)
*
* This function will write the specified string to standard output,
* and return 0 if successful, or a nonzero errno error code otherwise.
* errno itself is kept unchanged.
*
* You should not mix output to stdout and this function,
* unless stdout is set to unbuffered.
*
* Unless standard output is a pipe and the string is at most PIPE_BUF
* bytes long (PIPE_BUF >= 512), the write is not atomic.
* This means that if you use this function in a signal handler,
* or in multiple threads, the writes may be interspersed with each other.
*/
int wrout(const char *const string)
{
if (string)
return writefd(STDOUT_FILENO, string, strlen(string));
else
return 0;
}
/**
* wrerr() - An async-signal-safe alternative to fputs(string, stderr)
*
* This function will write the specified string to standard error,
* and return 0 if successful, or a nonzero errno error code otherwise.
* errno itself is kept unchanged.
*
* You should not mix output to stderr and this function,
* unless stderr is set to unbuffered.
*
* Unless standard error is a pipe and the string is at most PIPE_BUF
* bytes long (PIPE_BUF >= 512), the write is not atomic.
* This means that if you use this function in a signal handler,
* or in multiple threads, the writes may be interspersed with each other.
*/
int wrerr(const char *const string)
{
if (string)
return writefd(STDERR_FILENO, string, strlen(string));
else
return 0;
}
如果文件描述符引用管道,则writefd()可用于原子地写入PIPE_BUF(至少512个)字节. writefd()也可用于I / O密集型应用程序,以将信号(如果使用sigqueue(),相关值,整数或指针引发)转换为套接字或管道输出(数据),使其更容易多路复用多个I / O流和信号处理.变体(具有标记为close-on-exec的额外文件描述符)通常用于轻松检测子进程是执行另一个进程还是失败;否则很难检测出哪个进程 – 原始子进程或已执行进程退出.
在对这个答案的评论中,有一些关于errno的讨论,以及对write(2)修改errno这一事实是否使其不适合信号处理程序的困惑.
首先,POSIX.1-2008(及更早版本)将async-signal-safe函数定义为可以从信号处理程序安全地调用的函数. 2.4.3 Signal actions章节包括这些函数的列表,包括write().请注意,它还明确指出“获取errno值的操作和为errno赋值的操作应该是异步信号安全的”.
这意味着POSIX.1希望write()在信号处理程序中安全使用,并且还可以操纵该errno以避免被中断的线程在errno中看到意外的更改.
因为errno是一个线程局部变量,所以每个线程都有自己的errno.传递信号时,它总是会中断进程中的一个现有线程.信号可以指向特定的线程,但通常内核决定哪个线程获得进程范围的信号;它因系统而异.如果只有一个线程,初始线程或主线程,那么显然它是被中断的线程.所有这些意味着如果信号处理程序保存它最初看到的errno的值,并在它返回之前恢复它,则对errno的更改在信号处理程序之外是不可见的.
有一种方法可以检测到它,但是,在POSIX.1-2008中也通过谨慎的措辞暗示:
从技术上讲,& errno几乎总是有效的(取决于系统,编译器和应用的标准),并产生保存当前线程的错误代码的int变量的地址.因此,另一个线程可以监视另一个线程的错误代码,是的,该线程会在信号处理程序中看到对它的更改.但是,不能保证其他线程能够原子地访问错误代码(尽管它在许多架构上都是原子的):这样的“监视”只会提供信息.
遗憾的是,C中的几乎所有信号处理程序示例都使用stdio.h printf()等等.不仅在许多层面上都是错误的 – 从非异步安全到缓存问题,可能是对FILE字段的非原子访问,如果被中断的代码同时也在进行I / O,但是正确的使用unistd.h的解决方案类似于我在这个编辑中的例子就是这么简单.在信号处理程序中使用stdio.h I / O的基本原理似乎是“它通常有效”.我个人非常讨厌,因为例如暴力也“通常有效”.我认为这很愚蠢和/或懒惰.
我希望你发现这个信息丰富.
内容总结
以上是互联网集市为您收集整理的C信号处理程序中的竞争条件全部内容,希望文章能够帮你解决C信号处理程序中的竞争条件所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。