内核同步机制之complete
- 格式:docx
- 大小:16.58 KB
- 文档页数:2
Linux时间同步原理一、背景介绍在当今信息化社会中,时间的准确性和同步性对于各种应用至关重要。
无论是服务器、网络设备还是个人计算机,都需要精确的时间戳来记录事件、保证数据的一致性以及进行各种基于时间的操作。
Linux作为最流行的开源操作系统,其时间同步机制对于系统的稳定性和可靠性起到了至关重要的作用。
二、时间同步的重要性时间同步在许多方面都发挥着关键作用。
首先,对于需要记录事件顺序的应用,如日志审计、安全监控等,精确的时间戳是不可或缺的。
其次,许多网络协议和服务依赖于时间同步,例如DNS、NTP(网络时间协议)等。
此外,时间同步还与系统性能和资源利用率密切相关,例如任务调度、磁盘I/O等。
三、Linux时间同步机制Linux操作系统采用了一套复杂而高效的时间同步机制,以确保系统时间的准确性和一致性。
以下是这一机制的主要组成部分:1. 系统时钟系统时钟是Linux内核维护的时间基准。
它以硬件时钟(通常是一个实时时钟,RTC)为基础,并可以由系统管理员进行配置和调整。
系统时钟还负责提供时间戳服务和进程时间测量。
2. NTPNTP(Network Time Protocol)是Linux用于时间同步的主要协议。
它通过互联网或其他网络,从NTP服务器获取准确的时间,并自动调整系统时钟以保持与服务器的时间同步。
NTP使用分层架构和选举机制,以提供可靠的时间服务。
3. PTP(Precision Time Protocol)PTP用于精确测量和同步局域网内的高精度时钟。
它主要应用于需要微秒级时间精度的应用,如音频和视频处理、高性能计算等。
4. 守护进程和工具Linux提供了如ntpd和chronyd等守护进程,它们在后台运行,负责监听NTP或PTP时间服务器,并自动调整系统时钟。
此外,还有一些用户空间工具,如ntpq和chronyc,用于查询和监视时间同步状态。
四、时间同步技术细节1. NTP工作原理NTP采用客户端-服务器架构,通过交换NTP数据包来调整客户端的时钟以匹配服务器的时间。
⏹内核空间◆对于提供保护机制的现代系统来说,内核独立于普通应用程序,它一般处于系统态,拥有受保护的内存空间和访问硬件设备的所有权限。
这种系统态和被保护起来的内存空间,统称为内核空间。
⏹用户空间◆应用程序在用户空间执行。
它们只能看到允许它们使用的部分系统资源,并且不能使用某些特定的系统功能,不能直接访问硬件,还有其他一些使用限制。
当内核运行的时候,系统以内核态进入内核空间,相反,普通用户程序以用户态进入用户空间⏹进程上下文◆当一个应用程序请求执行一条系统调用,我们说内核正在代其执行。
进一步解释,应用程序被称为通过系统调用在内核空间运行,而内核被称为运行于进程上下文中。
这种交互关系——应用程序通过系统调用陷入内核——是应用程序完成其工作的基本行为方式。
⏹中断上下文◆许多操作系统的中断服务程序都不在进程上下文中执行。
它们在一个与所有进程都无关的、专门的中断上下文中运行。
◆这些上下文代表着内核活动的范围。
概括为下列三者之一:☐运行于内核空间,处于进程上下文,代表某个特定的进程执行。
☐运行干内核空间,处于中断上下文,与任何进程无关,处理某个特定的中断。
☐运行于用户空间,执行用户进程。
配置编译内核:$ tar zxvf linux-4.4.19.tar.gz在编译内核之前,首先你必须配置它。
由于内核提供了数不胜数的功能,支持了难以计数的硬件,因而有许多东西需要配置。
这些配置项要么是二选一,要么是三选一。
配置选项也可以是字符串或整数。
⏹内核提供了各种不同的工具来简化内核配置。
◆最简单的一种是一个基于文本的命令行工具:$make config☐该工具会挨个遍历所有配置项,要求用户选择yes、no或是module(如果是三选一的话)。
◆用基于ncurse库的图形界面工具:$make menuconfig◆用基于x11的图形工具:$make xconfig◆用基于gtk+图形工具:$make gconfig编译内核:配置完成后保存$ make -j2 V=1编译完后得到linux内核: arch/arm/boot/zImage内核开发的特点:◆内核编程时不能访问C库。
内核同步介绍Linux设备驱动中必须解决的一个问题是多个进程对共享资源的并发访问,并发访问会导致竞态,linux提供了多种解决竞态问题的方式,这些方式适合不同的应用场景。
Linux内核是多进程、多线程的操作系统,它提供了相当完整的内核同步方法。
内核同步方法列表如下:中断屏蔽原子操作自旋锁读写自旋锁顺序锁信号量读写信号量BKL(大内核锁)Seq锁一、并发与竞态:定义:并发(concurrency)指的是多个执行单元同时、并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量、静态变量等)的访问则很容易导致竞态(race conditions)。
在linux中,主要的竞态发生在如下几种情况:1、对称多处理器(SMP)多个CPU特点是多个CPU使用共同的系统总线,因此可访问共同的外设和存储器。
2、单CPU内进程与抢占它的进程3、中断(硬中断、软中断、Tasklet、底半部)与进程之间只要并发的多个执行单元存在对共享资源的访问,竞态就有可能发生。
如果中断处理程序访问进程正在访问的资源,则竞态也会会发生。
多个中断之间本身也可能引起并发而导致竞态(中断被更高优先级的中断打断)。
解决竞态问题的途径是保证对共享资源的互斥访问,所谓互斥访问就是指一个执行单元在访问共享资源的时候,其他的执行单元都被禁止访问。
访问共享资源的代码区域被称为临界区,临界区需要以某种互斥机制加以保护,中断屏蔽,原子操作,自旋锁,和信号量都是linux 设备驱动中可采用的互斥途径。
临界区和竞争条件:所谓临界区(critical regions)就是访问和操作共享数据的代码段,为了避免在临界区中并发访问,编程者必须保证这些代码原子地执行——也就是说,代码在执行结束前不可被打断,就如同整个临界区是一个不可分割的指令一样,如果两个执行线程有可能处于同一个临界区中,那么就是程序包含一个bug,如果这种情况发生了,我们就称之为竞争条件(race conditions),避免并发和防止竞争条件被称为同步。
linux中的同步机制Linux中的同步机制在操作系统中,同步机制是一种重要的机制,用于协调多个进程或线程的执行顺序,确保数据的一致性和正确性。
Linux作为一种开源的操作系统,也提供了多种同步机制,本文将介绍其中常用的几种。
1. 互斥锁(Mutex)互斥锁是一种最简单且常用的同步机制。
它通过对临界区域加锁,保证同一时间只有一个进程或线程可以访问该区域。
当一个进程或线程获得了互斥锁后,其他进程或线程必须等待该锁的释放才能继续执行。
Linux提供了多种互斥锁的实现,如传统的pthread_mutex_t和更高级的std::mutex。
2. 信号量(Semaphore)信号量是一种更为复杂的同步机制,用于控制对共享资源的访问。
它维护一个计数器,可以通过原子操作进行增减,当计数器等于0时,进程或线程需要等待;当计数器大于0时,进程或线程可以继续执行。
Linux提供了多种信号量的实现,如System V的信号量(semget、semop等)和POSIX信号量(sem_init、sem_wait等)。
3. 条件变量(Condition Variable)条件变量是一种用于线程间通信的同步机制。
它允许线程在某个条件满足时等待,直到其他线程满足条件后通过唤醒操作通知等待的线程。
Linux提供了pthread_cond_t等条件变量的实现,通过pthread_cond_wait和pthread_cond_signal等函数来实现等待和唤醒操作。
4. 屏障(Barrier)屏障是一种同步机制,用于在多个线程中设置一个同步点,要求所有线程在此处等待,直到所有线程都到达同步点后才能继续执行。
屏障常用于解决多线程并行计算中的任务分配和结果合并问题。
Linux提供了pthread_barrier_t等屏障的实现,通过pthread_barrier_wait等函数来实现等待操作。
5. 读写锁(Reader-Writer Lock)读写锁是一种特殊的互斥锁,用于在读多写少的情况下提高并发性能。
Windows内核模式下的线程同步Windwos下内核模式/用户模式实现线程同步的比较•在用户模式下进行线程同步的最大好处就是非常快。
利用内核对象进行线程同步时,调用线程必须从用户模式切换到内核模式,此过程相对挺慢的•需要在多个进程间进行线程同步时或者线程同步时希望设置超时时间,那么只能利用内核对象进行线程同步常用等待函数函数功能WaitForSingleObject Waits until the specified object is in the signaled state orthe time-out interval elapses. WaitForMultipleObjects Waits until one or all of the specified objects are in thesignaled state or the time-out interval elapses.参考资料事件内核对象函数功能CreateEvent Creates or opens a named or unnamed eventobject第二个参数为TRUE代表手动,为FALSE代表自动第三个参数代表初始状态是否为触发状态OpenEvent Opens an existing named event object. SetEvent Sets the specified event object to the signaledstate.ResetEvent Sets the specified event object to thenonsignaled state.情况手动重置事件自动重置事件事件内核对象被触发所有等待该事件的线程均变为可调度状态只有一个等待该事件的线程变为可调度状态等待事件内核对象成功的副作用无自动置为非触发状态可等待的计时器内核对象函数功能CreateWaitableTimer Creates or opens a waitable timer object.第二个参数:TRUE表示手动重置,为FALSE表示为自动重置函数功能OpenWaitableTimer Opens an existing named waitable timer object. SetWaitableTimer Activates the specified waitable timer. When thedue time arrives, the timer is signaled and thethread that set the timer calls the optionalcompletion routine. CancelWaitableTimer Sets the specified waitable timer to the inactivestate.情况手动重置计时器自动重置计时器计时器内核对象被触发所有等待该计时器内核对象的线程均变为可调度状态只有一个等待该计时器内核对象的线程变为可调度状态等待的副作用无自动置为非触发状态SetWaitableTimer详解参数解释HANDLE hTimer可等待的计时器内核对象句柄const LARGE_INTEGER *lpDueTime 第一次触发时间,可以是一个绝对时间(此时间可以已逝去),可以是一个相对调用时间(传负值,传入值必须是100纳秒的整数倍)LONG lPeriod间隔触发时间,单位为毫秒,传0代表只触发一次PTIMERAPCROUTINEpfnCompletionRoutine回调函数LPVOIDlpArgToCompletionRoutine回调函数参数BOOL fResume一般为 FALSE注意点•可等待的计时器内核对象会在其中一指定的时间触发,或每隔一段时间触发一次•当且仅当SetWaitableTimer的调用线程处于可提醒状态时,由此函数设定的回调函数会被同一线程调用(异步过程调用,APC)。
操作系统:进程同步基本概念在 Os 中引⼊进程后,虽然提⾼了资源的利⽤率和系统的吞吐量,但由于进程的异步性,也会给系统造成混乱,尤其是在他们争⽤临界资源时。
例如,当多个进程去争⽤⼀台打印机时,有可能使多个进程的输出结果交织在⼀起,难于区分;⽽当多个进程去争⽤共享变量、表格、链表时,有可能致使数据处理出错。
进程同步的主要任务是对多个相关进程在执⾏次序上进⾏协调,以使并发执⾏的诸进程之间能有效地共享资源和相互合作,从⽽使程序的执⾏具有可再现性。
在资源共享的情况下:保证诸进程以互斥的⽅式访问临界资源—必须以互斥⽅式访问的共享资源;在相互合作的关系中:进程同步的主要任务是保证相互合作的诸进程在执⾏次序上协调,(有些教材把这种功能称做“协调”)。
相互合作的进程可能同时存在资源共享的关系。
如何实现进程互斥,需要让进程以互斥的⽅式进⼊各⾃的临界区,先执⾏进⼊区的代码。
⼈为地加⼀段代码。
临界资源必须以互斥⽅式访问的共享资源counter的例⼦:在机器语⾔中实现两个进程给count加⼀的操作register1 = countregister1 = register1 + 1count = register1register2 = countregister2 = register2 + 1count = register2但是如果是并发执⾏,可能会出现下⾯的情况register1 = countregister2 = countregister1 = register1 + 1register2 = register2 + 1count = register1count = register2结果就不对了。
可见,counter应该作为临界资源。
多个进程必须对其进⾏互斥访问临界区在每个进程中访问临界资源的那段代码称为临界区。
如果能保证诸进程互斥地进⼊⾃⼰的临界区,便可实现诸进程对临界资源的互斥访问。
每个进程在进⼊临界区之前,应先对欲访问的临界资源进⾏检查,看它是否正被访问。
Linux内核同步机制简介1 介绍1)由于现代Linux操作系统是多任务、SMP、抢占式以及中断是异步执行的,导致共享资源容易被并发访问,从而使得访问共享资源的各线程之间互相覆盖共享数据,造成被访问数据处于不一致状态,因此Linux提供了同步机制来防止并发访问。
2)常用的同步机制(如自旋锁)用来保护共享数据使用起来简单有效,但由于CPU的处理速度与访问内存的速度差距越来越大,导致获取锁的开销相对于CPU的速度在不断的增加。
因为这种锁使用了原子操作指令,需要原子地访问内存,即获取锁的开销与访问内存的速度相关。
3)Linux内核根据对不同共享资源的特性,提供多种同步机制:原子操作、自旋锁、读-写自旋锁、信号量、读-写信号量、完成变量、顺序锁、禁止抢占、内存屏障及RCU,本文将对其分别进行简要介绍。
2 原子操作(atomic)2.1 基本原理1)所谓原子操作,就是该操作绝不会在执行完毕前被任何其它任务或事件打断,它是最小的执行单位,不可能有比它更小的执行单位。
2)原子操作通常是内联函数,通过内联汇编指令来实现。
3)原子操作需要硬件的支持,因此不同的体系结构的实现方式不同。
4)内核提供了两组原子操作接口:整数操作和位操作。
2.1.2 原子整数操作1)原子操作主要用于实现资源计数,很多引用计数就是通过原子操作实现的。
2)原子类型定义如下:(参看RHEL6.5GA_x86_64内核文件:/root/include/linux/types.h)3)针对整数的原子操作只能对atomic_t类型的数据进行处理,原因如下:a)让原子函数只接受atomic_t类型的操作数,可以确保原子操作只与这种特殊类型一起使用。
b)使用atomic_t类型确保编译器不对相应的值进行优化,使得原子操作最终接收到正确的内存地址。
c)可以屏蔽不同体系结构上实现原子操作的差异。
2.1.2 原子位操作1)位操作函数是对普通的内存地址进行操作的,对所操作的数据类型没有要求。
本文周详的介绍了Linux内核中的同步机制:原子操作、信号量、读写信号量和自旋锁的API,使用需求及一些典型示例一、引言在现代操作系统里,同一时间可能有多个内核执行流在执行,因此内核其实象多进程多线程编程相同也需要一些同步机制来同步各执行单元对共享数据的访问。
尤其是在多处理器系统上,更需要一些同步机制来同步不同处理器上的执行单元对共享的数据的访问。
在主流的Linux内核中包含了几乎所有现代的操作系统具有的同步机制,这些同步机制包括:原子操作、信号量(semaphore)、读写信号量(rw_semaphore)、spinlock、BKL(Big Kernel Lock)、rwlock、brlock(只包含在2.4内核中)、RCU(只包含在2.6内核中)和seqlock(只包含在2.6内核中)。
二、原子操作所谓原子操作,就是该操作绝不会在执行完毕前被所有其他任务或事件打断,也就说,他的最小的执行单位,不可能有比他更小的执行单位,因此这里的原子实际是使用了物理学里的物质微粒的概念。
原子操作需要硬件的支持,因此是架构相关的,其API和原子类型的定义都定义在内核源码树的include/asm/atomic.h文件中,他们都使用汇编语言实现,因为C语言并不能实现这样的操作。
原子操作主要用于实现资源计数,非常多引用计数(refcnt)就是通过原子操作实现的。
原子类型定义如下:typedef struct{volatile int counter;}atomic_t;volatile修饰字段告诉gcc不要对该类型的数据做优化处理,对他的访问都是对内存的访问,而不是对寄存器的访问。
原子操作API包括:atomic_read(atomic_t * v);该函数对原子类型的变量进行原子读操作,他返回原子类型的变量v的值。
atomic_set(atomic_t * v, int i);该函数设置原子类型的变量v的值为i。
Linux 同步方法剖析内核原子,自旋锁和互斥锁你也许接触过并发(concurrency)、临界段(critical section)和锁定,不过怎么在内核中使用这些概念呢?本文讨论了 2.6 版内核中可用的锁定机制,包括原子运算符(atomic operator)、自旋锁(spinlock)、读/写锁(reader/writer lock)和内核信号量(kernel semaphore)。
本文还探讨了每种机制最适合应用到哪些地方,以构建安全高效的内核代码。
本文讨论了 Linux 内核中可用的大量同步或锁定机制。
这些机制为 2.6.23 版内核的许多可用方法提供了应用程式接口(API)。
不过在深入学习 API 之前,首先需要明白将要解决的问题。
并发和锁定当存在并发特性时,必须使用同步方法。
当在同一时间段出现两个或更多进程并且这些进程彼此交互(例如,共享相同的资源)时,就存在并发现象。
在单处理器(uniprocessor,UP)主机上可能发生并发,在这种主机中多个线程共享同一个 CPU 并且抢占(preemption)创建竞态条件。
抢占通过临时中断一个线程以执行另一个线程的方式来实现 CPU 共享。
竞态条件发生在两个或更多线程操纵一个共享数据项时,其结果取决于执行的时间。
在多处理器(MP)计算机中也存在并发,其中每个处理器中共享相同数据的线程同时执行。
注意在 MP 情况下存在真正的并行(parallelism),因为线程是同时执行的。
而在 UP 情形中,并行是通过抢占创建的。
两种模式中实现并发都较为困难。
Linux 内核在两种模式中都支持并发。
内核本身是动态的,而且有许多创建竞态条件的方法。
Linux 内核也支持多处理(multiprocessing),称为对称多处理(SMP)。
临界段概念是为解决竞态条件问题而产生的。
一个临界段是一段不允许多路访问的受保护的代码。
这段代码能操纵共享数据或共享服务(例如硬件外围设备)。
Linux内核的同步机制信号量与自旋锁在现代操作系统里,同一时间可能有多个内核执行流在执行,因此内核其实象多进程多线程编程一样也需要一些同步机制来同步各执行单元对共享数据的访问。
尤其是在多处理器系统上,更需要一些同步机制来同步不同处理器上的执行单元对共享的数据的访问。
信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为1就变成互斥锁(Mutex),即同时只能有一个任务可以访问信号量保护的共享资源。
信号量的API有:DECLARE_MUTEX(name)该宏声明一个信号量name并初始化它的值为0,即声明一个互斥锁。
DECLARE_MUTEX_LOCKED(name)该宏声明一个互斥锁name,但把它的初始值设置为0,即锁在创建时就处在已锁状态。
因此对于这种锁,一般是先释放后获得。
void sema_init (struct semaphore *sem, int val);该函用于数初始化设置信号量的初值,它设置信号量sem的值为val。
void init_MUTEX (struct semaphore *sem);该函数用于初始化一个互斥锁,即它把信号量sem的值设置为1。
void init_MUTEX_LOCKED (struct semaphore *sem);该函数也用于初始化一个互斥锁,但它把信号量sem的值设置为0,即一开始就处在已锁状态。
void down(struct semaphore * sem);该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文(包括IRQ上下文和s oftirq上下文)使用该函数。
该函数将把sem的值减1,如果信号量sem的值非负,就直接返回,否则调用者将被挂起,直到别的任务释放该信号量才能继续运行。
int down_interruptible(struct semaphore * sem);该函数功能与down类似,不同之处为,down不会被信号(signal)打断,但down_interrup tible能被信号打断,因此该函数有返回值来区分是正常返回还是被信号中断,如果返回0,表示获得信号量正常返回,如果被信号打断,返回-EINTR。
内核编程中常见的一种模式是,在当前线程之外初始化某个活动,然后等待该活动的结束。
这个活动可能是,创建一个新的内核线程或者新的用户空间进程、对一个已有进程的某个请求,或者某种类型的硬件动作,等等。
在这种情况下,我们可以使用信号量来同步这两个任务。
然而,内核中提供了另外一种机制——completion接口。
Completion是一种轻量级的机制,他允许一个线程告诉另一个线程某个工作已经完成。
linux/include/linux/completion.h
struct completion {
unsigned int done;//指示等待的事件是否完成。
初始化时为0。
如果为0,则表示等待的事件未完成。
大于0表示等待的事件已经完成。
wait_queue_head_t wait; //等待队列。
};
虽然信号量可以用于实现同步,但往往可能会出现一些不好的结果。
例如:当进程A分配了一个临时信号量变量,把它初始化为关闭的MUTEX,并把其地址传递给进程B,然后在A之上调用down(),进程A打算一旦被唤醒就撤销给信号量。
随后,运行在不同CPU上的进程B在同一个信号量上调用up()。
然而,up()和down()的目前实现还允许这两个函数在同一个信号量上并发。
因此,进程A可以被唤醒并撤销临时信号量,而进程B还在运行up()函数。
结果up()可能试图访问一个不存在的数据结构。
这样就会出现错误。
为了防止发生这种错误就专门设计了completion机制专门用于同步。
定义和初始化:
struct completion completion; 动态初始化completion
init_completion(&completion);
static inline void init_completion(struct completion *x)
{
x->done = 0;
init_waitqueue_head(&x->wait);
}直接定义并调用init_completion()初始化。
init_completion()会将done字段初始化为0,wait字段的自旋锁为未锁,等待队列为空。
这说明调用该完成量的进程必须等待某事件完成(即另外一进程必须先调用completiom()唤醒该完成量)。
(2)
DECLARE_COMPLETION(completion); 静态初始化completion
直接定义并初始化completion完成量,效果等同于以上定义方式。
2、等待完成量:
(1)wait_for_completion()函数,/linux/kernel/sched.c
该函数相当于信号量中的down()操作。
不过在操作中对使用其自身的自旋锁。
如果done为0,则说明等待的事件没有完成,则调用DECLARE_WAITQUEUE()定义等待队列wait并将当前进程添加进等待队列wait。
然后将wait添加进该完成量的等待队列的末尾,进入循环。
设置当前进程为不可中断状态(TASK_UNINTERRUPTIBLE),释放自旋锁并让当前进程进入睡眠状态。
一旦进程被调度唤醒据又获得自旋锁并查看等待的事件是否完成。
如果完成(大于0),则从完成量的等待队列中删除等待的进程,并自减
也是等待完成量。
与wait_for_completion()最大的区别是它等待超时的情况下返回。
也就是说如果经过给定的时间该完成量还没有被唤醒,就直接返回。
这样最大的好处是经过一定的时间该进程已经不需要等待某事件,那么就可以直接被唤醒继续执行。
(3)wait_for_completion_interruptible()函数,/linux/kernel/sched.c
可见这中等待完成量的方式是可以被信号打断的。
如果当前进程收到如果收到TIF_SIGPENDING信号,则等待该完成量的进程会被从等待队列中删除,并返回ERESTARTSYS。
举例complete使用方式.
在sd 的request函数中,从SD读取数据时通过D MA来读取数据的。
在请求函数中初始化一个completion。
在读取数据时,配置好DMA的地址与数据长度,启动D MA开始读取数据后,调用wait_for_completion_timeout(&host->xfer_done,
5*DAT_TIMEOU T)等待数据读取完成。
带DMA读取数据完毕后会产生一个中断,来告诉系统数据读取完毕。
在中断会开启D MA 的tasklet来调用complete(&host->xfer_done) ,告诉SD_request函数已经读取完毕,wait_for_completion_timeout此函数会返回据timeout还有多久。
否则超时,读取失败。
One task
{
Complete( completion);
}
Another task
{
Init_completion(completion)
Wait_for_completion/waitfor_completion_timeout函数等待
}。