linux内核并发情景

通过《linux内核并发基本概念》,我们看到了,对于并发访问共享资源,造成的运行结果与预期的不一致问题,这样的结果是软件设计者不能允许的。我们知道,我们编写一个软件一定是需要软件实现特定的功能,如果我们在设计的时候,期望得到结果A,但实际软件运行中却得到的结果B,那么这个软件是相当糟糕的,因此,对于并发引起的竞态,是我们在设计内核和驱动软件时,必须要留意的。

在《linux内核并发基本概念》中,通过一个简单的并发的场景,引出了竞态、共享资源和临界区等概念。接下来,本文就linux内核中,容易造成并发的场景,进行一下分析。我们学习知识,往往偏重于对问题的解决方法,而对需要解决什么问题,一知半解,结果学完了方法,也不知道该怎么去在实际中使用,所以andrew觉得,首先我们对需要解决的问题来进行分析,通过分析问题为什么会出现,出现后有什么后果,来让我们认识并发引起的竞态到底是什么,有心的读者,可以在了解了问题之后,不要去翻阅资料查看linux并发控制,而是自己思考下如何解决这些问题,发散下自己的思维,也许会创造出比linux更好的并发控制方法呢。

本文只针对linux内核中常见的并发场景进行描述,为了让并发引起的竞态问题展现的淋漓尽致,本文在场景中不使用任何并发控制。

图1中,数组array为全局的,函数ArrayWrite_A和函数ArrayWrite_B都可以访问,执行函数ArrayWrite_A的线程为线程A,执行函数ArrayWrite_B的线程为线程B。

对称多处理器:

Linux内核从2.0版本开始支持对称多处理器,对称多处理器可以理解为一个计算机中有多个CPU,多个CPU共享内存、总线等资源。对称多处理器可以同时运行,每个CPU可以同时运行不同的代码段,也可能同时运行同一代码段。

如图2所示,例如CPU1和CPU2上执行的两个程序流,程序流1恰好运行好函数ArrayWrite_A开始处,同时,程序流2恰好运行到函数ArrayWrite_B的开始处,如果两个CPU继续运行下去,两条数据流并发运行引起的竞态,会对共享数组array造成什么样的影响,我们是无法预知的,但肯定不是我们想要的结果。

再来看一个简单的例子,如图3,data为一个全局的整形变量,初值为0,函数DataWrite将入参的值赋给全局变量data。在对称多处理器中,多个CPU也可能同时运行同一个代码段,如果CPU1和CPU2同时运行同一个代码段,该代码段包含图3中的函数,那么当两个CPU执行完函数DataWrite的代码后,全局变量的data的值到底是哪个CPU运行的结果呢?这就无法预知了。

中断:

中断也是linux内核中主要的并发源,因为中断可能随时会打断正在运行的代码,这就造成了竞态的危险。

线程A会调用图1中函数ArrayWrite_A,而中断处理程序会调用函数ArrayWrite_B。如图4所示,当线程A正在执行时,中断产生打断了线程A的执行,而中断处理程序也需要对全局数据array进行操作,这样就会引起全局数组array的数据不一致问题。如果线程A已经对全局数组array赋值完毕,中断处理程序势必会覆盖线程A的赋值结果。如果线程A在中断处理程序之后对全局数据array进行赋值,又势必会覆盖掉中断处理程序的结果,如果线程A正在对全局数组array进行赋值,还没有赋值完毕,而被中断处理程序打断,那么全局数组array的赋值情况就是未知的。

除了中断外,中断下半部也是并发的主要来源,这部分会在中断下半部中介绍。在linux内核2.6之后,开始支持内核抢占,正在执行的内核代码,随时可能被抢占,这又会导致并发,从而引起竞态的可能。

Ok,通过对对称多处理器和中断引起的并发场景进行描述,希望读者能更加深入地了解什么是并发,以及并发引起的竞态造成了什么样的后果。如果还没有了解过linux内核并发控制的读者,先不要去查看linux并发控制相关的资料,可以先想一下,上述的情景中,如何能够解决竞态引起的数据不一致问题呢?

时间: 2025-01-02 16:32:55

linux内核并发情景的相关文章

linux内核并发基本概念

在讨论linux内核并发之前,我们先来分享一个情景. 字符数组array是一个内核全局数组,执行函数ArrayWrite_A的线程称为线程A,执行函数ArrayWrite_B的线程称为线程B.线程A和线程B均可访问全局数组array,ArrayWrite_A函数实现的功能是将数组成员依次从0递增赋值到9,ArrayWrite_B函数实现的功能是将数组成员全部赋值为1. 假设此时线程A运行到函数ArrayWrite_A,我们期望的结果是函数ArrayWrite_A执行完后,数组array中的成员应

Linux内核源代码情景分析-fork()

父进程fork出子进程: fork经过系统调用,来到了sys_fork,详细过程请参考Linux内核源代码情景分析-系统调用. asmlinkage int sys_fork(struct pt_regs regs) { return do_fork(SIGCHLD, regs.esp, &regs, 0); } int do_fork(unsigned long clone_flags, unsigned long stack_start, //stack_start为用户空间堆栈指针 str

Linux内核源代码情景分析-内存管理之slab-回收

在上一篇文章Linux内核源代码情景分析-内存管理之slab-分配与释放,最后形成了如下图的结构: 图 1 我们看到空闲slab块占用的若干页面,不会自己释放:我们是通过kmem_cache_reap和kmem_cache_shrink来回收的.他们的区别是: 1.我们先看kmem_cache_shrink,代码如下: int kmem_cache_shrink(kmem_cache_t *cachep) { if (!cachep || in_interrupt() || !is_chaine

Linux内核源代码情景分析-系统调用mknod

普通文件可以用open或者create创建,FIFO文件可以用pipe创建,mknod主要用于设备文件的创建. 在内核中,mknod是由sys_mknod实现的,代码如下: asmlinkage long sys_mknod(const char * filename, int mode, dev_t dev) //比如filename为/tmp/server_socket,dev是设备号 { int error = 0; char * tmp; struct dentry * dentry;

Linux内核源代码情景分析-文件系统的安装

执行sudo mount -t ext2 /dev/sdb1 /mnt/sdb,将文件系统挂在到/mnt/sdb上.系统调用mount,映射到内核层执行的是sys_mount.假设/dev/sdb1和/mnt/sdb都位于ext2文件系统中. asmlinkage long sys_mount(char * dev_name, char * dir_name, char * type, unsigned long flags, void * data)//dev_name指向了"/dev/sdb

Linux内核源代码情景分析-访问权限与文件安全性

在Linux内核源代码情景分析-从路径名到目标节点,一文中path_walk代码中,err = permission(inode, MAY_EXEC)当前进程是否可以访问这个节点,代码如下: int permission(struct inode * inode,int mask) { if (inode->i_op && inode->i_op->permission) { int retval; lock_kernel(); retval = inode->i_

Linux内核源代码情景分析-文件系统安装后的访问

在Linux内核源代码情景分析-文件系统的安装,一文中,已经调用sudo mount -t ext2 /dev/sdb1 /mnt/sdb,在/mnt/sdb节点上挂载了文件系统,那么我们接下来访问/mnt/sdb/hello.c节点.我们来看一下path_walk的执行有什么不同? int path_walk(const char * name, struct nameidata *nd) { struct dentry *dentry; struct inode *inode; int er

Linux内核源代码情景分析-内存管理

用户空间的页面有下面几种: 1.普通的用户空间页面,包括进程的代码段.数据段.堆栈段.以及动态分配的"存储堆". 2.通过系统调用mmap()映射到用户空间的已打开文件的内容. 3.进程间的共享内存区. 这些页面的的周转有两方面的意思. 1.页面的分配,使用,回收.如进程压栈时新申请的页面,这类页面不进行盘区交换,不使用时释放得以回收. 这部分通过一个场景来解释: Linux内核源代码情景分析-内存管理之用户堆栈的扩展. 2.盘区交换.如要执行硬盘上的对应代码段.把硬盘上的代码段换入内

Linux内核源代码情景分析-共享内存

一.库函数shmget()--共享内存区的创建与寻找 asmlinkage long sys_shmget (key_t key, size_t size, int shmflg) { struct shmid_kernel *shp; int err, id = 0; down(&shm_ids.sem); if (key == IPC_PRIVATE) { err = newseg(key, shmflg, size);//分配一个共享内存区供本进程专用,最后返回的是一体化的标示号 } el