免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: linuxiang
打印 上一主题 下一主题

多个进程把日志记录在同一个文件的问题 [复制链接]

论坛徽章:
0
11 [报告]
发表于 2006-08-04 10:12 |只看该作者
原帖由 isnowran 于 2006-8-4 09:59 发表
?我记忆中原子操作只有sync,open,fcntl,dup以及锁等等为数不多的函数,从来没有印象书中说过write、read也是原子操作,我先去查查。。。

不用查了,凡是系统调用都是原子操作。这是由内核决定的。(参见操作系统概念)
但,仅限于UP,对于SMP,情况要复杂一些。

[ 本帖最后由 narkissos 于 2006-8-4 10:13 编辑 ]

论坛徽章:
0
12 [报告]
发表于 2006-08-04 10:14 |只看该作者
Linux内核的同步机制(一):原子操作
原子操作:UP和SMP的异同
原子操作是不可分割的,在执行完毕不会被任何其它任务或事件中断。在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是"原子操作",因为中断只能发生于指令之间。这也是某些CPU指令系统中引入了test_and_set、test_and_clear等指令用于临界资源互斥的原因。但是,在对称多处理器(Symetric Multi-Processor)结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。我们以decl(递减指令)为例,这是一个典型的"读-改-写"过程,涉及两次内存访问。设想在不同CPU运行的两个进程都在递减某个计数值,可能发生的情况是:
1. CPU A(上的进程,以下同)从内存单元把当前计数值(2)装载进它的寄存器中;
2. CPU B从内存单元把当前计数值(2)装载进它的寄存器中。
3. CPU A在它的寄存器中将计数值递减为1;
4. CPU B在它的寄存器中将计数值递减为1;
5. CPU A把修改后的计数值(1)写回内存单元。
6. CPU B把修改后的计数值(1)写回内存单元。
我们看到,内存里的计数值应该是0,然而它却是1。如果该计数值是一个共享资源的引用计数,每个进程都在递减后把该值与0进行比较,从而确定是否需要释放该共享资源。这时,两个进程都去掉了对该共享资源的引用,但没有一个进程能够释放它--两个进程都推断出:计数值是1,共享资源仍然在被使用。
原子性不可能由软件单独保证--必须需要硬件的支持,因此是和架构相关的。在x86平台上,CPU提供了在指令执行期间对总线加锁的手段。CPU芯片上有一条引线#HLOCK pin,如果汇编语言的程序中在一条指令前面加上前缀"LOCK",经过汇编以后的机器代码就使CPU在执行这条指令的时候把#HLOCK pin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。
Linux内核中的原子操作
Linux 2.4.21中,原子类型的定义和原子操作API都放在内核源码树的include/asm/atomic.h文件中,大部分使用汇编语言实现,因为c语言并不能实现这样的操作。
在x86的原子操作实现代码中,定义了LOCK宏,这个宏可以放在随后的内联汇编指令之前。如果是SMP,LOCK宏被扩展为lock指令;否则被定义为空--单CPU无需防止其它CPU的干扰,锁内存总线完全是在浪费时间。
#ifdef CONFIG_SMP
#define LOCK "lock ; "
#else
#define LOCK ""
#endif
typedef struct { volatile int counter; } atomic_t;
在所有支持的体系结构上原子类型atomic_t都保存一个int值。在x86的某些处理器上,由于工作方式的原因,原子类型能够保证的可用范围只有24位。volatile是一个类型描述符,要求编译器不要对其描述的对象作优化处理,对它的读写都需要从内存中访问。
#define ATOMIC_INIT(i) { (i) }
用于在定义原子变量时,初始化为指定的值。如:
static atomic_t count = ATOMIC_INIT(1);
#define atomic_read(v) ((v)->counter)
读取v指向的原子变量的值。由于该操作本身就是原子的,只需要一次内存访问就能完成,因此定义为一个宏,并用C代码实现。
#define atomic_set(v,i) (((v)->counter) = (i))
设置v指向的原子变量为i。由于该操作本身就是原子的,只需要一次内存访问就能完成,因此定义为一个宏,并用C代码实现。
static __inline__ void atomic_add(int i, atomic_t *v)
将v指向的原子变量加上i。该函数不关心原子变量的新值,返回void类型。在下面的实现中,使用了带有C/C++表达式的内联汇编代码,格式如下(参考《AT&T ASM Syntax》):
__asm__ __volatile__("Instruction List" : Output : Input : Clobber/Modify);
__asm__ __volatile__指示编译器原封不动保留表达式中的汇编指令系列,不要考虑优化处理。涉及的约束还包括:
1. 等号约束(=):只能用于输出操作表达式约束,说明括号内的左值表达式v->counter是write-only的。
2. 内存约束(m):表示使用不需要借助寄存器,直接使用内存方式进行输入或输出。
3. 立即数约束(i):表示输入表达式是一个立即数(整数),不需要借助任何寄存器。
4. 寄存器约束(r):表示使用一个通用寄存器,由GCC在%eax/%ax/%al、%ebx/%bx/%bl、%ecx/%cx/%cl和%edx/%dx/%dl中选取一个合适的。
{
__asm__ __volatile__(
  LOCK "addl %1,%0"
  :"=m" (v->counter)
  :"ir" (i), "m" (v->counter));
}
static __inline__ void atomic_sub(int i, atomic_t *v)
从v指向的原子变量减去i。
static __inline__ int atomic_sub_and_test(int i, atomic_t *v)
从v指向的原子变量减去i,并测试是否为0。若为0,返回真,否则返回假。由于x86的subl指令会在结果为0时设置CPU的zero标志位,而且这个标志位是CPU私有的,不会被其它CPU影响。因此,可以执行一次加锁的减操作,再根据CPU的zero标志位来设置本地变量c,并相应返回。
{
unsigned char c;
__asm__ __volatile__(
  LOCK "subl %2,%0; sete %1"
  :"=m" (v->counter), "=qm" (c)
  :"ir" (i), "m" (v->counter) : "memory");
return c;
}
static __inline__ void atomic_inc(atomic_t *v)
递增v指向的原子变量。
static __inline__ void atomic_dec(atomic_t *v)
递减v指向的原子变量。
static __inline__ int atomic_dec_and_test(atomic_t *v)
递减v指向的原子变量,并测试是否为0。若为0,返回真,否则返回假。
static __inline__ int atomic_inc_and_test(atomic_t *v)
递增v指向的原子变量,并测试是否为0。若为0,返回真,否则返回假。
static __inline__ int atomic_add_negative(int i, atomic_t *v)
将v指向的原子变量加上i,并测试结果是否为负。若为负,返回真,否则返回假。这个操作用于实现semaphore。

论坛徽章:
0
13 [报告]
发表于 2006-08-04 10:18 |只看该作者
好。这不,理论证据来了。

论坛徽章:
0
14 [报告]
发表于 2006-08-04 10:29 |只看该作者
谢谢二位指点,纠正了我一个误区死角,至少今天就不算白活了;)

再提个疑问,如果通过网络write或read 1M 的数据,假设网络状况很糟糕,要持续30分钟,那么系统会怎么样?一直等下去吗?

或者说此时这个进程是否不能被打断,不能被kill,即使是kill -9 也不行?

论坛徽章:
0
15 [报告]
发表于 2006-08-04 10:31 |只看该作者
可以打断,但不代表write不是原子的。
比如被信号打断。

论坛徽章:
0
16 [报告]
发表于 2006-08-04 10:40 |只看该作者
比如说,某进程有一个周期1秒的定时器,这样的话write岂不是很容易被SIGALRM打断?
那么,他的原子性岂不是很没有立场?根本不能因为自己是原子操作而保障用户程序的正确性?

我就是因为这种情况一直认为他们不是原子操作。

论坛徽章:
0
17 [报告]
发表于 2006-08-04 10:59 |只看该作者
write似乎不是原子操作,因为write的实现肯定不是单条指令,但是它有一种机制来保证它的顺序,比如nfs的write

  1.         lock_kernel();
  2.         if (!IS_SYNC(inode) && inode_referenced) {
  3.                 err = nfs_writepage_async(ctx, inode, page, 0, offset);
  4.                 if (err >= 0) {
  5.                         err = 0;
  6.                         if (wbc->for_reclaim)
  7.                                 nfs_flush_inode(inode, 0, 0, FLUSH_STABLE);
  8.                 }
  9.         } else {
  10.                 err = nfs_writepage_sync(ctx, inode, page, 0,
  11.                                                 offset, priority);
  12.                 if (err >= 0) {
  13.                         if (err != offset)
  14.                                 redirty_page_for_writepage(wbc, page);
  15.                         err = 0;
  16.                 }
  17.         }
  18.         unlock_kernel();
复制代码


大多数write地实现都用锁来保证它的顺序的,如果谁写的write函数没有保证这个东东,那就混乱了。

论坛徽章:
0
18 [报告]
发表于 2006-08-04 11:00 |只看该作者
打断了,回来接着写,不会被其它write插进来。

论坛徽章:
0
19 [报告]
发表于 2006-08-04 11:00 |只看该作者
原帖由 isnowran 于 2006-8-4 10:29 发表
谢谢二位指点,纠正了我一个误区死角,至少今天就不算白活了;)

再提个疑问,如果通过网络write或read 1M 的数据,假设网络状况很糟糕,要持续30分钟,那么系统会怎么样?一直等下去吗?

或者说此时这个进 ...

可以被打断

论坛徽章:
0
20 [报告]
发表于 2006-08-04 11:37 |只看该作者
内核设计的有许多地方可以被打断。信号的打断一般在系统调用返回时候和进程切换时。这些不影响原子性。
如果被中断打断,中断程序是KERNEL的部分,它知道如何不影响系统调用的原子性

但是在KERNEL的许多地方是无法打断的。这时候,比如你错误地编写一个模块BLOCK式的读一个什么硬件设备,而数据一直没有,那KERNEL就死了。实际很简单,你遍一个模块,进入一个for(;, 一插入KERNEL,机器就死。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP