18.kthread_worker:内核线程异步传输

news2024/11/23 8:25:10

目录

kthread_worker

驱动传输数据的方式

同步传输

异步传输

头文件

kthread_worker结构体

kthread_work结构体

kthread_flush_work结构体

init_kthread_worker()函数

为kthread_worker创建内核线程

init_kthread_work()函数

启动工作

刷新工作队列

停止内核线程

实验环节

dts_led.c文件

Makefile文件

执行过程

函数解析

init_kthread_worker()宏

kthread_worker_fn()函数

init_kthread_work()函数

kthread_queue_work()函数

kthread_flush_worker()函数


kthread_worker

可以通过kthread_worker结构体让内核创建一个线程,在线程里完成一件事情。

驱动传输数据的方式

同步传输

低速数据:驱动同步传输

优点:简单直接

缺点:传输效率低,同步传输会造成当前线程阻塞,影响用户空间应用程序执行效率

异步传输

高速数据:驱动交给内核来异步传输

优点:无阻塞

缺点:机制复杂、内核里的相关代码很多

头文件

#define <linux/kthread.h>

kthread_worker结构体

该结构体存放在内核/include/linux/kthread.h文件。

把内核线程抽象为流水线工人,按序处理其他线程 / 进程交付的批量工作。内核里的流水线工人可能有多个。

struct kthread_worker {
	unsigned int		flags;	
	spinlock_t		    lock;                // 自旋锁
	struct list_head	work_list;           // 不需要延时完成的工作串联,即串联kthread_work结构体
	struct list_head	delayed_work_list;   // 需要延时完成的工作串联
	struct task_struct	*task;               // 表示一个具体的进程或者线程
	struct kthread_work	*current_work;       // 指向正在处理的某一个具体的工作
};

kthread_work结构体

该结构体存放在内核/include/linux/kthread.h文件。

表示等待内核线程处理的具体工作。

struct kthread_work {
	struct list_head	    node;    // 此链表节点将串联在kthread_worker->work_list
	kthread_work_func_t	    func;    // 函数指针,为每一个具体工作指定一个具体的函数,真正负责完成具体的工作,自定义
	struct kthread_worker	*worker; // 这个具体工作属于哪个工人,内核里面可能有多个工人,指向上面node成员指向的kthread_worker

	/* Number of canceling calls that are running at the moment. */
	int			            canceling;
};

typedef void (*kthread_work_func_t)(struct kthread_work *work);

kthread_flush_work结构体

该结构体存放在内核/kernel/kthread.c文件。

表示等待某个内核进程工人处理完所有工作。

struct kthread_flush_work {
	struct kthread_work	work;    //具体内核线程工人
	struct completion	done;    //完成量,等待所有工作处理完毕
};

init_kthread_worker()函数

先定义,再初始化。

struct kthread_worker hi_worker;    // 定义一个工人
init_kthread_worker(&hi_worker);    // 初始化一个工人

为kthread_worker创建内核线程

先定义,再初始化。

#define <linux/sched.h>

/*
 * kthread_worker_fn:内核线程一直运行的函数,具体的工作就是在这个函数指针里完成。这是内核提供给我们的
 * hi_worker:已初始化的kthread_worker结构体变量
 * “nvme%d”:为内核线程设置名字
 */
struct task_struct *kworker_task;
kworker_task = kthread_run(kthread_worker_fn, &hi_worker, "nvme%d", 1);    // kworker_task 要被hi_worker中的task成员所指向

init_kthread_work()函数

先定义,再初始化。

// xxx_work_fn:处理该工作的具体函数,自定义实现,负责完成具体的工作
struct kthread_work hi_work;                  // 定义一个具体的工作变量
init_kthread_work(&hi_work, xxx_work_fn);     // 为此工作变量指定一个具体的工作函数

启动工作

交付工作给内核线程工人,注意工人手里可能有多个具体工作。

/*
 * hi_worker:具体内核线程工人,struct kthread_worker
 * hi_work:具体工作,struct kthread_work
 */
kthread_queue_work(&hi_worker, &hi_work);

刷新工作队列

刷新指定kthread_worker上所有work

// 等待此流水线工个人手里的工作完成
// hi_worker:具体内核线程工人
kthread_flush_worker(&hi_worker);

停止内核线程

停止创建的内核线程。

// struct task_struct 指针
kthread_stop(kworker_task);

实验环节

dts_led.c文件

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
 
#include <linux/string.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/platform_device.h>

#include <linux/kthread.h>
#include <linux/sched.h>

#include <asm/mach/map.h>
#include <asm/io.h>
 
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
 
#define DEV_NAME        "rgb_led"
#define DEV_CNT         (1)

int rgb_led_red;
int rgb_led_green;
int rgb_led_blue;

static dev_t led_devno;
static struct cdev led_chrdev;
struct class *class_led;
struct device *device;
struct device_node *rgb_led_device_node;

struct kthread_worker 	hi_worker;
struct kthread_work 	hi_work;
struct task_struct 		*kworker_task;

unsigned int write_data = 0;
 
static int led_chrdev_open(struct inode *inode, struct file *filp)
{
        printk("open form driver\n");
		kworker_task = kthread_run(kthread_worker_fn, &hi_worker, "nvme%d", 1);
        return 0;
}

void rgb_control(struct kthread_work *work)
{
		/* 设置GPIO1_04输出点平 */
		if(write_data & 0x04){
				gpio_set_value(rgb_led_red, 0);
		}else{
				gpio_set_value(rgb_led_red, 1);
		}
		
		/* 设置GPIO4_20输出点平 */
		if(write_data & 0x02){
				gpio_set_value(rgb_led_green, 0);
		}else{
				gpio_set_value(rgb_led_green, 1);
		}
		
		/* 设置GPIO4_19输出点平 */
		if(write_data & 0x01){
				gpio_set_value(rgb_led_blue, 0);
		}else{
				gpio_set_value(rgb_led_blue, 1);
		}
}

static ssize_t led_chrdev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
        int ret, error;
        unsigned char receive_data[10];         //用于保存接收到的数据
 
        if(cnt > 10)    cnt = 10;
 
        error = copy_from_user(receive_data, buf, cnt);
        if(error < 0)   return -1;
 
        ret = kstrtoint(receive_data, 16, &write_data);
        if(ret)         return -1;
 
        kthread_init_work(&hi_work, rgb_control);
		kthread_queue_work(&hi_worker, &hi_work);

        return cnt;
}
 
static int led_chrdev_release(struct inode *inode, struct file *filp)
{
        printk(KERN_ALERT "finished!!!\n");
 
        return 0;
}
 
static struct file_operations led_chrdev_fops = {
        .owner = THIS_MODULE,
        .open = led_chrdev_open,
        .write = led_chrdev_write,
        .release = led_chrdev_release,
};

static int led_probe(struct platform_device *pdv)
{
        int ret = -1;   //保存错误状态码
 
        printk(KERN_ALERT "match successed!\n");
 
        /* 获取rgb_led的设备树节点 */
        rgb_led_device_node = of_find_node_by_path("/rgb_led");
        if(rgb_led_device_node == NULL){
                printk(KERN_ERR "get rgb_led failed!\n");
                return -1;
        }
 
        /* 获取red led GPIO 引脚号 */
        rgb_led_red = of_get_named_gpio(rgb_led_device_node, "rgb_led_red", 0);
        if(rgb_led_red < 0){
                printk(KERN_ERR "rgb_led_red failed!\n");
                return -1;
        }
 
        /* 获取green led GPIO 引脚号 */
        rgb_led_green = of_get_named_gpio(rgb_led_device_node, "rgb_led_green", 0);
        if(rgb_led_green < 0){
                printk(KERN_ERR "rgb_led_green failed!\n");
                return -1;
        }
 
                /* 获取blue led GPIO 引脚号 */
        rgb_led_blue = of_get_named_gpio(rgb_led_device_node, "rgb_led_blue", 0);
        if(rgb_led_blue < 0){
                printk(KERN_ERR "rgb_led_blue failed!\n");
                return -1;
        }
 
        /* 设置GPIO为输出模式,并默认高电平 */
        gpio_direction_output(rgb_led_red, 1);
        gpio_direction_output(rgb_led_green, 1);
        gpio_direction_output(rgb_led_blue, 1);
 
        /* 第一步
         * 采用动态分配的方式获取设备编号,次设备号为0
         * 设备名称为rgb-leds,可通过命令cat /proc/devices查看
         * DEV_CNT为1,当前只申请一个设备编号
         */
        ret = alloc_chrdev_region(&led_devno, 0, DEV_CNT, DEV_NAME);
		if(ret < 0){
                printk("fail to alloc led_devno\n");
                goto alloc_err;
        }
 
        /* 第二步
         * 关联字符设备结构体cdev与文件操作结构体file_operations
         */
        led_chrdev.owner = THIS_MODULE;
        cdev_init(&led_chrdev, &led_chrdev_fops);
 
        /* 第三步
         * 添加设备到cdev_map哈希表中
         */
        ret = cdev_add(&led_chrdev, led_devno, DEV_CNT);
        if(ret < 0){
                printk("fail to add cdev\n");
                goto add_err;
        }
 
        /* 第四步:创建类 */
        class_led = class_create(THIS_MODULE, DEV_NAME);
 
        /* 第五步:创建设备 */
        device = device_create(class_led, NULL, led_devno, NULL, DEV_NAME);
 
        return 0;
 
alloc_err:
        return -1;
add_err:
        //添加设备失败时,需要注销设备号
        unregister_chrdev_region(led_devno, DEV_CNT);
        printk("error!\n");
}
 
static const struct of_device_id rgb_led[] = {
        {.compatible = "fire,rgb_led"},
        {/* sentinel */}
};
 
/* 定义平台设备结构体 */
struct platform_driver led_platform_driver = {
        .probe = led_probe,
        .driver = {
                .name = "rgb-leds-platform",
                .owner = THIS_MODULE,
                .of_match_table = rgb_led,
        }
};
 
static int __init led_platform_driver_init(void)
{
        int DriverState;

		kthread_init_worker(&hi_worker);
        DriverState = platform_driver_register(&led_platform_driver);
        printk(KERN_ALERT "DriverState is %d\n", DriverState);
 
        return 0;
}
 
static void __exit led_platform_driver_exit(void){
		kthread_flush_worker(&hi_worker);
		kthread_stop(kworker_task);
		
        /* 销毁设备 */
        device_destroy(class_led, led_devno);
        /* 删除设备号 */
        cdev_del(&led_chrdev);
        /* 取消注册字符设备 */
        unregister_chrdev_region(led_devno, DEV_CNT);
        /* 销毁类 */
        class_destroy(class_led);
 
        platform_driver_unregister(&led_platform_driver);
 
        printk(KERN_ALERT "led_platform_driver exit\n");
}
 
module_init(led_platform_driver_init);
module_exit(led_platform_driver_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("couvrir");
MODULE_DESCRIPTION("led module");
MODULE_ALIAS("led module");

Makefile文件

照旧

执行过程

虚拟机:

执行make和make copy。生成.ko文件。

开发板(在挂载目录下执行):

sudo insmod dts_led.ko

sudo sh -c "echo 1 > /dev/rgb_led"

sudo sh -c "echo 2 > /dev/rgb_led"

sudo sh -c "echo 4 > /dev/rgb_led"

sudo rmmod dts_led.ko

函数解析

init_kthread_worker()宏

初始化kthread_worker结构体。

#define init_kthread_worker(worker)					\
	do {								\
		static struct lock_class_key __key;			\
		__init_kthread_worker((worker), "("#worker")->lock", &__key); \
	} while (0)

void __init_kthread_worker(struct kthread_worker *worker, const char *name, struct lock_class_key *key)
{
	memset(worker, 0, sizeof(struct kthread_worker));	
	spin_lock_init(&worker->lock);                 // 初始化自旋锁
	lockdep_set_class_and_name(&worker->lock, key, name);
	
	INIT_LIST_HEAD(&worker->work_list);            // 初始化链表结点
	INIT_LIST_HEAD(&worker->delayed_work_list);    // 初始化链表结点
}

kthread_worker_fn()函数

int kthread_worker_fn(void *worker_ptr)
{
	// 指针类型转化
	struct kthread_worker *worker = worker_ptr;
	struct kthread_work *work;
	...
	// current是内核的一个全局变量,专门用来表示当前运行的进程或者线程
	// 当某个线程运行这个函数时,线程会被保存到task指针
	worker->task = current;

	if (worker->flags & KTW_FREEZABLE)
		set_freezable();

repeat:
	set_current_state(TASK_INTERRUPTIBLE);	// 设置当前线程的运行状态(当前的线程可以接收中断)
	if (kthread_should_stop()) {            // 判断当前线程是否应该停止运行
		__set_current_state(TASK_RUNNING);  // 设置当前进程线程为正在运行态
		spin_lock_irq(&worker->lock);
		worker->task = NULL;                // task原来是指向当前线程,现在要停止运行了,指针设置为指向空
		spin_unlock_irq(&worker->lock);
		return 0;
	}
	// 若线程没有结束运行,则接着执行下面的代码
	// struct kthread_work
	work = NULL;
	spin_lock_irq(&worker->lock);
	// 遍历工人的work_list成员
	if (!list_empty(&worker->work_list)) {
		// 根据链表结点获取具体工作结构体 kthread_work
		work = list_first_entry(&worker->work_list, struct kthread_work, node);
		list_del_init(&work->node);    // 将work中的链表结点从worker的链表里面删除
	}
	worker->current_work = work;
	spin_unlock_irq(&worker->lock);

	if (work) {
		__set_current_state(TASK_RUNNING);    // 设置当前线程为正在运行
		work->func(work);                     // 自己实现的那个具体函数
	} else if (!freezing(current))
		schedule();                           // 让出处理机资源

	try_to_freeze();
	cond_resched();
	// 其实是个死循环,每一次循环都会有一个链表节点对应的node被提取出来
	goto repeat;
}

bool kthread_should_stop(void)
{
	// 判断当前线程的标志位
	return test_bit(KTHREAD_SHOULD_STOP, &to_kthread(current)->flags);
}

init_kthread_work()函数

初始化kthread_work,为工作变量结构体指定一个具体的函数fn。

#define kinit_thread_work(work, fn)					\
	do {								\//初始化work成员变量
		memset((work), 0, sizeof(struct kthread_work));		\ 
		INIT_LIST_HEAD(&(work)->node);				\//初始化work中的链表节点
		(work)->func = (fn);					\//fn就是我们自定义的
	} while (0)

kthread_queue_work()函数

把具体的工作交付给worker(其实就是把work中的链表节点插入到worker对应的链表中)。

bool kthread_queue_work(struct kthread_worker *worker, struct kthread_work *work)
{
	bool ret = false;
	unsigned long flags;
	
	spin_lock_irqsave(&worker->lock, flags);        // 加锁,加锁前先关中断
	
	if (!queuing_blocked(worker, work)) {
		kthread_insert_work(worker, work, &worker->work_list);
		ret = true;
	}
	
	spin_unlock_irqrestore(&worker->lock, flags);    // 解锁,加锁后开中断
	return ret;
}

kthread_flush_worker()函数

void kthread_flush_worker(struct kthread_worker *worker)
{
	struct kthread_flush_work fwork = {
		KTHREAD_WORK_INIT(fwork.work, kthread_flush_work_fn),
		COMPLETION_INITIALIZER_ONSTACK(fwork.done),
	};
	// 两件事:work中的node加入,唤醒worker工作
	// 其实相当于在worker的链表最末尾新加了一个链表节点,这个具体工作最后被处理(就是最后执行kthread_flush_work_fn)
	kthread_queue_work(worker, &fwork.work);
	// 参数类型为 struct completion 完成量
	// 进程进入休眠状态
	// 会在 kthread_flush_work_fn 中唤醒此完成量,其实就是执行到最后kthread_work对应的函数就是这个,那么前面的都执行完毕了
	wait_for_completion(&fwork.done);
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/964563.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3D点云测量:计算三个平面的交点

文章目录 0. 测试效果1. 基本内容文章目录:3D视觉测量目录微信:dhlddxB站: Non-Stop_0. 测试效果 1. 基本内容 计算三个平面的交点需要找到满足所有三个平面方程的点。三个平面通常由它们的法向量和通过它们的点(或参数形式的方程)来定义。以下是计算三个平面的交点的一般步…

在VScode中使用sftp传输本地文件到服务器端

安装SFTP 在VScode的扩展中安装sftp 注意这里需要在你没连接服务器的状态下安装&#xff0c;即本机需要有sftp 配置传输端口 安装成功后&#xff0c;使用快捷键"ctrlshiftp",输入sftp&#xff0c;选择Config 根据自己的实际情况修改配置文件&#xff0c;主要改h…

设计模式-6--装饰者模式(Decorator Pattern)

一、什么是装饰者模式&#xff08;Decorator Pattern&#xff09; 装饰者模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许你在不修改现有对象的情况下&#xff0c;动态地将新功能附加到对象上。这种模式通过创建一个包装类&#xff0c;…

排序之交换排序

文章目录 前言一、冒泡排序1、冒泡排序基本思想2、冒泡排序的效率 二、快速排序 -- hoare版本1、快速排序基本思想2、快速排序代码实现3、为什么最左边值做key时&#xff0c;右边先走 三、快速排序 -- 挖坑法1、快速排序 -- 挖坑法基本思想2、快速排序 -- 挖坑法代码实现3、为什…

stable diffusion实践操作-随机种子seed

系列文章目录 stable diffusion实践操作 文章目录 系列文章目录前言一、seed是什么&#xff1f;二、使用步骤1.多批次随机生成多张图片2.提取图片seed3. 根据seed 再次培养4 seed使用4.1 复原别人图4.1 轻微修改 三、差异随机种子1. webUI位置2. 什么是差异随机种子3.使用差异…

找redis大key工具rdb_bigkeys

github官网 https://github.com/weiyanwei412/rdb_bigkeys 在centos下安装go [roothadoop102 rdb_bigkeys-master]# wget https://dl.google.com/go/go1.13.5.linux-amd64.tar.gz [roothadoop102 rdb_bigkeys-master]# tar -zxf go1.13.5.linux-amd64.tar.gz -C /usr/local将g…

安装bpftrace和bcc的踩坑记录

最后在Ubuntu22.04使用Ubuntu提供的安装命令完成了安装。这里是记录尝试在Ubuntu18.04和Ubuntu22.04使用源码安装未果的过程。 文章目录 22版本安装bcc准备工具安装命令使用报错&#xff1a;iovisor封装的安装方式ubuntu的安装方式 For Bionic (18.04 LTS)官方提供的源码安装准…

SpringCloudGateway集成SpringDoc

SpringCloudGateway集成SpringDoc 最近在搞Spring版本升级&#xff0c;按客户要求升级Spring版本&#xff0c;原来用着SpringBoot 2.2.X版本&#xff0c;只需要升级SpringBoot 2.X最新版本也就可以满足客户Spring版本安全要求&#xff0c;可是好像最新的SpringBoot 2.X貌似也不…

Laravel chunk和chunkById的坑

在编写定时任务脚本的时候&#xff0c;经常会用到chunk和chunkById的API。 一、前言 数据库引擎为innodb。 表结构简述&#xff0c;只列出了本文用到的字段。 字段类型注释idint(11)IDtypeint(11)类型mark_timeint(10)标注时间&#xff08;时间戳&#xff09; 索引&#x…

手撕 视觉slam14讲 ch13 代码(1)工程框架与代码结构

在学习slam一年之后开始&#xff0c;开始自己理思路&#xff0c;全手敲完成ch13的整个代码 我们按照自己写系统的思路进行&#xff0c;首先确定好SLAM整体系统的流程&#xff0c;见下图&#xff0c;输入为双目图像&#xff0c;之后进入前端位姿估计和后端优化&#xff0c;中间…

滑动窗口实例3(最大连续1的个数Ⅲ)

题目&#xff1a; 给定一个二进制数组 nums 和一个整数 k&#xff0c;如果可以翻转最多 k 个 0 &#xff0c;则返回 数组中连续 1 的最大个数 。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,1,0,0,0,1,1,1,1,0], K 2 输出&#xff1a;6 解释&#xff1a;[1,1,1,0,0,1,1…

stable diffusion实践操作-宽高设置以及高清修复

系列文章目录 stable diffusion实践操作 文章目录 系列文章目录前言一、SD宽高怎么设置&#xff1f;1.1 宽高历史 二、高清修复总结 前言 主要介绍SD的宽高设置以及高清修复 一、SD宽高怎么设置&#xff1f; 1.1 宽高历史 SD生成256256图片效果最好。512512是SD一直使用的画…

【管理运筹学】第 7 章 | 图与网络分析(1,图论背景以及基本概念、术语)

文章目录 引言一、图与网络的基本知识1.1 图与网络的基本概念1.1.1 图的定义1.1.2 图中相关术语1.1.3 一些特殊图类1.1.4 图的运算 写在最后 引言 按照正常进度应该学习动态规划了&#xff0c;但我想换换口味&#xff0c;而且动态规划听说也有一定难度&#xff0c;还不一定会考…

设计模式—简单工厂

目录 一、前言 二、简单工厂模式 1、计算器例子 2、优化后版本 3、结合面向对象进行优化&#xff08;封装&#xff09; 3.1、Operation运算类 3.2、客户端 4、利用面向对象三大特性&#xff08;继承和多态&#xff09; 4.1、Operation类 4.2、加法类 4.3、减法类 4…

VTK——使用ICP算法进行模型配准

ICP算法 迭代最近点&#xff08;Iterative Closest Point&#xff0c;ICP&#xff09;算法是一种用于两个三维形状之间几何对齐&#xff08;也叫做配准&#xff09;的计算方法。通常&#xff0c;这两个形状至少有一个是点云数据。ICP算法用于最小化源点云与目标点云之间点到点…

【设计模式】Head First 设计模式——构建器模式 C++实现

设计模式最大的作用就是在变化和稳定中间寻找隔离点&#xff0c;然后分离它们&#xff0c;从而管理变化。将变化像小兔子一样关到笼子里&#xff0c;让它在笼子里随便跳&#xff0c;而不至于跳出来把你整个房间给污染掉。 设计思想 ​ 将一个复杂对象的构建与其表示相分离&…

【两个有序数组合并】

问题描述: 给定两个有序整数数组 A 和 B&#xff0c;将B合并到A中&#xff0c;使得 A 成为一个有序数组。 说明: 初始化 A 和 B 的元素数量分别为 m 和 n。A有足够的空间&#xff08;空间大小大于或等于 m n&#xff09;来保存 B 中的元素。默认升序。 输入输出描述&#xf…

大数据组件-Flume集群环境的启动与验证

&#x1f947;&#x1f947;【大数据学习记录篇】-持续更新中~&#x1f947;&#x1f947; 个人主页&#xff1a;beixi 本文章收录于专栏&#xff08;点击传送&#xff09;&#xff1a;【大数据学习】 &#x1f493;&#x1f493;持续更新中&#xff0c;感谢各位前辈朋友们支持…

《YOLOv5:从入门到实战》专栏介绍 专栏目录

&#x1f31f;YOLOv5&#xff1a;从入门到实战 | 目录 | 使用教程&#x1f31f; 本专栏涵盖了丰富的YOLOv5算法从入门到实战系列教程&#xff0c;专为学习YOLOv5的同学而设计&#xff0c;堪称全网最详细的教程&#xff01;该专栏从YOLOv5基础知识入门到项目应用实战都提供了详细…

JavaWeb_LeadNews_Day10-Xxljob, Redis实现定时热文章

JavaWeb_LeadNews_Day10-Xxljob, Redis实现定时热文章 xxl-job概述windows部署调度中心docker部署调度中心 xxl-job入门案例xxl-job分片广播热点文章定时计算思路分析具体实现热文章计算定时计算 查询文章接口改造来源Gitee xxl-job概述 windows部署调度中心 运行 xxl-job\do…