关于volatile和(*(unsigned long *)&jiffies)++

本文深入解析了C语言中volatile关键字的作用及其在多线程环境下的实现方式,通过三个小程序实例详细解释了volatile如何确保变量操作的原子性和防止编译器优化带来的潜在问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

volatile中文的意思是易于挥发的。在C语言中,如果一个变量被声明为volatile,则说明这个变量每回都要从内存读到寄存器中,操作完以后,再将值写回到内存中,编译器(例如gcc)并不将值cache在寄存器中。对于多线程或SMP系统,要注意使用volatile。
int wait = TRUE;


thread1() thread2()
{ {
while (wait) { wait = FALSE;
sleep(1000); ...
} }
}

当编译器看到thread1调用sleep的时候,它认为sleep是一个外部模块的函数,不可能改变wait的值,所以它"可能"就进行代码优化,将wait的值cache在寄存器中,以提高效率。假如另外一个线程thread2开始运行,将wait改成FALSE,如果CPU没有预测到wait变量的依赖关系的话,thread1就会一直sleep下去。在这种情况下,volatile可以强制编译器不将wait缓存到寄存器中。注意,一般情况下,CPU是能检测到wait被改过了,从而将寄存器刷新,但是这和CPU的体系结构有关,Linux是能支持多种体系结构的,所以经常使用volatile。
有网友问为什么在do_timer()函数中,有一句(*(unsigned long *)&jiffies)++,为什么不直接用jiffies++呢? 这个问题和两个原因有关:
* jiffiers是volatile变量
* CPU可能会重新排序指令


我编了3个小程序来测试,

jiffier1.c


int main()
{
unsigned long jiffiers = 777;


jiffiers++;
return 0;
}

gcc -S jiffer1.c,生成的汇编为

.file "jiffier1.c"
.version "01.01"
gcc2_compiled.:
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp, %ebp
subl , %esp
movl 7, -4(%ebp)
leal -4(%ebp), %eax
incl (%eax)
movl , %eax
leave
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 2.96 20000731 (Red Hat Linux 7.3 2.96-110)"


从汇编中,可以看到jiffiers的值是直接在内存里加一的。

jiffier2.c


int main()
{
volatile unsigned long jiffiers = 777;
jiffiers++;
return 0;
}

生成的汇编为

.file "jiffier2.c"
.version "01.01" gcc2_compiled.:
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp, %ebp
subl , %esp
movl 7, -4(%ebp)
movl -4(%ebp), %eax
incl %eax
movl %eax, -4(%ebp)
movl , %eax
leave
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 2.96 20000731 (Red Hat Linux 7.3 2.96-110)"

当jiffiers被声明为volatile时,在加一之前,会先从内存中将值读到寄存器%eax中, 直接都寄存器操作,完了以后不做cache,将值写回内存。它需要3条指令才完成了加一 操作,

movl -4(%ebp), %eax
incl %eax
movl %eax, -4(%ebp)


也就是说,jiffiers++并不是原子操作,在多处理器环境中,
问题就出来了,jiffers有可能被加了两次,内存中却只是加了1。
另外的原因是这3条指令执行的时候,可能会被重新排序,从而破坏了操作的原子性。
当然我们也可以直接调用汇编指令加一,但是do_timers函数是独立于体系结构的,
所以Linux使用的一种最简单的方法,也就是(*(unsigned long *)&jiffiers)++
来解决这些问题。

jiffier3.c


int main()
{
volatile unsigned long jiffiers = 777;


(*(unsigned long *)&jiffiers)++;


return 0;
}

生成的汇编为
.file "jiffier3.c"
.version "01.01" gcc2_compiled.:
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp, %ebp
subl , %esp
movl 7, -4(%ebp)
leal -4(%ebp), %eax
incl (%eax)
movl , %eax
leave
ret
.Lfe1: .size main,.Lfe1-main
.ident "GCC: (GNU) 2.96 20000731 (Red Hat Linux 7.3 2.96-110)"

可以看出来,生成的指令和jiffer1.c的一模一样,也就是说, (*(unsigned long*)&jiffiers)++将volatile的jiffiers转换成一般的内存变量,避免了用寄存器做cache,从而保证了jiffiers加一操作的原子性。
Linux一开始也是直接用jiffiers++的,到后来的版本才改成(*(unsigned long *)&jiffiers)++, 可想而知写一个牢固的OS是不容易的,要解决的隐含细节非常多。
Good Luck, Linux!
static void check_hung_uninterruptible_tasks(unsigned long timeout) { int max_count = sysctl_hung_task_check_count; unsigned long last_break = jiffies; struct task_struct *g, *t; bool need_check = true; /* * If the system crashed already then all bets are off, * do not report extra hung tasks: */ 1. Condition test_taint(7), taking false branch. 2. Condition did_panic, taking false branch. if (test_taint(TAINT_DIE) || did_panic) return; hung_task_show_lock = false; rcu_read_lock(); 3. Condition 0 /* !((((sizeof (g->tasks.next) == sizeof (char) || sizeof (g->tasks.next) == sizeof (short)) || sizeof (g->tasks.next) == sizeof (int)) || sizeof (g->tasks.next) == sizeof (long)) || sizeof (g->tasks.next) == sizeof (long long)) */, taking false branch. 4. Switch case value 8. 49821: (#2 of 2): Out-of-bounds read (OVERRUN) overrun-local: Overrunning array of 1 bytes at byte offset 7 by dereferencing pointer (__u64 *)__u.__c. 这里为什么会有越界? CID 166846:MISRA C-2004 Control Statement Expressions (MISRA C-2004 Rule 13.1) [ "select issue" ] for_each_process_thread(g, t) { if (!max_count--) goto unlock; if (time_after(jiffies, last_break + HUNG_TASK_LOCK_BREAK)) { if (!rcu_lock_break(g, t)) goto unlock; last_break = jiffies; } trace_android_vh_check_uninterruptible_tasks(t, timeout, &need_check); if (need_check) /* use "==" to skip the TASK_KILLABLE tasks waiting on NFS */ CID 49821:(#1 of 2):Out-of-bounds read (OVERRUN) [ "select issue" ] if (READ_ONCE(t->__state) == TASK_UNINTERRUPTIBLE) check_hung_task(t, timeout); } trace_android_vh_check_uninterruptible_tasks_dn(NULL); unlock: rcu_read_unlock(); if (hung_task_show_lock) debug_show_all_locks(); if (hung_task_show_all_bt) { hung_task_show_all_bt = false; trigger_all_cpu_backtrace(); } if (hung_task_call_panic) panic("hung_task: blocked tasks"); }
06-14
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值