linux设备驱动中常用函数
- 格式:doc
- 大小:148.00 KB
- 文档页数:9
一般的,用户空间使用函数malloc在堆上分配内存空间,同样的,在内核空间同样有一套类似的函数来分配空间。
下面的知识会涉及页式管理的内存机制,如果不懂的要先复习一下,在S3C2440数据手册的MMU部分有介绍。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxx一、内核空间和用户空间有什么不同学c语言的时候应该学过,从用户空间看,每个进程都傻乎乎的以为自己有4G 的内存空间,其中位于高地址(3G-4G)的1G空间给内核用,另外的3G(0-3G)都是它一个人独占的。
所以用户空间很慷慨的把3G的空间分了好几个区域,如堆、栈、代码段等。
其中,malloc()分配的空间位于堆,而程序中的自动变量,如你在函数内定义的“int i”,它是放在栈上,同时。
用户空间的栈是可变栈,即随着数据的增多,对应函数的栈空间也会增多。
跟每个用户空间的进程不一样,内核只有1G的空间,同时,除了自己本身有进程运行外,内核还要允许用户空间进程调用系统调用进入内核空间去执行。
所以,内核对此相当吝啬,它规定在内核中的每个进程都只有4KB或8KB(32位下)的定长栈。
出于这样的原因,大的数据结构就不能在栈中分配,只能请求内核分配新的空间来存放数据,如函数kmalloc()。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxx二、内存的基本单位是字节吗?在介绍分配内存空间的函数前,我们还要了解一下内存是怎么被划分的。
内核不仅知道用户空间中看到的1G内核空间是假的,它还知道实际的物理内存是多少(我的开发板是64M)。
所以,内核的其中一个任务就是,当这段虚假内存中的数据需要调用时,内核把这段虚拟内存与实际的物理内存对应上,运行完后又把两段内存的对应关系撤销掉给另外的虚拟内存用。
linux核心函数Linux 内核是操作系统的核心部分,它提供了操作系统的核心功能,包括进程管理、内存管理、文件系统等。
Linux 内核的源代码中包含了大量的函数,用于实现各种操作系统的功能。
以下是一些Linux 内核中常见的核心函数,它们扮演着关键的角色:1.进程管理函数:–fork():创建一个新的进程。
–exec():在当前进程中执行一个新的程序。
–wait():等待子进程结束。
–exit():终止当前进程。
2.调度和任务管理函数:–schedule():进行进程调度。
–yield():主动让出CPU,将当前进程移动到就绪队列的末尾。
–wake_up_process():唤醒一个等待中的进程。
3.内存管理函数:–kmalloc():在内核中分配内存。
–kfree():释放内核中的内存。
–vmalloc():在虚拟地址空间中分配内存。
4.文件系统函数:–open():打开一个文件。
–read():从文件中读取数据。
–write():向文件中写入数据。
–close():关闭文件。
5.设备驱动函数:–register_chrdev():注册字符设备。
–unregister_chrdev():注销字符设备。
–request_irq():注册中断处理函数。
6.网络函数:–socket():创建套接字。
–bind():将套接字与地址绑定。
–listen():侦听传入连接请求。
–accept():接受传入的连接请求。
7.定时器和时钟函数:–timer_create():创建一个定时器。
–timer_settime():设置定时器的时间。
–gettimeofday():获取当前时间。
8.同步和互斥函数:–spin_lock():获取自旋锁。
–spin_unlock():释放自旋锁。
–mutex_lock():获取互斥锁。
–mutex_unlock():释放互斥锁。
这些函数仅仅是Linux 内核中众多函数的一小部分,Linux 内核的源代码非常庞大而复杂,包含了各种各样的功能和模块。
i2c_driver的remove函数随着电子产品的普及和发展,I2C总线技术在各种嵌入式系统中得到了广泛的应用。
I2C总线是一种串行通信协议,它可以让多个设备通过只用两根线进行通信,这种通信方式具有高效、简洁的特点,因此受到了广泛的关注和应用。
在Linux系统中,I2C驱动的开发离不开i2c_driver结构体。
i2c_driver结构体是Linux内核中用于表示I2C设备驱动的结构体,它包含了I2C设备驱动的各种信息和方法,如设备的初始化、探测、读写操作等。
在i2c_driver结构体中,remove函数是一个十分重要的函数,它负责在I2C设备被卸载时执行一些必要的清理工作,以保证系统的稳定和可靠性。
下面我们将详细介绍i2c_driver的remove 函数的相关内容。
1. remove函数的定义在i2c_driver结构体中,remove函数的定义如下所示:```cint (*remove)(struct i2c_client *client)```remove函数是一个函数指针,它指向一个以struct i2c_client*类型作为参数的函数。
在I2C设备被卸载时,内核将会根据该函数指针来调用相应的remove函数。
remove函数负责执行I2C设备驱动的卸载工作,包括取消中断、释放资源、注销设备等操作。
2. remove函数的作用remove函数在I2C设备驱动卸载时起到了非常重要的作用。
它负责完成以下工作:- 取消中断:在I2C设备被卸载之前,需要取消对应的中断,以避免在设备卸载过程中出现意外的中断响应,从而导致系统的不稳定和安全问题。
- 释放资源:在remove函数中,需要对已分配的资源进行释放,包括内存空间、I/O端口、中断请求等资源。
这样可以避免资源泄漏和冲突,保证系统的稳定性和可靠性。
- 注销设备:remove函数还需要将I2C设备从I2C总线上注销,以保证系统的正常运行。
register_chrdev函数regiter_chrdev函数是Linux内核提供的一个用于注册字符设备驱动程序的函数。
它的原型如下:```cint register_chrdev(unsigned int major, const char *name, struct file_operations *fops);```该函数会在Linux内核的字符设备表中创建一个新的字符设备,并将其与相应的驱动程序函数关联起来。
下面是对register_chrdev函数的详细解析。
1.参数说明:- `major`:表示字符设备的主设备号。
主设备号用于标识设备驱动程序,一个设备对应一个主设备号。
如果传递参数为0,内核会动态分配一个主设备号,并将其作为函数返回值。
- `name`:表示字符设备的名称。
通常以字符串的形式表示。
- `fops`:表示设备驱动程序的操作函数集。
file_operations结构体中定义了字符设备的各种操作函数,例如open、release、read、write等。
2.返回值说明:-如果返回值为负数,表示注册失败。
返回值为正数,则表示返回的主设备号。
- 当major参数为0时,返回的主设备号由内核动态分配。
3.函数功能:- register_chrdev函数的主要功能是将字符设备的操作函数和设备的主设备号关联起来,以便内核能够在用户空间请求操作设备时,找到相应的驱动程序函数进行处理。
-在函数内部,会首先获取一个未使用的主设备号,如果传入的主设备号参数为0,则由内核分配。
成功获取主设备号后,会在字符设备表中创建一个新的字符设备项。
-然后,将传入的操作函数集与该字符设备项关联起来,并将主设备号返回,以便用户空间可以使用该号码来打开、关闭、读取或写入设备。
4.注意事项:- 在使用register_chrdev函数注册字符设备驱动程序之前,需要在内核空间中编写相应的设备操作函数,并将这些函数封装到file_operations结构体中。
ioctl函数的作用及特定函数解释1. ioctl函数的定义和概述ioctl函数(Input Output Control)是Unix/Linux系统中的一个系统调用,用于对设备文件进行控制。
它提供了一种通用的机制,使应用程序能够与设备驱动程序进行交互,并对设备进行各种操作和配置。
ioctl函数的原型如下:int ioctl(int fd, unsigned long request, ...);其中,fd是设备文件的文件描述符,request是控制命令,后面的参数是根据不同的request而变化的,可以是一个值,也可以是一个指针。
2. ioctl函数的用途ioctl函数的主要用途是对设备进行控制,包括但不限于以下几个方面:•配置设备参数:通过ioctl函数可以设置设备的各种参数,如串口的波特率、数据位、校验位等。
这些参数通过request参数指定,具体的参数值通过后续的可选参数传递。
•读取设备状态:有些设备会提供一些状态信息,通过ioctl函数可以读取这些状态信息,如网卡的接收和发送数据包数量等。
•控制设备行为:通过ioctl函数可以控制设备的行为,如打开或关闭设备、启动或停止设备的某些功能等。
•传输数据:有些设备可以通过ioctl函数进行数据的传输,如USB设备可以通过ioctl函数进行数据的读写操作。
总之,ioctl函数提供了一种通用的机制,使应用程序能够与设备驱动程序进行交互,实现对设备的控制和配置。
3. ioctl函数的工作方式ioctl函数的工作方式与其他系统调用类似,主要分为以下几个步骤:1.应用程序调用ioctl函数,并传递设备文件的文件描述符、控制命令和可选参数。
2.内核根据文件描述符找到对应的设备驱动程序,并将控制命令和可选参数传递给设备驱动程序。
3.设备驱动程序根据控制命令和可选参数执行相应的操作,如配置设备参数、读取设备状态、控制设备行为等。
4.设备驱动程序将执行结果返回给内核。
linux驱动面试题Linux驱动是指在Linux操作系统中,用于控制与硬件之间的交互和通信的软件模块。
在Linux的工作环境中,驱动程序起着至关重要的作用。
如果你准备参加Linux驱动的面试,以下是一些常见的Linux驱动面试题,希望可以对你有所帮助。
一、简述Linux驱动的作用和功能。
Linux驱动是一种软件模块,用来控制硬件设备与操作系统之间的通信和交互。
它负责将输入/输出请求传递给硬件设备,并处理来自硬件设备的中断和事件。
Linux驱动的功能包括设备初始化和配置、数据传输和处理以及错误处理等。
二、请简要介绍Linux驱动程序的加载过程。
当系统启动时,Linux内核首先会加载核心模块和驱动程序模块。
驱动程序模块是以目标硬件设备为基础的,它们包含了与设备通信所需的函数和数据结构。
一般情况下,系统会根据硬件设备信息自动加载对应的驱动程序模块。
加载驱动程序模块需要通过insmod或modprobe命令进行,这些命令可以在启动时自动执行。
三、请简述Linux驱动程序的实现方式。
Linux驱动程序的实现方式包括内核空间驱动和用户空间驱动。
内核空间驱动是指驱动程序运行在内核空间,直接与硬件设备进行交互。
用户空间驱动是指驱动程序运行在用户空间,通过系统调用和内核模块实现与硬件设备的通信。
内核空间驱动的优势是性能更好,但需要对内核进行编译和加载,而用户空间驱动的优势是开发更加容易,但性能会稍差。
四、请介绍Linux驱动程序中常用的数据结构和函数。
在Linux驱动程序中,常用的数据结构有file结构体、inode结构体和cdev结构体等。
file结构体用于表示一个打开的设备文件,可以通过它传递与设备相关的信息。
inode结构体用于表示一个文件的元数据信息,包括文件的权限、大小和创建时间等。
cdev结构体用于表示一个字符设备,包含了设备文件的操作函数和设备号等信息。
常用的函数包括register_chrdev、unregister_chrdev、request_irq和release_irq等。
i2c_register_driver函数详解在嵌入式软件开发中,I2C(Inter-Integrated Circuit)总线是一种常用的串行通信接口,用于在微控制器和外部设备之间传输数据。
i2c_register_driver函数是Linux内核中一个重要的函数,用于注册I2C 驱动程序。
本文将详细解析i2c_register_driver函数的功能、参数和应用。
一、i2c_register_driver函数概述i2c_register_driver函数是在Linux内核中注册一个I2C驱动程序的函数。
它的作用是将驱动程序与对应的I2C适配器绑定,使得操作系统能够正确地识别和管理该驱动程序。
在驱动程序注册后,当相应的I2C设备连接到系统时,驱动程序将会自动加载并为该设备提供服务。
二、i2c_register_driver函数参数i2c_register_driver函数包含一个结构体参数,该结构体用于指定驱动程序的相关信息和功能。
1. struct i2c_driverstruct i2c_driver是一个定义I2C驱动程序的结构体,包含了以下重要的成员:- .driver:指向内核的struct device_driver结构体,用于描述驱动程序的信息,如名称、文件操作方法等。
- .probe:指向I2C设备探测函数的指针,用于在设备连接时进行初始化和配置。
- .remove:指向I2C设备移除函数的指针,用于在设备断开连接时进行清理和释放资源。
- .id_table:指向I2C设备ID表的指针,用于匹配设备和驱动程序。
2. I2C设备探测函数(probe函数)I2C设备探测函数是I2C驱动程序的核心功能之一,在I2C设备连接到系统时被调用。
该函数的作用是检测和初始化I2C设备,并将设备与驱动程序进行绑定。
在probe函数中,可以执行一系列必要的操作,如配置寄存器、分配内存、注册字符设备等。
register_chrdev函数register_chrdev()函数register_chrdev()函数是Linux kernel 驱动程序中,负责注册字符设备的函数。
它用于为某块设备分派一个major号和一组minor号,以此来构造一个唯一的设备号,以用于文件系统的操作,这样就可以把设备当作一个文件来操作。
1. 用法register_chrdev()函数有三个参数,分别为major号,设备名,以及file_operations指针。
(1)第一个参数:major:这是一个整型变量。
如果major被设置为0,则表示由内核自动分配一个major号;如果major被设置为某个大于等于0的整数,内核就会尝试使用该值为设备号分配major号,如果该范围的major号已经被占用,则失败。
(2)第二个参数:Name:指定该设备的名字(字符串形式),该设备将会以相应的路径/dev/name的形式挂入文件系统中。
(3)第三个参数:fops:在Linux操作系统中,所有的与设备相关的操作都是通过一个结构体file_operations规定的,在驱动注册时该结构体必须给出,以便Linux内核知晓以何种方式与真实的设备进行交互,这样就可以自动完成后台的对设备的访问初始工作,以便将设备当成一个文件一样来操作。
该参数是一个结构体file_operations指针,指向一个file_operations类型的变量。
2. 返回值当register_chrdev函数执行成功时,返回0。
失败时,返回一个负值,一般为-EINVAL。
3. 示例#include <linux/fs.h>#include <linux/module.h>int major_number; // Major number驱动程序也可能定义major号static struct file_operations fops ={.owner = THIS_MODULE,.llseek = no_llseek,.read = read,.write = write,.open = open,.unlocked_ioctl = ioctl,.release = close};static int __init device_init(void){major_number = register_chrdev(0, "my_char_device", &fops);if (major_number < 0)printk("registration error %d\n", major_number);return major_number;}4. 备注register_chrdev用于为某块设备分配一个全局的major号,尽管major 号在Linux系统中是唯一的,但是如果当模块已经卸载时还必须要调用unregister_chrdev释放这个major号,以便后面使用时,可以重新获得该major号。
以下是Linux系统下常用的C函数:
printf() -输出函数,常用于打印文本和变量值。
scanf() -输入函数,用于从键盘读取输入数据。
malloc() -内存分配函数,用于在堆上分配指定大小的内存空间。
free() -内存释放函数,用于释放先前分配的内存空间。
strcpy() -字符串复制函数,用于将一个字符串复制到另一个字符串中。
strlen() -字符串长度函数,用于计算一个字符串的长度。
strcmp() -字符串比较函数,用于比较两个字符串是否相等。
memset() -内存设置函数,用于将指定内存区域设置为指定的值。
memcpy() -内存复制函数,用于将一个内存区域的内容复制到另一个内存区域中。
fopen() -文件打开函数,用于打开一个文件以进行读写操作。
fclose() -文件关闭函数,用于关闭先前打开的文件。
fgets() -从文件中读取一行数据的函数。
fputs() -将一行数据写入文件的函数。
fprintf() -格式化输出到文件的函数,类似于printf()。
fscanf() -格式化输入从文件中读取数据的函数,类似于scanf()。
Linux2.6设备驱动常用的接口函数(一)----字符设备刚开始,学习linux驱动,觉得linux驱动很难,有字符设备,块设备,网络设备,针对每一种设备其接口函数,驱动的架构都不一样。
这么多函数,要每一个的熟悉,那可多难啦!可后来发现linux驱动有很多规律可循,驱动的基本框架都差不多,再就是一些通用的模块。
基本的架构里包括:加载,卸载,常用的读写,打开,关闭,这是那种那基本的咯。
利用这些基本的功能,当然无法实现一个系统。
比方说:当多个执行单元对资源进行访问时,会引发竞态;当执行单元获取不到资源时,它是阻塞还是非阻塞?当突然间来了中断,该怎么办?还有内存管理,异步通知。
而linux 针对这些问题提供了一系列的接口函数和模板框架。
这样,在实际驱动设计中,根据具体的要求,选择不同的模块来实现其功能需求。
觉得能熟练理解,运用这些函数,是写号linux设备驱动的第一步。
因为是设备驱动,是与最底层的设备打交道,就必须要熟悉底层设备的一些特性,例如字符设备,块设备等。
系统提供的接口函数,功能模块就像是工具,能够根据不同的底层设备的的一些特性,选择不同的工具,方能在linux驱动中游刃有余。
最后就是调试,这可是最头疼的事。
在调试过程中,总会遇到这样,那样的问题。
怎样能更快,更好的发现并解决这些问题,就是一个人的道行咯!我个人觉得:发现问题比解决问题更难!时好时坏的东西,最纠结!看得见的错误比看不见的错误好解决!一:Fops结构体中函数:①ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以-EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型). ②ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数③loff_t (*llseek) (struct file *, loff_t, int);llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示. 如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述).④int (*open) (struct inode *, struct file *);尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.⑤int (*release) (struct inode *, struct file *);在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.⑥int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表. 如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的 ioctl"), 系统调用返回一个错误.⑦int (*mmap) (struct file *, struct vm_area_struct *);mmap 用来请求将设备内存映射到进程的地址空间. 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.⑧unsigned int (*poll) (struct file *, struct poll_table_struct *); poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞. poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到 I/O 变为可能. 如果一个驱动的 poll 方法为 NULL, 设备假定为不阻塞地可读可写。
二:驱动的基本架构1:模块加载①创建设备号:MAJOR(dev_t dev):根据设备号dev获得主设备号;MINOR(dev_t dev):根据设备号dev获得次设备号;MKDEV(int major, int minor):根据主设备号major和次设备号minor构建设备号。
可以通过以下方法来创建设备号:dev_t mydev;mydev=MKDEV(50,0);我们也可以由mydev得到major 和minor number.int major,minor;major=MAJOR(mydev);minor=MINOR(mydev);dev_t类型:在内核中,dev_t类型(定义在<linux/types.h>中)用来保存设备编号——包括主设备号和次设备号。
在内核2.6.0中,dev_t是一个32位的数,其中高12位表示主设备号,低20位表示次设备号。
②申请设备号内核提供了三个函数来注册一组字符设备编号,这三个函数分别是register_chrdev_region()、alloc_chrdev_region() 。
这三个函数都会调用一个共用的 __register_chrdev_region() 函数来注册一组设备编号范围(即一个char_device_struct 结构)。
静态申请:register_chrdev_region(dev_t first,unsigned int count,char *name) First :要分配的设备编号范围的初始值(次设备号常设为0);Count:连续编号范围.Name:编号相关联的设备名称. (/proc/devices);成功时返回 0 ,失败时返回负数。
动态分配:alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char*name);*dev:存放返回的设备号;Firstminor : 通常为0;Count:连续编号范围.Name:编号相关联的设备名称. (/proc/devices);③初始化设备结构体一个cdev 一般它有两种定义初始化方式:静态的和动态的。
静态内存定义初始化:struct cdev my_cdev;cdev_init(&my_cdev, &fops);my_cdev.owner = THIS_MODULE;动态内存定义初始化:struct cdev *my_cdev = cdev_alloc();my_cdev->ops = &fops;my_cdev->owner = THIS_MODULE;初始化cdev 后,需要把它添加到系统中去。
为此可以调用cdev_add() 函数。
传入cdev 结构的指针,起始设备编号,以及设备编号范围。
cdev_add(struct cdev *p, dev_t dev, unsigned count);2:模块卸载:注销设备:cdev_del(struct cdev *p);释放设备号:unregister_chrdev_region(dev_t from, unsigned count);三:中断1:申请中断:int request_irq(unsigned int irq,void (*handler)(int irq, void *dev_id, struct pt_regs *regs), unsigned long irqflags,const char * devname,oid *dev_id );irq: 要申请的硬件中断号。
在Intel平台,范围是0~15。
handler: 向系统登记的中断处理函数。
这是一个回调函数,中断发生时,系统掉用这个函数,传入的参数包括硬件中断号,device id,寄存器值。
dev_id就是下面的request_irq时传递给系统的参数dev_id。
irqflags: 中断处理的一些属性。
比较重要的有SA_INTERRUPT,标明中断处理程序是快速处理程序(设置SA_INTERRUPT)还是慢速处理程序(不设置SA_INTERRUPT)。
快速处理程序被调用时屏蔽所有中断。
慢速处理程序不屏蔽。
还有一个 SA_SHIRQ属性,设置了以后运行多个设备共享中断。
dev_id: 中断共享时会用到。
一般设置为这个设备的device结构本身或者NULL。
中断处理程序可以用dev_id找到相应的控制这个中断的设备,或者用irq2dev_map找到中断对应的设备。
2:释放中断:void free_irq(unsigned int irq, void *dev_id);irq: 是将要注销掉的中断服务函数的中断号;dev_id: 指定与 request_irq() 函数中使用的 dev_id 值相同的值。
3:中断处理程序的架构为了在中断执行时间尽可能短和中断处理需要完成尽可能多的工作间寻找一个平衡点,linux将中断处理程序分为两个半部:顶半部和底半部。
顶半部完成尽可能少的比较紧急的任务,而底半部通常做了中断处理程序中所有工作,而且可以被新的中断打断。
尽管系统将中断处理程序分为两部分,但并可以僵化的认为中断处理程序中一定要分为上下两半部。
通常底半部机制主要有tasklet、工作队列和软中断①tasklet②工作队列中断Struct work_struct xxx_wq;void xxx_do_work(unsigned long);INIT_WORK(&xxx_wq,(void(*)(void *))xxx_do_work,NULL);定义工作队列并关联函数void xxx_do_work(unsigned long){......}中断处理函数底半部irqreturn_t xxx_interrupt(int irq, void *dev_id, struct pt_regs*regs){...schedule_work(&xxx_wq);...}int __init xxx_int(void){...result = request_irq(xxx_irq, xxx_interrupt,SA_INTERRUPT, "xxx", NULL);...}中断处理函数顶半部设备驱动模块加载函数void __exit xxx_exit(void){...free_irq(xxx_irq, xxx_interrupt);...}设备驱动模块卸载函数四:阻塞与轮询阻塞,执行单元在不能获得资源时便挂起,直到获得可操作的条件后才进行操作。