九、(正点原子)Linux定时器

一、Linux中断简介

        1、中断号

        每个中断都有一个中断号,通过中断号即可区分不同的中断,有的资料也把中断号叫做中断线。在 Linux 内核中使用一个 int 变量表示中断号。在Linux中,我们可以使用已经编写好的API函数来申请中断号,定义在include/linux/interrupt.h里面。

        ①、设备树中断信息节点

        如果使用设备树的话就需要在设备树中设置好中断属性信息, Linux 内核通过读取设备树
中 的 中断 属性 信息 来配 置中 断。 对于 中断 控制 器 而言 ,设备树绑定信息参考文档Documentation/devicetree/bindings/arm/gic.txt。打开 imx6ull.dtsi 文件,其中的 intc 节点就是I.MX6ULL 的中断控制器节点,节点内容如下所示:

intc: interrupt-controller@00a01000 {
    compatible = "arm,cortex-a7-gic";
    #interrupt-cells = <3>;
    interrupt-controller;
    reg = <0x00a01000 0x1000>,
          <0x00a02000 0x100>;
};

         Documentation/devicetree/bindings/arm/gic.txt这个文档里面说:

         compatible表示GIC中断控制器对应的型号,我们使用imx6ull属于cortex-A7系列。

        #interrupt-cells#address-cells #size-cells 一样。表示此中断控制器下设备的 cells大小,对于设备而言,会使用 interrupts 属性描述中断信息, #interrupt-cells 描述了 interrupts 属性的 cells 大小,也就是一条信息有几个 cells。每个 cells 都是 32 位整形值,对于 ARM 处理的GIC 来说,一共有 3 个 cells,这三个 cells 的含义如下:

        第一个 cells:中断类型, 0 表示 SPI(共享) 中断, 1 表示 PPI (私有)中断。
        第二个 cells:中断号,对于 SPI 中断来说中断号的范围为 0~987,对于 PPI 中断来说中断
号的范围为 0~15。
        第三个 cells:标志, bit[3:0]表示中断触发类型,为 1 的时候表示上升沿触发,为 2 的时候
表示下降沿触发,为 4 的时候表示高电平触发,为 8 的时候表示低电平触发。 bit[15:8]为 PPI
断的 CPU 掩码。
        第 4 行, interrupt-controller 节点为空,表示当前节点是中断控制器。

        举个例子,比如GPIO,GPIO有可以作为一个中断控制器:

gpio5: gpio@020ac000 {
    compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
    reg = <0x020ac000 0x4000>;
    interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
                 <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
};

        interrupts有三个参数,第一个表示中断类型,第二个表示中断号,可以查看参考手册。第三个表示中断触发方式。

        interrupt-controller 表明了 gpio5 节点也是个中断控制器,用于控制 gpio5 所有 IO的中断。
        将#interrupt-cells 修改为 2,我们可以打开gpio5的一个子节点看一下:

fxls8471@1e {
    compatible = "fsl,fxls8471";
    reg = <0x1e>;
    position = <0>;
    interrupt-parent = <&gpio5>;
    interrupts = <0 8>;
};

        可以看到因为interrups属性只有两个值,所以在它的父节点的#interrupt-cells就为2.

注:我们使用设备树添加中断信息时候,其实我们只需要添加interrupt-parent,指定父中断,也就是中断控制器h和interrupts,指定中断号,触发方式,这两个属性。


        ②、获取设备树中节点的中断号

        编写驱动的时候需要用到中断号,我们用到中断号,中断信息已经写到了设备树里面,因此可以通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号,定义在:linux/of_irq.h中函数原型如下:

extern unsigned int irq_of_parse_and_map(struct device_node *node, int index);

        node:设备节点。

        index:索引号。

        返回值:中断号。 

        如果我们使用中断的引脚属于GPIO中断,我们可以使用gpio_to_irq 函数来获取 gpio 对应的中断号,定义在include/asm/gpio.h下函数原型如下:

static inline int __gpio_to_irq(unsigned gpio)

        gpio:要获取中断号的gpio编号。

        返回值:gpio对于的中断号。 


 

        ③、request_irq函数

        在 Linux 内核中要想使用某个中断是需要申请的, request_irq 函数用于申请中断, request_irq函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 request_irq 函数。 request_irq 函数会激活(使能)中断,所以不需要我们手动去使能中断,request_irq 函数原型如下:

static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler,                     
                                           unsigned long flags,const char *name, void *dev)

        irq:要申请中断的中断号。

        handler:中断处理函数,当中断发生以后就会执行此中断处理函数。函数原型:

typedef irqreturn_t (*irq_handler_t)(int irq, void *dev);

        (irq):中断处理函数的中断号。

        (dev):第二个参数是一个指向 void 的指针,也就是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备,dev 也可以指向设备数据结构。

        (返回值):一般中断服务函数返回值使用如下形式:

return IRQ_RETVAL(IRQ_HANDLED)

        flags:中断标志,可以在在文件 include/linux/interrupt.h 里面查看所有的中断标志,此标志可以使用“|”命令来实现多种组和,这里举例常用的中断标志。

#define IRQF_SHARED                 0x00000080    /*多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话, request_irq 函数的 dev 参数就是唯一区分他们的标志。 */

#define IRQF_TRIGGER_NONE	        0x00000000    /* 无触发 */
#define IRQF_TRIGGER_RISING	        0x00000001    /* 上升沿触发 */
#define IRQF_TRIGGER_FALLING	    0x00000002    /* 下降沿触发 */
#define IRQF_TRIGGER_HIGH	        0x00000004    /* 高电平触发 */
#define IRQF_TRIGGER_LOW	        0x00000008    /* 低电平触发 */
#define IRQF_TRIGGER_MASK	    (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
				                 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)

        name:中断的名字,设置申请后可以在/proc/interrupts文件看到对应的中断名字。使用命令cat /proc/interrupts查看

        dev:如果flags设置为IRQF_SHARED,使用dev来区分不同的中断,一般情况下,将dev设置为设备结构体,dev会传递给中断处理函数的第二个参数。

        返回值:0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了。

        ④、free_irq函数

        使用中断的时候需要通过 request_irq 函数申请,使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会删除中断处理函数并且禁止中断。 free_irq函数原型如下所示(定义在include/linux/interrupt.h):

extern void free_irq(unsigned int irq, void *dev);

         irq:释放的中断号。

        dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。

        ⑤、中断使能与禁止函数

        常用的中断使能和禁止函数为(定义在include/linux/interrupt.h):

/* 定义在linux/interrupt.h */
extern void disable_irq_nosync(unsigned int irq);
extern void disable_irq(unsigned int irq);
extern void enable_irq(unsigned int irq);


/* 定义在linux/irqflags */
#define local_irq_enable()	        do { raw_local_irq_enable(); } while (0)
#define local_irq_disable()	        do { raw_local_irq_disable(); } while (0)

#define local_irq_save(flags)	    do {raw_local_irq_save(flags);} while (0)
#define local_irq_restore(flags)    do { raw_local_irq_restore(flags); } while (0)

        enable_irqdisable_irq 用于使能和禁止指定的中断。disable_irq要等到当前正在执行的中断处理函数执行完才返回,如果我们希望使用时候不需要等待中断处理函数已经全部退出。在这种情况下我们使用disable_irq_nosync函数。

        disable_irq_nosync 函数调用以后立即返回,不会等待当前中断处理程序执行完毕。

        local_irq_enablelocal_irq_disable函数关闭全局中断。

        如果现在有两个任务A和B,A任务关闭全局中断10s,2s以后B任务关闭全局中断3s,然后打开全局中断。这时候B任务打开全局中断才过去5sA任务关闭10s全局中断还没到,A任务就会崩溃。为了解决这个问题, B 任务不能直接简单粗暴的通过 local_irq_enable 函数来打开全局中断,而是将中断状态恢复到以前的状态,要考虑到别的任务的感受,此时就要用到下面两个函数:

        local_irq_savelocal_irq_restore函数,关闭全局中断时会先将中断的状态保存起来,打开全局中断将以前保存的中断的状态在恢复。

        ⑥、中断示例代码:

第一步:修改Linux内核设备树信息,添加中断信息。

/* 在设备树文件中 */
节点{

    interrupt-parent = <父节点中断>;
    interrups = <硬件对应的gpio或者其他中断引脚号  中断的触发方式 >
};

第二步:从设备树中获取中断号并向Linux申请中断号
    
/* 初始化文件中 */
    int irqnum = irq_of_parse_and_map(设备树节点, 索引如果属性只有一个就为0);
    request_irq(request_irq(unsigned int irq, irq_handler_t handler,                     
                            unsigned long flags,const char *name, void *dev);

第三步:建立中断处理函数
类型:irqreturn_t (*irq_handler_t)(int irq, void *dev);

第四步:删除中断号
free_irq(unsigned int irq, void *dev);

        2、上半部与下半部

        中断号申请成功后,执行中断处理函数。我们都知道在中断中,必须快进快出,不能过多耽误。如果在中断中调用一些函数或者执行语句耽误的时间很多,那么效率就很低下,所以Linux内核引入上半部和下半部,上半部里面处理一些耗时少的,下半部处理耗时多的。

        ①、简介

        上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。
        下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。
        因此, Linux 内核将中断分为上半部和下半部的主要目的就是实现中断处理函数的快进出,那些对时间敏感、执行速度快的操作可以放到中断处理函数中,也就是上半部。剩下的所有工作都可以放到下半部去执行,至于哪些代码属于上半部,哪些代码属于下半部并没有明确的规定,一切根据实际使用情况去判断,这里有一些可以借鉴的参考点:

  •      如果要处理的内容不希望被其他中断打断,那么可以放到上半部
  •      如果要处理的任务与硬件有关,可以放到上半部
  •      如果要处理的任务对时间敏感,可以放到上半部

     上半部处理很简单,直接编写中断处理函数就行了,关键是下半部该怎么做呢?

        ②、处理下半部 

  •         软中断

        一开始Linux 内核提供了“bottom half”机制来实现下半部,简称“BH”。后面引入了软中断和 tasklet 来替代“BH”机制,完全可以使用软中断和 tasklet 来替代 BH,从 2.5 版本的 Linux内核开始 BH 已经被抛弃了。 Linux 内核使用结构体 softirq_action 表示软中断, softirq_action结构体定义在文件 include/linux/interrupt.h 中,内容如下:

struct softirq_action
{
	void	(*action)(struct softirq_action *);
};

        在kernel/softirq.c中定义了10个软中断 :

static struct softirq_action softirq_vec[NR_SOFTIRQS]

NR_SOFTIRQS:
enum
{
	HI_SOFTIRQ=0,               /* 高优先级软中断 */
	TIMER_SOFTIRQ,              /* 定时器软中断 */
	NET_TX_SOFTIRQ,             /* 网络数据发送软中断 */
	NET_RX_SOFTIRQ,             /* 网络数据接收软中断 */
	BLOCK_SOFTIRQ,
	BLOCK_IOPOLL_SOFTIRQ,
	TASKLET_SOFTIRQ,            /* tasklet 软中断 */
	SCHED_SOFTIRQ,              /* 调度软中断 */
	HRTIMER_SOFTIRQ,            /* 高精度定时器软中断 */
	RCU_SOFTIRQ,                /* RCU 软中断 */

	NR_SOFTIRQS
};

        softirq_action 结构体中的 action 成员变量就是软中断的服务函数,数组 softirq_vec 是个全局数组,因此所有的 CPU(对于 SMP 系统而言)都可以访问到,每个 CPU 都有自己的触发和控制机制,并且只执行自己所触发的软中断。但是各个 CPU 所执行的软中断服务函数确是相同的,都是数组 softirq_vec 中定义的 action 函数。要使用软中断,必须先使用 open_softirq 函数注册对应的软中断处理函数, open_softirq 函数原型如下:

extern void open_softirq(int nr, void (*action)(struct softirq_action *));

         nr:要开启的软中断,在kernel/softirq.c中定义了10个软中断。

        action:软中断对应的中断服务函数。

  注:软中断必须在编译的时候静态注册!Linux 内核使用softirq_init函数,来初始化软中断,函数原型: 

void __init softirq_init(void)
{
	int cpu;

	for_each_possible_cpu(cpu) {
		per_cpu(tasklet_vec, cpu).tail =
			&per_cpu(tasklet_vec, cpu).head;
		per_cpu(tasklet_hi_vec, cpu).tail =
			&per_cpu(tasklet_hi_vec, cpu).head;
	}

	open_softirq(TASKLET_SOFTIRQ, tasklet_action);
	open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

        可以看到会默认打开tasklet软中断和高精度定时器软中断。 

        注册号软中断后,还需要开启软中断,也叫作触发软中断,使用raise_softirq函数,函数原型如下(定义在kernel/softirq.c):

extern void raise_softirq(unsigned int nr);

        nr:要触发的软中断。

  •         tasklet

         tasklet 是利用软中断来实现的另外一种下半部机制,在软中断tasklet 之间,建议大家使用 tasklet。 Linux 内核使用 tasklet_struct 结构体来表示 tasklet(定义在linux/interrupt.h):

struct tasklet_struct
{
	struct tasklet_struct *next;        /* 下一个 tasklet */
	unsigned long state;                /* tasklet 状态 */
	atomic_t count;                     /* 计数器,记录对 tasklet 的引用次数 */
	void (*func)(unsigned long);        /* tasklet 执行的函数,用户定义 */
	unsigned long data;                 /* 函数 func 的参数 */
};

         要使用tasklet来处理下半部,先定义一个tasklet,一般定义tasklet都是在设备结构体中定义。然后使用 tasklet_init 函数初始化 tasklettaskled_init 函数原型如下:

extern void tasklet_init(struct tasklet_struct *t,
			             void (*func)(unsigned long), unsigned long data);

         t:要初始化的tasklet

        func:tasklet处理函数。

        data:传递给func函数的参数。

        也可以使用宏DECLARE_TASKLET来一次性完成tasklet的定义和初始化。定义如下:

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

        name:要定义的tasklet的名字。

        func:tasklet处理函数。

        data: 传递给func函数的参数。

        注:tasklet的func处理函数要能够执行,必须在上半部,也就是在中断处理函数中调用tasklet_schedule函数,就能使 tasklet 在合适的时间运行, tasklet_schedule 函数原型如下:

static inline void tasklet_schedule(struct tasklet_struct *t)

        t:要调度的tasklet,也就是tasklet的名字。 

        ③、使用tasklet的示例代码:

第一步:先定义一个tasklet并初始化tasklet。
第二部:在中断处理函数中调度tsklet。
第三步:tasklet函数处理。
第四步:删除tasklet。


/* 定义 taselet */
struct tasklet_struct testtasklet;
/* tasklet 处理函数 */
void testtasklet_func(unsigned long data)
{
/* tasklet 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
......
/* 调度 tasklet */
tasklet_schedule(&testtasklet);
......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
......
/* 初始化 tasklet */
tasklet_init(&testtasklet, testtasklet_func, data);
/* 注册中断处理函数 */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
......
}
  • 工作队列

         工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet。

        Linux 内核使用 work_struct 结构体表示一个工作,定义在(linux/workqueue.h)内容如下:

struct work_struct {
	atomic_long_t data;
	struct list_head entry;
	work_func_t func;            /* 工作队列处理函数 */
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};

         这些工作组成工作队列,工作队列使用 workqueue_struct 结构体表示,内容如下:

struct workqueue_struct {
	struct list_head	pwqs;		/* WR: all pwqs of this wq */
	struct list_head	list;		/* PR: list of all workqueues */

	struct mutex		mutex;		/* protects this wq */
	int			work_color;	/* WQ: current work color */
	int			flush_color;	/* WQ: current flush color */
	atomic_t		nr_pwqs_to_flush; /* flush in progress */
	struct wq_flusher	*first_flusher;	/* WQ: first flusher */
	struct list_head	flusher_queue;	/* WQ: flush waiters */
	struct list_head	flusher_overflow; /* WQ: flush overflow list */

	struct list_head	maydays;	/* MD: pwqs requesting rescue */
	struct worker		*rescuer;	/* I: rescue worker */

	int			nr_drainers;	/* WQ: drain in progress */
	int			saved_max_active; /* WQ: saved pwq max_active */

	struct workqueue_attrs	*unbound_attrs;	/* WQ: only for unbound wqs */
	struct pool_workqueue	*dfl_pwq;	/* WQ: only for unbound wqs */

#ifdef CONFIG_SYSFS
	struct wq_device	*wq_dev;	/* I: for sysfs interface */
#endif
#ifdef CONFIG_LOCKDEP
	struct lockdep_map	lockdep_map;
#endif
	char			name[WQ_NAME_LEN]; /* I: workqueue name */

	struct rcu_head		rcu;

	unsigned int		flags ____cacheline_aligned; /* WQ: WQ_* flags */
	struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */
	struct pool_workqueue __rcu *numa_pwq_tbl[]; /* FR: unbound pwqs indexed by node */
};

        Linux 内核使用工作者线程(worker thread)来处理工作队列中的各个工作, Linux 内核使用
worker 结构体表示工作者线程, worker 结构体内容如下:

struct worker {
	/* on idle list while idle, on busy hash table while busy */
	union {
		struct list_head	entry;	/* L: while idle */
		struct hlist_node	hentry;	/* L: while busy */
	};

	struct work_struct	*current_work;	/* L: work being processed */
	work_func_t		current_func;	/* L: current_work's fn */
	struct pool_workqueue	*current_pwq; /* L: current_work's pwq */
	bool			desc_valid;	/* ->desc is valid */
	struct list_head	scheduled;	/* L: scheduled works */

	/* 64 bytes boundary on 64bit, 32 on 32bit */

	struct task_struct	*task;		/* I: worker task */
	struct worker_pool	*pool;		/* I: the associated pool */
						/* L: for rescuers */
	struct list_head	node;		/* A: anchored at pool->workers */
						/* A: runs through worker->node */

	unsigned long		last_active;	/* L: last active timestamp */
	unsigned int		flags;		/* X: flags */
	int			id;		/* I: worker id */


	char			desc[WORKER_DESC_LEN];

	struct workqueue_struct	*rescue_wq;	/* I: the workqueue to rescue */
};

        注:每个 worker 都有一个工作队列,工作者线程处理自己工作队列中的所有工作。在实际的驱动开发中,我们只需要定义工作(work_struct)即可。

        简单创建工作很简单,直接定义一个 work_struct 结构体变量即可,然后使用 INIT_WORK 宏来初始化工作, INIT_WORK (定义在linux/workqueue.h宏定义如下:

#define INIT_WORK(_work, _func)		__INIT_WORK((_work), (_func), 0)

         _work:要初始化的工作。

        _func:工作的内容,也就是处理函数。

        也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化,宏定义如下:

#define DECLARE_WORK(n, f)			struct work_struct n = __WORK_INITIALIZER(n, f)

        n:定义的工作。

        f:表示工作的内容,也就是对应的处理函数。 

        定义和初始化完工作,也就是work_struct结构体变量定义与初始化。需要在中断中调度才能开始工作运行,使用函数schedule_work,函数原型:

static inline bool schedule_work(struct work_struct *work)

        work:要调度的工作。

        返回值:0,成功。其他值失败。 

        ④、使用工作队列的示例

第一步:定义和初始化工作。
第二步:在中断处理函数中调度工作。
第三步:定义工作的内容,也就是编写work的处理函数。
第四步:删除工作。


/* 定义工作(work) */
struct work_struct testwork;

/* work 处理函数 */
void testwork_func_t(struct work_struct *work);
{
    /* work 具体处理内容 */
}

/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{

    /* 调度 work */
    schedule_work(&testwork);

}

/* 驱动入口函数 */
static int __init xxxx_init(void)
{

    /* 初始化 work */
    INIT_WORK(&testwork, testwork_func_t);

    /* 注册中断处理函数 */
    request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微服务中不同服务使用openfeign 相互调用

首先 我们上文 已经知道了 nacos 的注册服务&#xff0c;现在 我们 在不同服务中相互调用就可以使用openfeign 直接调用&#xff0c;而不是 再写冗余的调用代码啦 首先 我们的微服务组件如下 因为我这个微服务是我在 员工登录demo 中 拆出来的&#xff0c;在userlogin模块中…

基于4G工业路由器的连锁品牌店铺组网监测

基于4G工业路由器的连锁品牌店铺组网监测是智慧城市建设中至关重要的任务&#xff0c;它涉及到营运管理等多方面&#xff0c;应用物联网技术可确保店铺运营的高效、安全和可靠。 连锁品牌店铺遍布城市各领域&#xff0c;甚至跨城市部署&#xff0c;分布广泛。这对集团总部的管…

HTTP/2 头部压缩 Header Compress(HPACK)详解

文章目录 1. HPACK 的工作原理1.1 静态表1.2 动态表 2. 压缩过程2.1 编码过程2.2 解码过程 3. HPACK 的优势 在HTTP1.0中&#xff0c;我们使用文本的形式传输header&#xff0c;在header中携带cookie的话&#xff0c;每次都需要重复传输几百到几千的字节&#xff0c;这着实是一…

JavaWeb——MySQL:navicate客户端工具简单使用

目录 1. 连接 2. 新建数据库 3. 使用数据库 4. 新建表 5.使用表 6. 导出数据库 我这里是英文版&#xff0c;没有进行汉化。 1. 连接 点击左上角Connection&#xff0c;选择MySQL&#xff0c;&#xff08;我连接的是自己计算机上的数据库&#xff09;连接名输入&#x…

F5《企业DNS建设白皮书》中的DNS解析服务器最佳实践

在这个数字化转型加速的时代&#xff0c;DNS&#xff08;域名系统&#xff09;的重要性不言而喻。每一次重大事件都凸显了DNS的可靠性和安全性问题。对企业而言&#xff0c;它不仅关系到业务连续性&#xff0c;更是提供永续数字服务的关键。本文根据F5公司发布的《企业DNS建设白…

Mybatis-Plus学习|快速入门CRUD、主键生成策略(雪花算法、主键自增等)、自动填充、乐观锁、分页插件、逻辑删除

MyBatisPlus概述 为什么要学习它呢?MyBatisPlus可以节省我们大量工作时间&#xff0c;所有的CRUD代码它都可以自动化完成! JPA、tk-mapper、MyBatisPlus 偷懒的! MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff…

基于JSP的列车票务信息管理系统

开头语&#xff1a; 你好&#xff0c;我是专注于计算机科学与技术研究的学长。如果你对列车票务信息管理系统感兴趣或有相关需求&#xff0c;欢迎联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSP技术 工具&#xff1a;IDE、数据库管理工具…

spring boot jar 启动报错 Zip64 archives are not supported

spring boot jar 启动报错 Zip64 archives are not supported 原因、解决方案问题为什么 spring boot 不支持 zip64zip、zip64 功能上的区别zip 的文件格式spring-boot-loader 是如何判断是否是 zip64 的&#xff1f; 参考 spring boot 版本是 2.1.8.RELEASE&#xff0c;引入以…

HBDNY-40/1端子排电压继电器 DC110V 导轨安装 约瑟JOSEF

HBDNY系列端子排型电压电流继电器 系列型号&#xff1a;&#xff08;3、4过/低电压型&#xff0c;5、6过/低电流型&#xff09; HBDNY-30/1端子排型电压继电器&#xff1b;HBDNY-30/2端子排型电压继电器&#xff1b; HBDNY-30/3端子排型电压继电器&#xff1b;HBDNY-30/4端子…

摩柏BI,轻松实现word报告自动更新

|| 导语 告别手工&#xff01;在数字化办公的今天&#xff0c;高效、准确的数据处理能力已经成为职场人士必备的技能之一。尤其是对于财务分析师、市场研究员和管理人员&#xff0c;他们需要处理的报告不仅数量庞大&#xff0c;而且对数据的准确性和实时性要求极高。传统WORD报…

python爬虫需要什么HTTP代理?

用来爬虫的话&#xff0c;还是建议用高匿名代理&#xff0c;但显然题主用了高匿名代理还是出现了一部分问题&#xff0c;我们可以先找到问题关键再解决它&#xff0c;一般爬虫用了高匿名代理出现被封会有以下几种原因&#xff1a; 1.代理IP的质量不过关 一般来说每个网站都有…

解锁小红书新玩法:中小企业出海营销的集成策略

随着全球数字化浪潮的推进&#xff0c;小红书作为生活方式分享平台的崛起&#xff0c;为中小企业提供了一个全新的营销舞台。NetFarmer&#xff0c;作为专注于企业数字化出海的服务商&#xff0c;深谙小红书的营销策略&#xff0c;并致力于通过HubSpot产品销售与实施&#xff0…

HarmonyOS应用开发学习经验

一、HarmonyOS学习官网 开发者能力认证 HarmonyOS应用开发者基础认证6月之前的学习资源官网已经关闭过期&#xff0c;大家不要慌&#xff0c;官方更新了最新资源&#xff0c;但是&#xff0c;对于之前没有学习完的学员不友好&#xff0c;存在知识断片的现象&#xff0c;建议官…

微型导轨:手术机器人的高精度“骨骼”

微型导轨精度高&#xff0c;摩擦系数小&#xff0c;自重轻&#xff0c;结构紧凑&#xff0c;被广泛应用在医疗器械中&#xff0c;尤其是在手术机器人中的应用&#xff0c;通过手术机器人&#xff0c;外科医生可以远离手术台操纵机器人进行手术。可以说&#xff0c;是当之无愧的…

C语言实现:贪心算法

算法基础原理 贪心算法是一种在求解问题时&#xff0c;总是做出在当前看来是最好的选择的算法。它不从整体最优上进行考虑&#xff0c;而是通过每一步的局部最优选择&#xff0c;希望达到全局的最优解. 贪心算法的特点:贪心算法在每一步都选择当前状态下的最优解&#xff0c;即…

SSH的基本使用

文章目录 1. SSH使用介绍2. 如何配置OpenSSH Client和OpenSSH Server2.1 Windows系统配置2.2 Linux系统配置2.2.1. 安装OpenSSH服务2.2.2. 启动和检查SSH服务 3. SSH具体使用方式4. vscode中使用ssh远程连接 1. SSH使用介绍 SSH 最常见的用途是通过加密连接在不安全的网络中进…

fiddler抓https包

1&#xff0c;安装fiddler省略 2&#xff0c;下载证书步骤&#xff1a;tools-options-https 点击确认&#xff0c;点击OK&#xff0c;点击是 把证书安装到谷歌浏览器上步骤&#xff1a;点击谷歌浏览器右上角的设置&#xff0c;在搜索框中搜索证书&#xff0c;点击“证书管理”…

常见的排序算法【总结】

目录 排序的基本概念与分类排序的稳定性内排序与外排序简单排序冒泡排序时间复杂度&#xff1a; O ( n 2 ) O(n^2) O(n2) 简单选择排序排序原理&#xff1a;时间复杂度&#xff1a; O ( n 2 ) O(n^2) O(n2) 插入排序排序原理&#xff1a;时间复杂度&#xff1a; O ( n 2 ) O(n^…

MFC GDI绘制卡通人物

文章目录 主要代码完整visual studio工程下载 主要代码 // DrawFrogView.cpp : implementation of the CDrawFrogView class //#include "stdafx.h" #include "DrawFrog.h"#include "DrawFrogDoc.h" #include "DrawFrogView.h"#inclu…

让TSN DDS运转起来——面向智能汽车的以太网测试解决方案

概述 作为OPEN联盟和AUTOSAR联盟的核心成员&#xff0c;经纬恒润多年来持续为国内外各大OEM和供应商提供车载以太网相关的咨询服务&#xff0c;涵盖TCP/IP、SOME/IP、DDS、诊断、TSN等前沿技术领域的设计和测试。同时&#xff0c;经纬恒润与行业内的合作伙伴紧密合作&#xff0…