a. volatile是C语言定义的关键字,gcc为了需要又定义了__volatile__,它和
volatile表达的是同一意思。
b. volatile的本意是"易变的",由于访问寄存器的速度快于访存,所以编译器一般
都会作优化以减少访存。如果变量加上volatile修饰,则编译器就不会对此变量
的读写操作进行优化,即不通过寄存器缓冲而直接访存。
c. __asm__ __volatile__一起指示编译器不要改动优化后面的汇编语句。
http://www.gnu.org/s/hello/manual/libc/Atomic-Types.html
24.4.7.2 Atomic Types
To avoid uncertainty about interrupting access to a variable, you can use a particular data type for which access is always atomic:sig_atomic_t
. Reading and writing this data type is guaranteed to happen in a single instruction, so there's no way for a handler to run “in the middle” of an access.
The type sig_atomic_t
is always an integer data type, but which one it is, and how many bits it contains, may vary from machine to machine.
This is an integer data type. Objects of this type are always accessed atomically.
int
is atomic. You can also assume that pointer types are atomic; that is very convenient. Both of these assumptions are true on all of the machines that the GNU C library supports and on all POSIX systems we know of.
另摘录2:
控制变量的类型定义为,
static volatile sig_atomic_t canjump;
变量实际为某种整型,C标准规定程序写这种类型的变量时,不会被中断(C99 7.14)。(实现上,系统保证这种类型的变量存储不会跨越页的边界,可以用一条指令对其进行存取)。
数据类型sig_atomic_t,当把变量声明为该类型会保证该变量在使用或赋值时, 无论是在32位还是64位的机器上都能保证操作是原子的, 它会根据机器的类型自动适应。
在处理信号(signal)的时候,有时对于一些变量的访问希望不会被中断,无论是硬件中断还是软件中断,这就要求访问或改变这些变量需要在计算机的一条指令内完成。通常情况下,int类型的变量通常是原子访问的,也可以认为 sig_atomic_t就是int类型的数据,因为对这些变量要求一条指令完成,所以sig_atomic_t不可能是结构体,只会是数字类型。
在linux里这样定义:
typedef int __sig_atomic_t;
另外gnu c的文档也说比int短的类型通常也是具有原子性的,例如short类型。同时,指针(地址)类型也一定是原子性的。 该类型在所有gnu c库支持的系统和支持posix的系统中都有定义。
这种类型的变量总是包括ISO类型修饰符volatile,其原因是:该变量将由两个不同的控制线程访问(比如main函数和异步执行的信号处理程序)。volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。 而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在k中。而不是重新从i里面读。这样一来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问,不会出错。
#include "apue.h"
volatile sig_atomic_t quitflag; /* set nonzero by signal handler */
static void
sig_int(int signo) /* one signal handler for SIGINT and SIGQUIT */
{
if (signo == SIGINT)
printf("/ninterrupt/n");
else if (signo == SIGQUIT)
quitflag = 1; /* set flag for main loop */
}
int
main(void)
{
sigset_t newmask, oldmask, zeromask;
if (signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal(SIGINT) error");
if (signal(SIGQUIT, sig_int) == SIG_ERR)
err_sys("signal(SIGQUIT) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGQUIT);
/*
* Block SIGQUIT and save current signal mask.
*/
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
while (quitflag == 0)
sigsuspend(&zeromask);
/*
* SIGQUIT has been caught and is now blocked; do whatever.
*/
quitflag = 0;
/*
* Reset signal mask which unblocks SIGQUIT.
*/
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
exit(0);
}
sigsuspend(const sigset_t *sigmask); 将进程的屏蔽子替换为由参数sigmask给出的信号集,然后挂起程序的执行。当从sigsuspend函数返回后,信号屏蔽字将被设置为调用sigsuspend之前的值。这是原子操作的。早起的信号操作不可靠的一个问题是,如果要实现这个操作必须调用两个函数,sigprocmask和pause。而在sigprocmask和pause之间,有可能发生信号,那么当执行到pause()函数时,pause函数将会死等下去。
(come from apue and internet)