平台总线 在 Linux 内核 中,平台总线(Platform Bus) 是一个用于管理和连接”平台设备(platform device)”和”平台驱动(platform driver)”的抽象层。它充当了平台设备(platform device)和平台驱动(platform driver)之间的桥梁,负责将它们进行匹配和绑定。
它主要用于那些 不通过标准总线(如 PCI、USB、I²C、SPI)发现设备 的设备,而是直接集成在 SoC(System on Chip)或者主板上的设备 。
通过使用平台总线模型,将设备驱动和平台设备进行了分离。这样一来,我们只需编写一份通用的驱动代码即可,然后针对不同的平台设备进行配置,这就大大减少了重复编写代码的工作量,并提高了驱动代码的重用性。
当我们需要将驱动移植到不同的平台时,只需对硬件相关的部分进行适配即可,其他部分可以保持不变。
平台总线下,设备(platform_device)和驱动(platform_driver)是两个独立对象 。
设备来自 DT/板级文件/平台代码
驱动来自模块加载/编译进内核
驱动卸载不等于设备消失
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 struct platform_device { const char *name; int id; bool id_auto; struct device dev ; u64 platform_dma_mask; struct device_dma_parameters dma_parms ; u32 num_resources; struct resource *resource ; const struct platform_device_id *id_entry ; const char *driver_override; struct mfd_cell *mfd_cell ; struct pdev_archdata archdata ; };
const char *name:设备的名称,用于唯一标识设备。必须提供一个唯一的名称,以便内核能够正确识别和管理该设备。
int id:设备的 ID,可以用于区分同一种设备的不同实例。这个参数是可选的,如果不需要使用 ID 进行区分,可以将其设置为-1
struct device dev:表示平台设备对应的 struct device 结构体,用于设备的基本管理和操作。必须为该参数提供一个有效的 struct device 对象,该结构体的 release 方法必须要实现,否则在编译的时候会报错。
u32 num_resources:设备资源的数量。如果设备具有资源(如内存区域、中断等),则需要提供资源的数量。
struct resource *resource:指向设备资源的指针。如果设备具有资源,需要提供一个指向资源数组的指针,会在下个小节对该结构体进行详细的讲解。
resource结构体 1 2 3 4 5 6 7 8 9 10 struct resource { resource_size_t start; resource_size_t end; const char *name; unsigned long flags; unsigned long desc; struct resource *parent , *sibling , *child ; };
中最重要的是前四个参数,每个参数的具体介绍如下所示:
resource_size_t start:资源的起始地址。它表示资源的起始位置或者起始寄存器的地址。
resource_size_t end:资源的结束地址。它表示资源的结束位置或者结束寄存器的地址。
const char *name:资源的名称。它是一个字符串,用于标识和描述资源。
unsigned long flags:资源的标志位。它包含了一些特定的标志,用于表示资源的属性或者特征。例如,可以用标志位来指示资源的可用性、共享性、缓存属性等。flags 参数的具体取值和含义可以根据系统和驱动的需求进行定义和解释,但通常情况下,它用于表示资源的属性、特征或配置选项。下面是一些常见的标志位及其可能的含义
分类
标志位
说明
资源类型 相关标志位
IORESOURCE_IO
资源是 I/O 端口资源
IORESOURCE_MEM
资源是内存资源
IORESOURCE_REG
资源是寄存器偏移量
IORESOURCE_IRQ
资源是中断资源
IORESOURCE_DMA
资源是 DMA(直接内存访问)资源
资源属性与特征 相关标志位
IORESOURCE_PREFETCH
资源是无副作用的可预取资源
IORESOURCE_READONLY
资源只读
IORESOURCE_CACHEABLE
资源支持缓存
IORESOURCE_RANGELENGTH
资源的范围长度
IORESOURCE_SHADOWABLE
资源可被影子资源替代
IORESOURCE_SIZEALIGN
资源大小字段对齐
IORESOURCE_STARTALIGN
起始地址字段对齐
IORESOURCE_MEM_64
资源是 64 位内存资源
IORESOURCE_WINDOW
资源由桥接器转发
IORESOURCE_MUXED
资源被软件复用
IORESOURCE_SYSRAM
资源是系统 RAM(修饰符)
状态与控制 相关标志位
IORESOURCE_EXCLUSIVE
用户空间无法映射此资源
IORESOURCE_DISABLED
资源当前被禁用
IORESOURCE_UNSET
地址尚未分配给资源
IORESOURCE_AUTO
地址由系统自动分配
IORESOURCE_BUSY
驱动程序标记资源为繁忙
项目
说明
函数定义
int platform_device_register(struct platform_device *pdev);
头文件
#include <linux/platform_device.h>
参数 pdev
指向 platform_device 结构体的指针,描述要注册的平台设备,包括设备名称、资源、设备 ID 等信息。
功能
将平台设备注册到内核,使其能够参与设备资源分配和驱动匹配。
返回值
成功:返回 0;失败:返回负数错误码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #define platform_get_device_id(pdev) ((pdev)->id_entry) #define dev_is_platform(dev) ((dev)->bus == &platform_bus_type) #define to_platform_device(x) container_of((x), struct platform_device, dev) extern int platform_device_register (struct platform_device *) ;extern void platform_device_unregister (struct platform_device *) ;int platform_device_register (struct platform_device *pdev) { device_initialize(&pdev->dev); setup_pdev_dma_masks(pdev); return platform_device_add(pdev); } EXPORT_SYMBOL_GPL(platform_device_register);
device_initialize(&pdev->dev)对 pdev->dev 进行初始化。pdev->dev 是 struct platform_device 结构体中的一个成员,它表示平台设备对应的 struct device 结构体。通过调用device_initialize 函数,对 pdev->dev 进行一些基本的初始化工作,例如设置设备的引用计数、设备的类型等。
setup_pdev_dma_masks根据平台设备的架构数据来设置 pdev的架构相关数据。这个函数的具体实现可能与具体的架构相关,它主要用于在不同的架构下对平台设备进行特定的设置。
platform_device_add 函数 , 将平台设备 pdev 添加到内核中 。platform_device_add 函数会完成平台设备的添加操作,包括将设备添加到设备层级结构中、添加设备的资源等。它会返回一个 int 类型的结果,表示设备添加的结果。
platform_device_register 函数的主要作用是将 platform_device 结构体描述的平台设备注册到内核中,包括设备的初始化、添加到 platform 总线和设备层级结构、添加设备资源等操作。
通过该函数,平台设备被注册后,就能够参与设备的资源分配和驱动的匹配过程。函数的返回值可以用于判断设备注册是否成功。
项目
说明
函数定义
void platform_device_unregister(struct platform_device *pdev);
头文件
#include <linux/platform_device.h>
参数 pdev
指向要取消注册的平台设备的 platform_device 结构体指针
功能
取消注册已注册的平台设备,从内核中移除设备,并进行资源清理。
返回值
无返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void platform_device_unregister (struct platform_device *pdev) { platform_device_del(pdev); platform_device_put(pdev); } EXPORT_SYMBOL_GPL(platform_device_unregister);
platform_device_del 函数,用于将设备从 platform 总线的设备列表中移除。它会将设备从设备层级结构中移除,停止设备的资源分配和驱动的匹配。
platform_device_put 函数,用于减少对设备的引用计数。这个函数会检查设备的引用计数,如果引用计数减为零,则会释放设备结构体和相关资源。通过减少引用计数,可以确保设备在不再被使用时能够被释放。
platform_device_unregister 函数的作用是取消注册已经注册的平台设备,从内核中移除设备 。它先调用 platform_device_del 函 数 将设备从设层级结构中移除 , 然后调用platform_device_put 函数减少设备的引用计数,确保设备在不再被使用时能够被释放。
示例 platform_device的release回调函数必须实现,否则可能编译不过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <linux/module.h> #include <linux/platform_device.h> #include <linux/ioport.h> #define MEM_START_ADDR 0xFDD60000 #define MEM_END_ADDR 0xFDD60004 #define IRQ_NUMBER 101 static struct resource my_resources [] = { { .start = MEM_START_ADDR, .end = MEM_END_ADDR, .flags = IORESOURCE_MEM, }, { .start = IRQ_NUMBER, .end = IRQ_NUMBER, .flags = IORESOURCE_IRQ, }, }; static void my_platform_device_release (struct device *dev) { } static struct platform_device my_platform_device = { .name = "my_platform_device" , .id = -1 , .num_resources = ARRAY_SIZE(my_resources), .resource = my_resources, .dev.release = my_platform_device_release, }; static int __init my_platform_device_init (void ) { int ret; ret = platform_device_register(&my_platform_device); if (ret) { printk(KERN_ERR "Failed to register platform device\n" ); return ret; } printk(KERN_INFO "Platform device registered\n" ); return 0 ; } static void __exit my_platform_device_exit (void ) { platform_device_unregister(&my_platform_device); printk(KERN_INFO "Platform device unregistered\n" ); } module_init(my_platform_device_init); module_exit(my_platform_device_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("topeet" );
加载后在/sys/bus/platform/devices 目录下,可以看到我们创建的 my_platform_device 设备
1 2 3 4 5 6 7 8 9 10 11 struct platform_driver { int (*probe)(struct platform_device *); int (*remove)(struct platform_device *); void (*shutdown)(struct platform_device *); int (*suspend)(struct platform_device *, pm_message_t state); int (*resume)(struct platform_device *); struct device_driver driver ; const struct platform_device_id *id_table ; bool prevent_deferred_probe; };
probe :平台设备的探测函数指针。当系统检测到一个平台设备与该驱动程序匹配时,该函数将被调用以初始化和配置设备。
remove :平台设备的移除函数指针。当平台设备从系统中移除时,该函数将被调用以执行清理和释放资源的操作。
shutdown :平台设备的关闭函数指针。当系统关闭时,该函数将被调用以执行与平台设备相关的关闭操作。
suspend :平台设备的挂起函数指针。当系统进入挂起状态时,该函数将被调用以执行与平台设备相关的挂起操作。
resume :平台设备的恢复函数指针。当系统从挂起状态恢复时,该函数将被调用以执行与平台设备相关的恢复操作。
driver :包含了与设备驱动程序相关的通用数据,它是 struct device_driver 类型的实例。其中包括驱动程序的名称、总线类型、模块拥有者、属性组数组指针等信息,该结构体的 name参数需要与platform_device 的.name 参数相同才能匹配成功,从而进入 probe 函数 。
id_table :指向 struct platform_device_id 结构体数组的指针,用于匹配平台设备和驱动程序之间的关联关系。通过该关联关系,可以确定哪个平台设备与该驱动程序匹配,和.driver.name起到相同的作用,但是优先级高于.driver.name
prevent_deferred_probe :一个布尔值,用于确定是否阻止延迟探测。如果设置为 true,则延迟探测将被禁用。
device_driver()结构体 struct device_driver 是 设备模型(Device Model)层) 的抽象,它描述的是 驱动程序本身的身份和行为 ,例如:
驱动名称、所属总线、模块信息
设备匹配表(设备树、ACPI)
驱动操作回调:probe / remove / suspend / resume
sysfs 属性和电源管理
核心思想:它关注的是驱动和设备之间的匹配与生命周期管理 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 struct device_driver { const char *name; struct bus_type *bus ; struct module *owner ; const char *mod_name; bool suppress_bind_attrs; enum probe_type probe_type ; const struct of_device_id *of_match_table ; const struct acpi_device_id *acpi_match_table ; int (*probe) (struct device *dev); void (*sync_state)(struct device *dev); int (*remove) (struct device *dev); void (*shutdown) (struct device *dev); int (*suspend) (struct device *dev, pm_message_t state); int (*resume) (struct device *dev); const struct attribute_group **groups ; const struct attribute_group **dev_groups ; const struct dev_pm_ops *pm ; void (*coredump) (struct device *dev); struct driver_private *p ; };
项目
说明
函数定义
int platform_driver_register(struct platform_driver *driver);
头文件
#include <linux/platform_device.h>
参数 driver
指向 platform_driver 结构体的指针,描述要注册的平台驱动程序,包括属性和回调函数。
功能
将平台驱动程序注册到内核,使内核能够与特定平台设备匹配并调用相应回调函数。
返回值
成功:返回 0; 失败:返回负数错误码
1 2 3 4 #define platform_driver_register(drv) \ __platform_driver_register(drv, THIS_MODULE) extern int __platform_driver_register(struct platform_driver *, struct module *);
这个宏用于简化平台驱动程序的注册过程。它将实际的注册函数 __platform_driver_register 与当前模块(驱动程序)关联起来。宏的参数 drv 是一个指向 struct platform_driver 结构体的指针,描述了要注册的平台驱动程序的属性和回调函数。THIS_MODULE 是一个宏,用于获取当前模块的指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int __platform_driver_register(struct platform_driver *drv, struct module *owner) { drv->driver.owner = owner; drv->driver.bus = &platform_bus_type; drv->driver.probe = platform_drv_probe; drv->driver.remove = platform_drv_remove; drv->driver.shutdown = platform_drv_shutdown; return driver_register(&drv->driver); } EXPORT_SYMBOL_GPL(__platform_driver_register);
通过这些操作,__platform_driver_register 函数将平台驱动程序与内核关联起来,并确保内核能够正确识别和调用驱动程序的各种回调函数,以实现与平台设备的交互和管理。函数的返回值表示注册过程的执行状态,以便在需要时进行错误处理。
项目
说明
函数定义
int platform_driver_register(struct platform_driver *driver);
头文件
#include <linux/platform_device.h>
参数 driver
指向 platform_driver 结构体的指针,描述要注册的平台驱动程序,包括属性和回调函数。
功能
将平台驱动程序注册到内核,使内核能够与特定平台设备匹配并调用相应回调函数。
返回值
成功:返回 0;失败:返回负数错误码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 extern void platform_driver_unregister (struct platform_driver *) ;void platform_driver_unregister (struct platform_driver *drv) { driver_unregister(&drv->driver); } EXPORT_SYMBOL_GPL(platform_driver_unregister); void driver_unregister (struct device_driver *drv) { if (!drv || !drv->p) { WARN(1 , "Unexpected driver unregister!\n" ); return ; } driver_remove_groups(drv, drv->groups); bus_remove_driver(drv); } EXPORT_SYMBOL_GPL(driver_unregister);
bus_remove_driver 函数,用于从总线中移除设备驱动程序。该函数会执行以下操作:
从总线驱动程序列表中移除指定的设备驱动程序。
调用与设备驱动程序关联的 remove 回调函数(如果有定义)。
释放设备驱动程序所占用的资源和内存。
最终销毁设备驱动程序的数据结构。
通过调用 driver_unregister 函数,可以正确地注销设备驱动程序,并在注销过程中进行必要的清理工作。这样可以避免资源泄漏和其他问题。在调用该函数后,应避免继续使用已注销的设备驱动程序指针,因为该驱动程序已不再存在于内核中
在驱动程序中获取平台设备的资源信息,并根据这些信息进行后续的操作和配置
项目
说明
函数定义
struct resource *platform_get_resource(struct platform_device *pdev, unsigned int type, unsigned int num);
头文件
#include <linux/platform_device.h>
参数 pdev
指向要获取资源的平台设备(platform_device)结构体指针
参数 type
资源类型,如: IORESOURCE_MEM:内存资源, IORESOURCE_IO:I/O 资源, IORESOURCE_IRQ:中断资源
参数 num
资源索引号,用于选择设备中同类型的第 num 个资源
功能
从平台设备的资源数组中获取指定类型和索引的资源信息
返回值
成功:返回指向 struct resource 的指针;失败或资源不存在:返回 NULL
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #include <linux/module.h> #include <linux/platform_device.h> #include <linux/ioport.h> static int my_platform_driver_probe (struct platform_device *pdev) { struct resource *res_mem , *res_irq ; if (pdev->num_resources >= 2 ) { struct resource *res_mem = &pdev->resource[0 ]; struct resource *res_irq = &pdev->resource[1 ]; printk("Method 1: Memory Resource: start = 0x%llx, end = 0x%llx\n" , res_mem->start, res_mem->end); printk("Method 1: IRQ Resource: number = %lld\n" , res_irq->start); } res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0 ); if (!res_mem) { dev_err(&pdev->dev, "Failed to get memory resource\n" ); return -ENODEV; } res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0 ); if (!res_irq) { dev_err(&pdev->dev, "Failed to get IRQ resource\n" ); return -ENODEV; } printk("Method 2: Memory Resource: start = 0x%llx, end = 0x%llx\n" , res_mem->start, res_mem->end); printk("Method 2: IRQ Resource: number = %lld\n" , res_irq->start); return 0 ; } static int my_platform_driver_remove (struct platform_device *pdev) { return 0 ; } static struct platform_driver my_platform_driver = { .driver = { .name = "my_platform_device" , .owner = THIS_MODULE, }, .probe = my_platform_driver_probe, .remove = my_platform_driver_remove, }; static int __init my_platform_driver_init (void ) { int ret; ret = platform_driver_register(&my_platform_driver); if (ret) { printk("Failed to register platform driver\n" ); return ret; } printk("Platform driver registered\n" ); return 0 ; } static void __exit my_platform_driver_exit (void ) { platform_driver_unregister(&my_platform_driver); printk("Platform driver unregistered\n" ); } module_init(my_platform_driver_init); module_exit(my_platform_driver_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("topeet" );
LED灯平台总线示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 #include <linux/module.h> #include <linux/platform_device.h> #include <linux/ioport.h> #include <linux/kdev_t.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/uaccess.h> #include <linux/io.h> struct device_test { dev_t dev_num; int major ; int minor ; struct cdev cdev_test ; struct class *class ; struct device *device ; char kbuf[32 ]; unsigned int *vir_gpio_dr; }; struct device_test dev1 ;static int cdev_test_open (struct inode *inode, struct file *file) { file->private_data=&dev1; printk("This is cdev_test_open\r\n" ); return 0 ; } static ssize_t cdev_test_write (struct file *file, const char __user *buf, size_t size, loff_t *off) { struct device_test *test_dev = (struct device_test *)file->private_data; if (copy_from_user(test_dev->kbuf, buf, size) != 0 ) { printk("copy_from_user error\r\n" ); return -1 ; } if (test_dev->kbuf[0 ]==1 ){ *(test_dev->vir_gpio_dr) = 0x8000c040 ; printk("test_dev->kbuf [0] is %d\n" ,test_dev->kbuf[0 ]); } else if (test_dev->kbuf[0 ]==0 ) { *(test_dev->vir_gpio_dr) = 0x80004040 ; printk("test_dev->kbuf [0] is %d\n" ,test_dev->kbuf[0 ]); } return 0 ; } static ssize_t cdev_test_read (struct file *file, char __user *buf, size_t size, loff_t *off) { struct device_test *test_dev = (struct device_test *)file->private_data; if (copy_to_user(buf, test_dev->kbuf, strlen ( test_dev->kbuf)) != 0 ) { printk("copy_to_user error\r\n" ); return -1 ; } printk("This is cdev_test_read\r\n" ); return 0 ; } static int cdev_test_release (struct inode *inode, struct file *file) { printk("This is cdev_test_release\r\n" ); return 0 ; } struct file_operations cdev_test_fops = { .owner = THIS_MODULE, .open = cdev_test_open, .read = cdev_test_read, .write = cdev_test_write, .release = cdev_test_release, }; static int my_platform_driver_probe (struct platform_device *pdev) { struct resource *res_mem ; int ret; res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0 ); if (!res_mem) { dev_err(&pdev->dev, "Failed to get memory resource\n" ); return -ENODEV; } ret = alloc_chrdev_region(&dev1.dev_num, 0 , 1 , "alloc_name" ); if (ret < 0 ) { goto err_chrdev; } printk("alloc_chrdev_region is ok\n" ); dev1.major = MAJOR(dev1.dev_num); dev1.minor = MINOR(dev1.dev_num); printk("major is %d \r\n" , dev1.major); printk("minor is %d \r\n" , dev1.minor); dev1.cdev_test.owner = THIS_MODULE; cdev_init(&dev1.cdev_test, &cdev_test_fops); ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1 ); if (ret<0 ) { goto err_chr_add; } dev1. class = class_create(THIS_MODULE, "test" ); if (IS_ERR(dev1.class)) { ret=PTR_ERR(dev1.class); goto err_class_create; } dev1.device = device_create(dev1.class, NULL , dev1.dev_num, NULL , "test" ); if (IS_ERR(dev1.device)) { ret=PTR_ERR(dev1.device); goto err_device_create; } dev1.vir_gpio_dr=ioremap(res_mem->start,4 ); if (IS_ERR(dev1.vir_gpio_dr)) { ret=PTR_ERR(dev1.vir_gpio_dr); goto err_ioremap; } return 0 ;err_ioremap: iounmap(dev1.vir_gpio_dr); err_device_create: class_destroy(dev1.class); err_class_create: cdev_del(&dev1.cdev_test); err_chr_add: unregister_chrdev_region(dev1.dev_num, 1 ); err_chrdev: return ret; } static int my_platform_driver_remove (struct platform_device *pdev) { return 0 ; } static struct platform_driver my_platform_driver = { .driver = { .name = "my_platform_device" , .owner = THIS_MODULE, }, .probe = my_platform_driver_probe, .remove = my_platform_driver_remove, }; static int __init my_platform_driver_init (void ) { int ret; ret = platform_driver_register(&my_platform_driver); if (ret) { printk("Failed to register platform driver\n" ); return ret; } printk("Platform driver registered\n" ); return 0 ; } static void __exit my_platform_driver_exit (void ) { unregister_chrdev_region(dev1.dev_num, 1 ); cdev_del(&dev1.cdev_test); device_destroy(dev1.class, dev1.dev_num); class_destroy(dev1.class); platform_driver_unregister(&my_platform_driver); printk("Platform driver unregistered\n" ); } module_init(my_platform_driver_init); module_exit(my_platform_driver_exit); MODULE_LICENSE("GPL" ); MODULE_AUTHOR("topeet" );
.remove 是设备被移除时调用的,而不是驱动卸载时调用的。.remove 在“设备消失”时执行,不代表“驱动要卸载,所以注销字符设备逻辑必须放在 module_exit()。