这是从信号处理程序返回值的可靠方法吗?
Is this a reliable way of returning values from a signal handler?
我想收集已终止子进程的 pids,但我在与信号处理程序通信时遇到了问题...我没有 c(++)11。
这有效吗?还请考虑到我的程序将是多线程的(但队列将仅从一个线程使用)。
#include <iostream>
#include <csignal>
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <sys/wait.h>
template<typename _ValueType, int _Capacity>
class signal_result_queue
{
public:
static const int capacity = _Capacity;
typedef _ValueType value_type;
private:
//+1 so we can distinguish empty from full
volatile value_type _data[capacity+1];
volatile sig_atomic_t _base;
volatile sig_atomic_t _next;
volatile sig_atomic_t _overflows;
public:
signal_result_queue()
: _base(0)
, _next(0)
, _overflows(0)
{}
void push_sh(value_type value)
{
int base = _base; // we are not interrupted anyway
int next = _next; // same here
int before_base = wrap(base-1);
if(next == before_base)
{
++_overflows;
}
else
{
_data[next] = value;
_next = wrap(next+1);
}
}
int overflows_t1()
{
int overflows = _overflows;
_overflows -= overflows;
return overflows;
}
bool pop_t1(value_type& result)
{
// this is only changed by us.
int base = _base;
// It might increase but no problem
int next = _next;
if(base == next)
{
return false;
}
else
{
result = _data[base];
_base = wrap(base+1);
return true;
}
}
private:
static int wrap( int i )
{
while(i>=capacity+1)
{
i-=(capacity+1);
}
while(i<0)
{
i+=(capacity+1);
}
return i;
}
};
signal_result_queue<pid_t, 20> q;
void sigchld_handler(int)
{
pid_t pid;
while((pid = waitpid(static_cast<pid_t>(-1), 0, WNOHANG)) > 0){
q.push_sh(pid);
}
}
int main()
{
struct sigaction sa;
sa.sa_handler = &sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
if (sigaction(SIGCHLD, &sa, 0) == -1) {
perror(0);
std::exit(1);
}
for(int i=0;i<10;i++)
{
pid_t pid = fork();
if(pid == 0) // child
{
sleep(i);
return 0;
}
else if(pid == -1) // error
{
std::cout << "fork error" << std::endl;
}
else // parent
{
std::cout << "fork pid: " << pid << std::endl;
}
}
int terminated = 0;
do
{
sleep(1);
int overflows = q.overflows_t1();
if(overflows > 0)
{
std::cout << "Overflow in queue:" << overflows << std::endl;
}
pid_t val;
while(q.pop_t1(val))
{
terminated++;
std::cout << "Terminated: " << val << std::endl;
}
} while(terminated < 10);
return 0;
}
这不是一种安全的处理方式,原因有二:
volatile
不足以避免多线程程序中的竞争条件。 sig_atomic_t 保证对于信号重入是原子的,但我怀疑它是否足以用于多线程。
- 全局队列变量的使用不符合信号处理程序的重入要求。
在 C++11 中,您可以使用 atomic 而不是 volatile。如果没有 C++11,您需要使用 mutex lock 保护对共享变量的访问来保护对队列的并发访问。这将解决上述两个问题。
如有疑问补充说明:
只是为了更详细地说明它有多不安全:
假设处理了第一个进程信号:
int base = _base; // acces to _base is atomic. It's protected ONLY DURING THE STATEMENT
int next = _next; // same here
int before_base = wrap(base-1);
现在假设另一个线程调用处理程序,并在 CPU 上运行。为简单起见,假设第一个被系统调度程序搁置。所以处理程序的第二个实例执行:
int base = _base; // SAME BASE IS STORED LOCALLY
int next = _next; // SAME NEXT IS STORED LOCALLY
int before_base = wrap(base-1);
所以此时,处理程序的两个实例在 base
和 next
的本地副本中具有相同的索引。现在第二个实例继续:
...
_data[next] = value; // value is stored in the queue
_next = wrap(next+1); // and _next is incremented.
这里调度程序再次唤醒第一个实例,它立即继续:
_data[next] = value; // USES ITS LOCAL COPY OF NEXT, WRITE IN SAME PLACE THAN THE OTHER INSTANCE !!
_next = wrap(next+1); // and _next is incremented BASED ON LOCAL COPY.
所以此刻,在您的队列中,您将只有应该存储的两个值之一。
我朋友解决了,他只在信号发生时自增一个volatilesig_atomic_t,并在MAIN线程中使用waitpid。
很简单。我在信号处理程序中看到了一个调用 waitpid 的代码,但我没有意识到稍后可以在主线程中调用它。
不需要这个队列。
我想收集已终止子进程的 pids,但我在与信号处理程序通信时遇到了问题...我没有 c(++)11。 这有效吗?还请考虑到我的程序将是多线程的(但队列将仅从一个线程使用)。
#include <iostream>
#include <csignal>
#include <cstdlib>
#include <cstdio>
#include <unistd.h>
#include <sys/wait.h>
template<typename _ValueType, int _Capacity>
class signal_result_queue
{
public:
static const int capacity = _Capacity;
typedef _ValueType value_type;
private:
//+1 so we can distinguish empty from full
volatile value_type _data[capacity+1];
volatile sig_atomic_t _base;
volatile sig_atomic_t _next;
volatile sig_atomic_t _overflows;
public:
signal_result_queue()
: _base(0)
, _next(0)
, _overflows(0)
{}
void push_sh(value_type value)
{
int base = _base; // we are not interrupted anyway
int next = _next; // same here
int before_base = wrap(base-1);
if(next == before_base)
{
++_overflows;
}
else
{
_data[next] = value;
_next = wrap(next+1);
}
}
int overflows_t1()
{
int overflows = _overflows;
_overflows -= overflows;
return overflows;
}
bool pop_t1(value_type& result)
{
// this is only changed by us.
int base = _base;
// It might increase but no problem
int next = _next;
if(base == next)
{
return false;
}
else
{
result = _data[base];
_base = wrap(base+1);
return true;
}
}
private:
static int wrap( int i )
{
while(i>=capacity+1)
{
i-=(capacity+1);
}
while(i<0)
{
i+=(capacity+1);
}
return i;
}
};
signal_result_queue<pid_t, 20> q;
void sigchld_handler(int)
{
pid_t pid;
while((pid = waitpid(static_cast<pid_t>(-1), 0, WNOHANG)) > 0){
q.push_sh(pid);
}
}
int main()
{
struct sigaction sa;
sa.sa_handler = &sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
if (sigaction(SIGCHLD, &sa, 0) == -1) {
perror(0);
std::exit(1);
}
for(int i=0;i<10;i++)
{
pid_t pid = fork();
if(pid == 0) // child
{
sleep(i);
return 0;
}
else if(pid == -1) // error
{
std::cout << "fork error" << std::endl;
}
else // parent
{
std::cout << "fork pid: " << pid << std::endl;
}
}
int terminated = 0;
do
{
sleep(1);
int overflows = q.overflows_t1();
if(overflows > 0)
{
std::cout << "Overflow in queue:" << overflows << std::endl;
}
pid_t val;
while(q.pop_t1(val))
{
terminated++;
std::cout << "Terminated: " << val << std::endl;
}
} while(terminated < 10);
return 0;
}
这不是一种安全的处理方式,原因有二:
volatile
不足以避免多线程程序中的竞争条件。 sig_atomic_t 保证对于信号重入是原子的,但我怀疑它是否足以用于多线程。- 全局队列变量的使用不符合信号处理程序的重入要求。
在 C++11 中,您可以使用 atomic 而不是 volatile。如果没有 C++11,您需要使用 mutex lock 保护对共享变量的访问来保护对队列的并发访问。这将解决上述两个问题。
如有疑问补充说明:
只是为了更详细地说明它有多不安全:
假设处理了第一个进程信号:
int base = _base; // acces to _base is atomic. It's protected ONLY DURING THE STATEMENT
int next = _next; // same here
int before_base = wrap(base-1);
现在假设另一个线程调用处理程序,并在 CPU 上运行。为简单起见,假设第一个被系统调度程序搁置。所以处理程序的第二个实例执行:
int base = _base; // SAME BASE IS STORED LOCALLY
int next = _next; // SAME NEXT IS STORED LOCALLY
int before_base = wrap(base-1);
所以此时,处理程序的两个实例在 base
和 next
的本地副本中具有相同的索引。现在第二个实例继续:
...
_data[next] = value; // value is stored in the queue
_next = wrap(next+1); // and _next is incremented.
这里调度程序再次唤醒第一个实例,它立即继续:
_data[next] = value; // USES ITS LOCAL COPY OF NEXT, WRITE IN SAME PLACE THAN THE OTHER INSTANCE !!
_next = wrap(next+1); // and _next is incremented BASED ON LOCAL COPY.
所以此刻,在您的队列中,您将只有应该存储的两个值之一。
我朋友解决了,他只在信号发生时自增一个volatilesig_atomic_t,并在MAIN线程中使用waitpid。 很简单。我在信号处理程序中看到了一个调用 waitpid 的代码,但我没有意识到稍后可以在主线程中调用它。 不需要这个队列。