Linux内核的同步机制(API函数) .
- 格式:pdf
- 大小:286.45 KB
- 文档页数:14
linux进程间同步机制一、进程间同步的概念在多进程系统中,进程间的通信是必要的,但同时也要防止进程间的相互干扰和数据污染。
进程间的同步机制就是用于解决这一问题的机制,它通过控制进程间的执行顺序、共享资源访问等方式,确保进程间的正确协作。
1. 互斥锁(Mutex)互斥锁是一种常用的进程同步机制,用于保护共享资源,防止多个进程同时访问和修改共享资源,导致数据错误或不一致。
使用互斥锁时,进程需要先获取锁才能访问共享资源,释放锁后才能进行其他操作。
示例代码:```cpthread_mutex_t mutex;pthread_mutex_lock(&mutex);// 访问共享资源pthread_mutex_unlock(&mutex);```2. 信号量(Semaphore)信号量是一种计数器,用于控制对共享资源的访问。
它可以实现进程间的同步和互斥,确保在任何时刻只有一个进程可以访问共享资源。
示例代码:```c#define MAX_COUNT 5sem_t sem;sem_wait(&sem); // 等待信号量释放// 访问共享资源sem_post(&sem); // 释放信号量```3. 屏障(Barrier)屏障是一种用于同步进程的机制,用于确保所有进程在执行完一定操作后才能继续执行。
在多线程或多进程编程中,屏障可以用于确保所有线程或进程完成了某个阶段的准备工作后,再继续执行后续的操作。
示例代码:```cpthread_barrier_t barrier;pthread_barrier_wait(&barrier); // 等待所有线程或进程到达屏障位置```4. 管道(Pipe)和消息队列(Message Queue)管道和消息队列是用于进程间通信的机制,它们允许一个进程向另一个进程发送消息或数据。
通过管道和消息队列,进程间可以异步地交换数据,从而实现同步。
Linux内核态⽂件读写相关函数API1、前⾔Linux系统中的⽂件系统由两层结构进⾏构建:第⼀层为虚拟⽂件系统(VFS),第⼆层则是各种不同的具体的⽂件系统。
VFS则是将各种具体的⽂件系统的公共部分抽取出来,从⽽形成⼀个抽象层,是Linux系统内核的⼀部分,它位于⽤户程序和具体的⽂件系统之间,对⽤户提供了标准的⽂件系统调⽤接⼝,对于具体的⽂件系统,通过⼀系列的对不同⽂件系统公⽤的函数指针来实际调⽤具体⽂件系统的函数,完成实际的各种差异操作。
对于⽤户,对⽂件的读写操作时,可以使⽤函数open()、read()、write()和close()等,当在Linux内核中,肯定是没有这些函数可以使⽤,这个时候,可以使⽤内核的⼀些函数filp_open()、vfs_read()、vfs_write()、和filp_close()等去完成⽂件的读写。
2、常⽤API接⼝接下来,简单介绍相关的内核API接⼝是如何使⽤的,对于filp_open()、filp_close()、vfs_read()、vfs_write()函数的声名在⽂件linux/fs.h中。
(1)⽂件打开filp_open函数原型如下:struct file *filp_open(const char *filename, int flags, umode_t mode);参数说明:filename:要打开或创建⽂件的字符串名称,包括路径部分;flags:⽂件的打开⽅式,该取值与open()函数类似,可以取O_CREATE、O_RDWR、O_RDONLY;mode:创建⽂件时使⽤该参数,设置⽂件的读写权限,其它情况可以设置为0。
返回值:⽂件打开成功返回正确的struct file *指针,失败返回错误的指针。
注意:函数filp_open()将返回struct file *结构指针,将会提供给后继的函数进⾏使⽤,需要使⽤IS_ERR()来校验指针的有效性。
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)。
临界段概念是为解决竞态条件问题而产生的。
一个临界段是一段不允许多路访问的受保护的代码。
这段代码能操纵共享数据或共享服务(例如硬件外围设备)。
[标题]:深入解析kthread_run函数的使用方法【摘要】:本文将介绍kthread_run函数的概念、使用方法和实际应用场景,帮助读者深入理解Linux内核中的线程调度机制。
1. 什么是kthread_run函数?在Linux内核中,kthread_run函数是用于创建和管理内核线程的函数之一。
内核线程是在内核态运行的轻量级进程,它们通常被用于执行一些后台任务或者处理一些不需要用户交互的工作。
kthread_run 函数是基于内核中的kthread API实现的,它允许开发人员在内核空间创建和管理线程,从而可以在内核态执行一些需要长时间运行的任务,而不会占用过多的系统资源。
2. kthread_run函数的使用方法使用kthread_run函数很简单,只需要传入一个指向线程函数的指针和一个指向传递给线程函数的参数的指针即可。
线程函数是一个普通的C函数,用于执行线程的具体逻辑,参数则可以用于传递需要的数据给线程函数。
开发人员还可以通过设置不同的线程属性来指定线程的优先级、调度策略以及绑定CPU等。
```c// 使用kthread_run函数创建内核线程struct task_struct *kthread_run(int (*threadfn)(void *data), void*data, const char *namefmt, ...);```3. kthread_run函数的实际应用场景kthread_run函数广泛应用于Linux内核中的各种模块和子系统中,例如网络子系统、文件系统和驱动程序等。
在网络子系统中,可以使用kthread_run函数创建专门用于数据包处理的线程,以避免阻塞网络服务进程。
在文件系统中,可以使用kthread_run函数创建用于数据同步和清理的线程,以提高文件系统的性能和稳定性。
在驱动程序中,可以使用kthread_run函数创建用于设备管理和状态监测的线程,以提供更完善的设备控制功能。
linux中的同步机制Linux中的同步机制在Linux操作系统中,同步机制是一种重要的机制,用于控制并发访问共享资源的顺序和互斥。
它确保多个进程或线程能够有序地访问共享资源,避免数据竞争和不一致的结果。
本文将介绍Linux中常用的同步机制,包括互斥锁、条件变量、信号量和屏障等。
一、互斥锁(Mutex)互斥锁是一种最常见的同步机制,用于保护共享资源的访问。
在互斥锁的帮助下,只有一个进程或线程能够获得锁,其他进程或线程需要等待锁的释放。
Linux提供了多种互斥锁的实现,如pthread_mutex_t和std::mutex等。
使用互斥锁需要注意避免死锁和竞态条件等问题。
二、条件变量(Condition Variable)条件变量是一种用于线程间通信的同步机制,它允许线程在满足特定条件之前等待,从而避免了忙等待的问题。
在Linux中,条件变量通常与互斥锁一起使用。
当某个线程发现条件不满足时,它可以调用条件变量的等待函数将自己阻塞,直到其他线程满足条件并发出信号,唤醒等待的线程。
三、信号量(Semaphore)信号量是一种用于控制并发访问的同步机制,它可以实现对资源的计数和管理。
Linux提供了两种类型的信号量:二进制信号量和计数信号量。
二进制信号量只有两种状态(0和1),用于互斥访问共享资源;计数信号量可以有多个状态,用于限制并发访问的数量。
通过使用信号量,可以实现进程或线程之间的同步和互斥。
四、屏障(Barrier)屏障是一种用于线程同步的机制,它在多个线程到达指定点之前将它们阻塞,直到所有线程都到达后才继续执行。
屏障可以用于并行计算中的阶段同步,确保每个阶段的计算完成后再进行下一阶段的计算。
在Linux中,可以使用pthread_barrier_t来创建和操作屏障。
五、读写锁(ReadWrite Lock)读写锁是一种特殊的锁机制,用于在读操作和写操作之间提供更好的并发性。
读写锁允许多个线程同时读取共享资源,但只允许一个线程进行写操作。
Linux C语言API说明一、引言Linux操作系统是一款强大的开源操作系统,广泛应用于各种领域。
在Linux环境下进行C语言开发,需要了解和掌握Linux提供的各种应用程序接口(API)。
这些API提供了丰富的功能,包括文件操作、系统调用、网络编程和线程编程等。
本文将对Linux C语言API的主要方面进行详细的说明和解释。
二、Linux C语言API概览Linux C语言API主要由系统调用接口、库函数、工具和实用程序等组成。
这些API提供的功能涉及操作系统核心功能、文件操作、网络编程、多线程编程等。
1.系统调用接口:系统调用接口是应用程序与操作系统内核交互的接口,提供了一系列的系统调用函数。
这些函数允许应用程序请求内核提供服务,如进程管理、文件操作、网络通信等。
系统调用接口通常通过"unistd.h"或"sys/types.h"等头文件定义。
2.库函数:库函数是Linux C语言API的重要组成部分,提供了许多常用的功能和工具。
这些库函数通常由标准C库(如glibc)提供,包括字符串处理、数学计算、数据结构等。
库函数通过提供封装好的函数接口,使得开发者可以更加方便地使用这些功能,而无需直接调用系统调用接口。
3.工具和实用程序:Linux还提供了一系列工具和实用程序,用于管理和维护系统。
这些工具包括编译器、调试器、性能分析工具等。
了解和掌握这些工具的使用方法,对于开发人员来说也是非常重要的。
三、系统调用接口系统调用接口是Linux C语言API的重要组成部分,提供了许多核心的系统服务。
下面是一些常用的系统调用接口:1.进程管理:fork()、exec()、wait()、kill()等函数用于创建新进程、执行新程序、等待进程结束以及发送信号给进程等操作。
2.文件操作:open()、read()、write()、close()等函数用于打开文件、读取数据、写入数据以及关闭文件等操作。
linux内核分析课后答案Linux是将应用层序的请求传递给硬件,并充当底层驱动程序,对系统中的各种设备和组件进行寻址。
支持模块的动态装卸(裁剪)。
Linux内核就是基于这个策略实现的。
Linux进程1.采用层次结构,每个进程都依赖于一个父进程。
内核启动init程序作为第一个进程。
该进程负责进一步的系统初始化操作。
init进程是进程树的根,所有的进程都直接或者间接起源于该进程。
从技术层面讲,内核是硬件与软件之间的一个中间层。
作用是将应用层序的请求传递给硬件,并充当底层驱动程序,对系统中的各种设备和组件进行寻址。
从应用程序的层面讲,应用程序与硬件没有联系,只与内核有联系,内核是应用程序知道的层次中的最底层。
在实际工作中内核抽象了相关细节。
内核是一个资源管理程序。
负责将可用的共享资源(CPU时间、磁盘空间、网络连接等)分配得到各个系统进程。
内核就像一个库,提供了一组面向系统的命令。
系统调用对于应用程序来说,就像调用普通函数一样。
Linux 内核可以进一步划分成 3 层。
最上面是系统调用接口,它实现了一些基本的功能,例如 read 和 write。
系统调用接口之下是内核代码,可以更精确地定义为独立于体系结构的内核代码。
这些代码是 Linux 所支持的所有处理器体系结构所通用的。
在这些代码之下是依赖于体系结构的代码,构成了通常称为 BSP(Board SupportPackage)的部分。
这些代码用作给定体系结构的处理器和特定于平台的代码。
Linux 内核实现了很多重要的体系结构属性。
在或高或低的层次上,内核被划分为多个子系统。
Linux 也可以看作是一个整体,因为它会将所有这些基本服务都集成到内核中。
这与微内核的体系结构不同,后者会提供一些基本的服务,例如通信、I/O、内存和进程管理,更具体的服务都是插入到微内核层中的。
每种内核都有自己的优点,不过这里并不对此进行讨论。
随着时间的流逝,Linux 内核在内存和 CPU 使用方面具有较高的效率,并且非常稳定。
linux线程间同步和互斥的方法随着计算机技术的飞速发展,多线程应用已经变得越来越普遍。
在Linux操作系统中,多线程是一种强大的工具,它允许程序同时执行多个任务,从而提高系统的并发性和效率。
然而,多线程应用也带来了一些挑战,如线程间的同步和互斥问题。
本文将介绍Linux线程间同步和互斥的方法。
一、互斥(Mutex)互斥是最基本的同步机制之一,用于保护共享资源,防止多个线程同时访问同一资源而造成数据混乱。
在Linux中,可以使用pthread_mutex_t类型来创建互斥锁。
使用pthread_mutex_lock()函数来锁定互斥锁,确保同一时刻只有一个线程可以访问被保护的资源;使用pthread_mutex_unlock()函数来解锁互斥锁,允许其他线程访问该资源。
二、条件变量(ConditionVariable)条件变量是一种更复杂的同步机制,它允许一个或多个线程在满足某个条件时被唤醒。
在Linux中,可以使用pthread_cond_t类型来创建条件变量。
线程可以通过pthread_cond_wait()函数进入等待状态,直到条件满足时被唤醒。
使用pthread_cond_signal()或pthread_cond_broadcast()函数来通知其他等待的线程。
三、读写锁(Read-WriteLock)读写锁是一种更高效的同步机制,它允许多个读线程同时访问共享资源,但在写操作时只允许一个写线程访问。
在Linux中,可以使用pthread_rwlock_t类型来创建读写锁。
读线程可以同时获取读锁,而写线程必须获取写锁。
当写线程释放写锁时,读线程可以再次获取读锁。
这种机制可以提高并发性能,降低资源争用的开销。
四、信号量(Semaphore)信号量是一种用于控制并发访问的计数器。
它通常用于计数有限的资源数量,如文件描述符或磁盘空间。
在Linux中,可以使用sem_t 类型来创建信号量。
使用sem_wait()函数来减少信号量的值,表示消耗了一个资源;使用sem_post()函数来增加信号量的值,表示释放了一个资源。
本文详细的介绍了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)就是通过原子操作实现的。
原子类型定义如下:volatile修饰字段告诉gcc不要对该类型的数据做优化处理,对它的访问都是对内存的访问,而不是对寄存器的访问。
原子操作API包括:该函数对原子类型的变量进行原子读操作,它返回原子类型的变量v的值。
该函数设置原子类型的变量v的值为i。
该函数给原子类型的变量v增加值i。
该函数从原子类型的变量v中减去i。
该函数从原子类型的变量v中减去i,并判断结果是否为0,如果为0,返回真,否则返回假。
该函数对原子类型变量v原子地增加1。
该函数对原子类型的变量v原子地减1。
该函数对原子类型的变量v原子地减1,并判断结果是否为0,如果为0,返回真,否则返回假。
该函数对原子类型的变量v原子地增加1,并判断结果是否为0,如果为0,返回真,否则返回假。
该函数对原子类型的变量v原子地增加I,并判断结果是否为负数,如果是,返回真,否则返回假。
该函数对原子类型的变量v原子地增加i,并且返回指向v的指针。
该函数从原子类型的变量v中减去i,并且返回指向v的指针。
该函数对原子类型的变量v原子地增加1并且返回指向v的指针。
该函数对原子类型的变量v原子地减1并且返回指向v的指针。
原子操作通常用于实现资源的引用计数,在TCP/IP协议栈的IP碎片处理中,就使用了引用计数,碎片队列结构struct ipq描述了一个IP碎片,字段refcnt就是引用计数器,它的类型为atomic_t,当创建IP碎片时(在函数ip_frag_create中),使用atomic_set函数把它设置为1,当引用该IP碎片时,就使用函数atomic_inc把引用计数加1。
当不需要引用该IP碎片时,就使用函数ipq_put来释放该IP碎片,ipq_put使用函数atomic_dec_and_test把引用计数减1并判断引用计数是否为0,如果是就释放IP碎片。
函数ipq_kill 把IP碎片从ipq队列中删除,并把该删除的IP碎片的引用计数减1(通过使用函数atomic_dec实现)。
三、信号量(semaphore)Linux内核的信号量在概念和原理上与用户态的System V的IPC机制信号量是一样的,但是它绝不可能在内核之外使用,因此它与System V的IPC机制信号量毫不相干。
信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为1就变成互斥锁(Mutex),即同时只能有一个任务可以访问信号量保护的共享资源。
一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减1,若当前信号量的值为负数,表明无法获得信号量,该任务必须挂起在该信号量的等待队列等待该信号量可用;若当前信号量的值为非负数,表示可以获得信号量,因而可以立刻访问被该信号量保护的共享资源。
当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果信号量的值为非正数,表明有任务等待当前信号量,因此它也唤醒所有等待该信号量的任务。
信号量的API有:该宏声明一个信号量name并初始化它的值为0,即声明一个互斥锁。
该宏声明一个互斥锁name,但把它的初始值设置为0,即锁在创建时就处在已锁状态。
因此对于这种锁,一般是先释放后获得。
该函用于数初始化设置信号量的初值,它设置信号量sem的值为val。
该函数用于初始化一个互斥锁,即它把信号量sem的值设置为1。
该函数也用于初始化一个互斥锁,但它把信号量sem的值设置为0,即一开始就处在已锁状态。
该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文(包括IRQ上下文和softirq 上下文)使用该函数。
该函数将把sem的值减1,如果信号量sem的值非负,就直接返回,否则调用者将被挂起,直到别的任务释放该信号量才能继续运行。
该函数功能与down类似,不同之处为,down不会被信号(signal)打断,但down_interruptible 能被信号打断,因此该函数有返回值来区分是正常返回还是被信号中断,如果返回0,表示获得信号量正常返回,如果被信号打断,返回-EINTR。
该函数试着获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,表示不能获得信号量sem,返回值为非0值。
因此,它不会导致调用者睡眠,可以在中断上下文使用。
该函数释放信号量sem,即把sem的值加1,如果sem的值为非正数,表明有任务等待该信号量,因此唤醒这些等待者。
信号量在绝大部分情况下作为互斥锁使用,下面以console驱动系统为例说明信号量的使用。
在内核源码树的kernel/printk.c中,使用宏DECLARE_MUTEX声明了一个互斥锁console_sem,它用于保护console驱动列表console_drivers以及同步对整个console驱动系统的访问。
其中定义了函数acquire_console_sem来获得互斥锁console_sem,定义了release_console_sem来释放互斥锁console_sem,定义了函数try_acquire_console_sem来尽力得到互斥锁console_sem。
这三个函数实际上是分别对函数down,up和down_trylock的简单包装。
需要访问console_drivers驱动列表时就需要使用acquire_console_sem来保护console_drivers列表,当访问完该列表后,就调用release_console_sem释放信号量console_sem。
函数console_unblank,console_device,console_stop,console_start,register_console和unregister_console都需要访问console_drivers,因此它们都使用函数对acquire_console_sem和release_console_sem来对console_drivers进行保护。
四、读写信号量(rw_semaphore)读写信号量对访问者进行了细分,或者为读者,或者为写者,读者在保持读写信号量期间只能对该读写信号量保护的共享资源进行读访问,如果一个任务除了需要读,可能还需要写,那么它必须被归类为写者,它在对共享资源访问之前必须先获得写者身份,写者在发现自己不需要写访问的情况下可以降级为读者。
读写信号量同时拥有的读者数不受限制,也就说可以有任意多个读者同时拥有一个读写信号量。
如果一个读写信号量当前没有被写者拥有并且也没有写者等待读者释放信号量,那么任何读者都可以成功获得该读写信号量;否则,读者必须被挂起直到写者释放该信号量。
如果一个读写信号量当前没有被读者或写者拥有并且也没有写者等待该信号量,那么一个写者可以成功获得该读写信号量,否则写者将被挂起,直到没有任何访问者。
因此,写者是排他性的,独占性的。
读写信号量有两种实现,一种是通用的,不依赖于硬件架构,因此,增加新的架构不需要重新实现它,但缺点是性能低,获得和释放读写信号量的开销大;另一种是架构相关的,因此性能高,获取和释放读写信号量的开销小,但增加新的架构需要重新实现。
在内核配置时,可以通过选项去控制使用哪一种实现。
读写信号量的相关API有:该宏声明一个读写信号量name并对其进行初始化。
该函数对读写信号量sem进行初始化。
读者调用该函数来得到读写信号量sem。
该函数会导致调用者睡眠,因此只能在进程上下文使用。
该函数类似于down_read,只是它不会导致调用者睡眠。
它尽力得到读写信号量sem,如果能够立即得到,它就得到该读写信号量,并且返回1,否则表示不能立刻得到该信号量,返回0。
因此,它也可以在中断上下文使用。
写者使用该函数来得到读写信号量sem,它也会导致调用者睡眠,因此只能在进程上下文使用。
该函数类似于down_write,只是它不会导致调用者睡眠。
该函数尽力得到读写信号量,如果能够立刻获得,就获得该读写信号量并且返回1,否则表示无法立刻获得,返回0。
它可以在中断上下文使用。
读者使用该函数释放读写信号量sem。
它与down_read或down_read_trylock配对使用。
如果down_read_trylock返回0,不需要调用up_read来释放读写信号量,因为根本就没有获得信号量。
写者调用该函数释放信号量sem。
它与down_write或down_write_trylock配对使用。
如果down_write_trylock返回0,不需要调用up_write,因为返回0表示没有获得该读写信号量。
该函数用于把写者降级为读者,这有时是必要的。
因为写者是排他性的,因此在写者保持读写信号量期间,任何读者或写者都将无法访问该读写信号量保护的共享资源,对于那些当前条件下不需要写访问的写者,降级为读者将,使得等待访问的读者能够立刻访问,从而增加了并发性,提高了效率。
读写信号量适于在读多写少的情况下使用,在linux内核中对进程的内存映像描述结构的访问就使用了读写信号量进行保护。
在Linux中,每一个进程都用一个类型为task_t或struct task_struct的结构来描述,该结构的类型为struct mm_struct的字段mm描述了进程的内存映像,特别是mm_struct结构的mmap字段维护了整个进程的内存块列表,该列表将在进程生存期间被大量地遍利或修改。