(Linux驱动学习 - 6).Linux中断

news2024/11/26 21:21:15

一. Linux 中断 API 函数

1.中断号

        每个中断都有一个中断号,通过中断号即可区分不同的中断,有的资料也把中断号叫做中
断线。在 Linux 内核中使用一个 int 变量表示中断号。

2.申请中断 - request_irq

函数原型:

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

功能

                ①.request_irq 函数用于申请中断,此函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用;

                ②.request_irq 函数会激活(使能)中断;

                ③.申请中断成功返回(0),申请失败返回(其他负值),如果返回(-EBUSY)则表示中断已经被申请了。

        参数

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

                handler:中断处理函数;

                name:中断名字,设置后可在 /proc/interrupts 文件中看到对应的中断名字;

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

                flags:中断标志,常用的中断标志有以下:

3.释放中断 - free_irq

函数原型

void free_irq(unsigned int irq,void *dev)

功能

        释放中断,若中断不是共享的,那么 free_irq 会删除中断处理函数并且禁止中断,若中断是共享的(IRQF_SHARED),那么共享中断在被释放掉最后的中断处理函数的时候才会被禁止掉。

参数

        irq:要释放的中断;

        dev:如果中断设置为共享(IRQF_SHARED),此参数用来区分具体的中断;

4.设置中断处理函数 - irqreturn_t (*irq_handler_t)(int,void*)

函数原型

irqreturn_t (*irq_handler_t)(int,void*)

功能

        设置中断处理函数;

        一般中断处理函数返回值为 return IRQ_RETVAL(IRQ_HANDLED)

参数

        int:传入中断号;

        void:用于区分共享中断的不同设备,也可以指向设备数据结构;

5.中断使能与禁止函数

函数原型

/* 使能中断 */
void enable_irq(unsigned int irq);

/* 禁止中断(等待当前正在执行的中断函数执行完才返回) */
void disable_irq(unsigned int irq);

/* 禁止中断(立刻返回,不会等待当前中断处理函数执行完毕) */  
void disable_irq_nosync(unsigned int irq);

功能

        使能和禁止指定的中断;

         disable_irq 函数要等到当前正在执行的中断处理函数执行完才返回;

参数

        irq:要禁止的中断号

函数原型

/* 使能当前处理器中断系统 */
local_irq_enable();

/* 禁止当前处理器中断系统 */
local_disbale();




/* 禁止中断,并且将中断状态保存在 flags 中 */
local_irq_save(flags);

/* 恢复中断,将中断恢复到 flags 状态 */
local_irq_restore(flags);

二.上半部与下半部

1.上半部与下半部的说明

        中断处理函数完成的要尽可能快点执行完毕,如果中断处理函数内处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行;将耗时短的代码(例如响应中断,清除中断标志位)可以放在上半部完成。

        这样做的目的是为了实现中断处理函数的快进快出,那些对时间敏感、执行速度快的操作可以放到中断处理函数中,也就是上半部。剩下的所有工作都可以放到下半部去执行,比如在上半部将数据拷贝到内存中,关于数据的具体处理就可以放到下半部去执行。

建议的参考

        ①.如果要处理的内容不希望被其他中断打断,那么可以放到上半部。

        ②.如果要处理的任务对时间敏感,可以放到上半部。

        ③.如果要处理的任务与硬件有关,可以放到上半部。

        ④.除了上述 3 点,其他任务优先考虑放到下半部。

2.软中断

        可用 软中断tasklet 来实现下半部。

        在 Linux 内核中使用结构体 softirq_action 表示软中断

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

        一共有 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
};

(1).注册软中断处理函数 - open_softirq

函数原型:

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

功能:

        注册对应的软中断处理函数;

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

参数:

        nr:要开启的软中断,在 HI_SOFTIRQ ~ RCU_SOFTIRQ 里面选一个;

        action:软中断对应的处理函数;

3.tasklet

(1).tasklet 结构体

        Linux 内核使用 tasklet_struct 结构体来表示 tasklet:

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 的参数 */
};

(2).初始化 tasklet - tasklet_init

函数原型

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

功能

        初始化tasklet

参数

        t:要初始化的tasklet;

        func:tasklet 的处理函数;

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

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

/*
*    @param - name        :要定义的tasklet名字,即tasklet_struct 变量
*    @param func          :tasklet的处理函数
*    @param - data        :传递给 func 函数的参数
*/
DECLARE_TASKLET(name,func,data);

(3).使 tasklet 在合适的时间运行 - tasklet_schedule

函数原型

void tasklet_schedule(struct tasklet_struct *t);

功能

        在上半部,也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运行

参数

        t:要调度的 tasklet , 也就是 DECLARE_TAKLET 宏里面的 name

(4).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);
    ......
}

4.工作队列

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

(1).工作结构体

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

(2).工作队列结构体

struct workqueue_struct 
{
    struct list_head pwqs;
    struct list_head list;
    struct mutex mutex;
    int work_color;
    int flush_color;
    atomic_t nr_pwqs_to_flush;
    struct wq_flusher *first_flusher;
    struct list_head flusher_queue;
    struct list_head flusher_overflow;
    struct list_head maydays;
    struct worker *rescuer;
    int nr_drainers;
    int saved_max_active;
    struct workqueue_attrs *unbound_attrs;
    struct pool_workqueue *dfl_pwq;
    char name[WQ_NAME_LEN];
    struct rcu_head rcu;
    unsigned int flags ____cacheline_aligned;
    struct pool_workqueue __percpu *cpu_pwqs;
    struct pool_workqueue __rcu *numa_pwq_tbl[];
};

(3).工作者线程结构体

struct worker 
{
    union 
    {
        struct list_head entry;
        struct hlist_node hentry;
    };

    struct work_struct *current_work;
    work_func_t current_func;
    struct pool_workqueue *current_pwq;
    bool desc_valid;
    struct list_head scheduled;
    struct task_struct *task;
    struct worker_pool *pool;
    struct list_head node;
    unsigned long last_active;
    unsigned int flags;
    int id;
    char desc[WORKER_DESC_LEN];
    struct workqueue_struct *rescue_wq;
};

(4).初始化工作 - INIT_WORK / DECLARE_WORK

宏原型:

/*
*    @param - _work:要初始化的工作,传入 work_struct 结构体
*    @param _func  :工作对应的处理函数
*/
#define INIT_WORK(_work,_func)



/*
*    @param - n        :定义的工作(work_struct)
*    @param - f        :工作对应的处理函数
*/
#define DECLARE_WORK(n,f)

        

(5).工作调度函数 - schedule_work

函数原型:

bool schedule_work(struct work_struct *work)

功能:

        开启工作的调度

        成功时返回(0),失败时返回(其他值)

参数:

        work:要调度的工作

        

(6).工作队列的使用示例

/* 定义工作(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);
    ......
}

三.获取中断号

        编写驱动的时候需要用到中断号,中断信息已经写到了设备树里面,我们可以通过 irq_of_parse_and_map 函数从 interrupts 属性中提取到对应的设备号。

1. irq_of_parse_and_map

函数原型

unsigned int irq_of_parse_and_map(struct device_node *dev,int index)

功能

        获取中断号;

        返回值为(中断号)

参数

        dev        :设备结点

        index     :索引号,interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息

2. gpio_to_irq

        若使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应中断号

函数原型

int gpio_to_irq(unsigned int gpio)

功能

        获取 gpio 对应的中断号;

        返回( GPIO 对应的中断号)

参数

        gpio:要获取的 gpio 的编号

四.GPIO按键中断实验代码编写

1.设备树

(1).流程图

(2).设备树代码

2.驱动代码

(1).流程图

(2).代码

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>



#define IMX6UIRQ_CNT    1                       /* 设备号个数 */
#define IMX6UIRQ_NAME   "imx6uirq"              /* 设备名字 */
#define KEY0VALUE       0X01                    /* KEY0按键值 */
#define INVAKEY         0XFF                    /* 无效的按键值 */
#define KEY_NUM         1                       /* 按键数量 */



/* 中断 IO 描述结构体 */
struct irq_keydesc
{
    int gpio;                               /* gpio */
    int irqnum;                             /* 中断号 */
    unsigned char value;                    /* 按键对应的键值 */
    char name[10];                          /* 名字 */
    irqreturn_t (*handler)(int,void *);     /* 指向中断服务函数的函数指针 */
};

/* imx6uirq 设备结构体 */
struct imx6uirq_dev
{
    dev_t devid;                            /* 设备号 */
    struct cdev cdev;                       /* cdev */
    struct class *class;                    /* 类 */
    struct device *device;                  /* 设备 */
    int major;                              /* 主设备号 */
    int minor;                              /* 次设备号 */
    struct device_node *nd;                 /* 设备结点 */
    atomic_t keyvalue;                      /* 有效的按键值 */
    atomic_t releasekey;                    /* 标记是否完成一次完整的按键动作 */
    struct timer_list timer;                /* 定义一个定时器 */
    struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键中断信息描述数组 */
    unsigned char curkeynum;                /* 当前的按键号 */
};


/* irq 设备 */
struct imx6uirq_dev imx6uirq;



/**
 * @description:                KEY0 按键中断服务函数,开启定时器,延时 10 ms,定时器用于按键消抖
 * @param - irq     :           中断号
 * @param - dev_id  :           设备结构
 * @return          :           中断执行结果
 */
static irqreturn_t key0_handler(int irq,void *dev_id)
{
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

    /* 表示按键中断触发(双边沿触发) (因为按键平时为上拉状态) */
    dev->curkeynum = 0;
    dev->timer.data = (volatile long)dev_id;

    /* 2.让定时器回调函数 10 ms 后触发 */
    mod_timer(&dev->timer,jiffies + msecs_to_jiffies(10));          

    return IRQ_RETVAL(IRQ_HANDLED);
}


/**
 * @description:                定时器服务函数,用于按键消抖,定时器到了以后再次读取按键值,
 *                              如果按键还是处于按下的状态就表示按键动作有效
 * @param - arg     :           设备结构体变量
 * @return          :           无
 */
void timer_function(unsigned long arg)
{
    unsigned char value;
    unsigned char num;
    struct irq_keydesc *keydesc;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

    /* 获取上一刻的按键状态 */
    num = dev->curkeynum;
    keydesc = &dev->irqkeydesc[num];

    /* 读取当前 gpio 的状态 */
    value = gpio_get_value(keydesc->gpio);
    /* 若此刻 gpio 的状态为低电平 , 则消抖后确认按键确实是按下了 (因为这个定时器服务函数是在按键中断之后触发的) */
    if(0 == value)
    {
        atomic_set(&dev->keyvalue,keydesc->value);
    }
    /* 若此刻 gpio 的状态为高电平 , 则消抖后确认按键确实是松开了 (因为这个定时器服务函数是在按键中断之后触发的) */
    else
    {
        atomic_set(&dev->keyvalue, 0x80 | keydesc->value);      //最高为置 1 
        atomic_set(&dev->releasekey, 1);                        //标记松开按键
    }
}



/**
 * @description:                按键 IO 初始化
 * @param           :           无
 * @return          :           成功返回(0),返回其他则为失败
 */
static int keyio_init(void)
{
    unsigned char i = 0;
    int ret = 0;

    /* 1.获取设备结点 */
    imx6uirq.nd = of_find_node_by_path("/key");
    if(NULL == imx6uirq.nd)
    {
        printk("key node not find!\r\n");
        return -EINVAL;
    }

    /* 2.获取 KEY 的 GPIO 编号 */
    for(i = 0;i < KEY_NUM;i++)
    {
        imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd,"key-gpio",i);
        /* 若获取 gpio 编号失败 */
        if(imx6uirq.irqkeydesc[i].gpio < 0)
        {
            printk("can not get key%d\r\n",i);
        }
    }

    
    for(i = 0;i < KEY_NUM;i++)
    {
        memset(imx6uirq.irqkeydesc[i].name,0,sizeof(imx6uirq.irqkeydesc[i].name));
        sprintf(imx6uirq.irqkeydesc[i].name,"KEY%d",i);

        /* 3.申请 GPIO */
        gpio_request(imx6uirq.irqkeydesc[i].gpio,imx6uirq.irqkeydesc[i].name);

        /* 4.设置 IO 为输入 */
        gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);

        /* 5.获取 GPIO 的中断号 */
        imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd,i);

#if 0
        /* 若使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应中断号 */
        imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif

        printk("key : %d ; gpio = %d , irqnum = %d\r\n",i,imx6uirq.irqkeydesc[i].gpio,imx6uirq.irqkeydesc[i].irqnum);
    }


    /* 6.申请中断 */
    imx6uirq.irqkeydesc[0].handler = key0_handler;
    imx6uirq.irqkeydesc[0].value = KEY0VALUE;
    for(i = 0;i < KEY_NUM;i++)
    {
        ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,
                          imx6uirq.irqkeydesc[i].handler,
                          IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
                          imx6uirq.irqkeydesc[i].name,
                          &imx6uirq);
    }
    
    
    /* 7.创建定时器 */
    init_timer(&imx6uirq.timer);
    imx6uirq.timer.function = timer_function;

    return 0;
}


/**
 * @description:            打开设备
 * @param - inode   :       传递给驱动的 inode
 * @param - filp    :       设备文件
 * @return          :       0 为成功 , 其他为失败
 */
static int imx6uirq_open(struct inode *inode,struct file *filp)
{
    /* 设置私有数据 */
    filp->private_data = &imx6uirq;

    return 0;
}


/**
 * @description:            从设备读取数据
 * @param - filp    :       文件描述符
 * @param - buf     :       返回给用户空间的数据缓冲区
 * @param - cnt     :       要读取的字节数
 * @param - offt    :       相对于文件首地址的偏移量
 * @return          :       成功读取的字节数,如果为负值,则表示失败
 */
static ssize_t imx6uirq_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    /* 读取消抖后的键值 */
    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    /* 如果有按键按下 */
    if(releasekey)
    {
        if(keyvalue & 0x80)
        {
            keyvalue &= ~0x80;      //将 keyvalue 最高为清零
            ret = copy_to_user(buf,&keyvalue,sizeof(keyvalue));
        }
        else
        {
            goto data_error;
        }

        /* 清除按下标志 */
        atomic_set(&dev->releasekey,0); 
    }
    else
    {   
        goto data_error;
    }

    return 0;

data_error:
    return -EINVAL;
}


/* 设备操作函数 */
static struct file_operations imx6uirq_fops = 
{
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
};


/**
 * @description:            驱动入口函数
 * @param           :       无
 * @return          :       无
 */
static int __init imx6uirq_init(void)
{
    /* 1.创建设备号 */
    if(imx6uirq.major)          //若定义了设备号
    {
        imx6uirq.devid = MKDEV(imx6uirq.major,0);
        register_chrdev_region(imx6uirq.devid,IMX6UIRQ_CNT,IMX6UIRQ_NAME);
    }
    else                        //若没有定义设备号
    {
        alloc_chrdev_region(&imx6uirq.devid,0,IMX6UIRQ_CNT,IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);
        imx6uirq.minor = MINOR(imx6uirq.devid);
    }

    /* 2.初始化cdev */
    cdev_init(&imx6uirq.cdev,&imx6uirq_fops);

    /* 3,添加一个cdev */
    cdev_add(&imx6uirq.cdev,imx6uirq.devid,IMX6UIRQ_CNT);

    /* 4.创建类 */
    imx6uirq.class = class_create(THIS_MODULE,IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.class))
    {
        return PTR_ERR(imx6uirq.class);
    }

    /* 5.创建设备 */
    imx6uirq.device = device_create(imx6uirq.class,NULL,imx6uirq.devid,NULL,IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.device))
    {
        return PTR_ERR(imx6uirq.device);
    }

    /* 6.初始化按键 */
    atomic_set(&imx6uirq.keyvalue,INVAKEY);
    atomic_set(&imx6uirq.releasekey,0);
    keyio_init();

    return 0;
}


/**
 * @description:            驱动出口函数
 * @param           :       无
 * @return          :       无
 */
static void __exit imx6uirq_exit(void)
{
    unsigned int i = 0;

    /* 1.删除定时器 */
    del_timer_sync(&imx6uirq.timer);

    /* 2.释放 中断 与 GPIO */
    for(i = 0;i < KEY_NUM;i++)
    {
        free_irq(imx6uirq.irqkeydesc[i].irqnum,&imx6uirq);
        gpio_free(imx6uirq.irqkeydesc[i].gpio);
    }

    /* 3. 注销字符设备驱动 */
    cdev_del(&imx6uirq.cdev);
    unregister_chrdev_region(imx6uirq.devid,IMX6UIRQ_CNT);
    device_destroy(imx6uirq.class,imx6uirq.devid);
    class_destroy(imx6uirq.class);
}


module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("kaneki");



Makefile:

KERNELDIR := /home/linux/IMX6ULL/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH :=$(shell pwd)
obj-m := imx6uirq.o
build: kernel_modules
kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

可在 /proc/interrupts 文件下查看对应的中断有没有被注册

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

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

相关文章

【ubuntu】APT、apt、apt-get介绍

目录 1.APT简介 2.常用apt指令 2.1安装 2.2更新列表 2.3更新已经安装的软件包 2.4搜索软件包 2.5显示软件包信息 2.6移除软件包 2.7清理无用的安装包 2.8清理无用的依赖项 3.apt和apt-get 3.1区别 3.2 总结 1.APT简介 APT的全称是advanced package …

Java反射、自定义注解Demo

本文主要尝试使用反射、自定义注解&#xff0c;实现简单的Demo&#xff0c;所有代码均可直接复制使用。&#xff08;反射和注解是Java框架不可或缺的一部分&#xff0c;我们应该熟练掌握这部分知识&#xff01;&#xff09; 本文的代码结构如下&#xff1a; 代码如下&#xff…

【Linux】详解Linux下的工具(内含yum指令和vim指令)

文章目录 前言1. Linux下软件安装的方式2. yum2.1 软件下载的小知识2.2 在自己的Linux系统下验证yum源的存在2.3 利用yum指令下载软件2.4 拓展yum源&#xff08;针对于虚拟机用户&#xff09; 3. vim编辑器3.1 vim是什么&#xff1f;3.2 如何打开vim3.2 vim各模式下的讲解3.2.1…

高级图片编辑器Photopea

什么是 Photopea &#xff1f; Photopea 是一款免费的在线工具&#xff0c;用于编辑光栅和矢量图形&#xff0c;支持PSD、AI 和 Sketch文件。 功能上&#xff0c;Photopea 和 老苏之前介绍的 miniPaint 比较像 文章传送门&#xff1a;在线图片编辑器miniPaint 支持的格式 复杂…

创建django项目,编译类型选择Custom environment后,却没有manage.py文件,无法启动项目?

选择 Custom environment 创建后&#xff0c;启动项目却发现没有manage.py文件 解决办法&#xff1a; 1、 首先查看项目中是否安装了django&#xff0c;没有则安装 2 、创建项目&#xff08;这里的myproject则表示项目名&#xff09; django-admin startproject myproject …

C语言 | Leetcode C语言题解之第454题四数相加II

题目&#xff1a; 题解&#xff1a; struct hashTable {int key;int val;UT_hash_handle hh; };int fourSumCount(int* A, int ASize, int* B, int BSize, int* C, int CSize, int* D, int DSize) {struct hashTable* hashtable NULL;for (int i 0; i < ASize; i) {for (…

外包功能测试干了4年,技术退步太明显了。。。。。​

先说一下自己的情况&#xff0c;本科生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了差不多4年的功能测试&#xff0c;今年中秋&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测…

Day01-postgresql数据库基础入门培训

Day01-postgresql数据库基础入门培训 1、PostgresQL数据库简介2、PostgreSQL行业生态应用3、PostgreSQL版本发展与特性4、PostgreSQL体系结构介绍5、PostgreSQL与MySQL的区别6、PostgreSQL与Oracle、MySQL的对比 1、PostgresQL数据库简介 PostgreSQL【简称&#xff1a;PG】是加…

搭建shopify本地开发环境

虽然shopify提供了在线编辑器的功能&#xff0c;但是远不及本地编辑器方便高效&#xff0c;这篇文章主要介绍如何在本地搭建shopify开发环境&#xff1a; 1、安装nodejs 18.2 2、安装git 3、安装shopify cli ,使用指令: npm install -g shopify/clilatest 4、安装ruby 5、…

软件设计师——数据结构

本博文所有内容来自于B站up主zst_2001 目录 时间复杂度 常规数据结构 链表 栈与队列 ​编辑 串 数组 树 卡特兰数&#xff1a; 平衡二叉树 哈夫曼 图 AOV 排序 顺序 折半 哈希 时间复杂度 常规数据结构 链表 栈与队列 串 找i位置前面的字符串&#xff0c…

JS | JavaScript中document.write()有哪些用法?

document.write()是 JavaScript 中用于向文档中插入内容的方法。它可以在文档加载过程中或在脚本执行时动态地将任意内容写入到 HTML 文档中。 document.write() 是 JavaScript 中的一个方法&#xff0c;用于在 HTML 文档中动态生成内容。 这个方法可以在网页加载过程中动态地…

Python 工具库每日推荐 【Pandas】

文章目录 引言Python数据处理库的重要性今日推荐:Pandas工具库主要功能:使用场景:安装与配置快速上手示例代码代码解释实际应用案例案例:销售数据分析案例分析高级特性数据合并和连接时间序列处理数据透视表扩展阅读与资源优缺点分析优点:缺点:总结【 已更新完 TypeScrip…

重学SpringBoot3-集成Redis(一)

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-集成Redis&#xff08;一&#xff09; 1. 项目初始化2. 配置 Redis3. 配置 Redis 序列化4. 操作 Redis 工具类5. 编写 REST 控制器6. 测试 API7. 总结 随…

【复习】html最重要的表单和上传标签

文章目录 imgforminput img <img src"https://tse1-mm.cn.bing.net/th/id/OIP-C._XVJ53-pN6sDMXp8W19F4AAAAA?rs1&pidImgDetMain"alt"二次元"height"350px"width"200px"/>常用 没啥说的&#xff0c;一般操作css多一些 for…

Nacos理论知识+应用案例+高级特性剖析

一、理论知识 Nacos功能 Nacos常用于注册中心、配置中心 Nacos关键特性 1、服务发现和服务健康监测 nacos作为服务注册中心可用于服务发现,并支持传输层&#xff08;TCP&#xff09;和应用层(HTTP&#xff09;的健康检查&#xff0c;并提供了agent上报和nacos server端主动…

数据结构——List接口

文章目录 一、什么是List&#xff1f;二、常见接口介绍三、List的使用总结 一、什么是List&#xff1f; 在集合框架中&#xff0c;List是一个接口&#xff0c;通过其源码&#xff0c;我们可以清楚看到其继承了Collection。 Collection 也是一个接口&#xff0c;该接口中规范了后…

启动redis

1. 进入root的状态&#xff0c;sudo -i 2. 通过sudo find /etc/redis/ -name "redis.conf"找到redis.conf的路径 3. 切换到/etc/redis目录下&#xff0c;开启redis服务 4. ps aux | grep redis命令查看按当前redis进程&#xff0c;发现已经服务已经开启 5.关闭服务…

如何使用WPS软件里的AI工具?

在wps文档中随机位置&#xff0c;按两下键盘上的【CTRL】键&#xff0c;唤醒AI工具 然后&#xff0c;输入想要的问题后&#xff0c;按下【回车】&#xff0c;就会生成答案在文档中 如果答案是自己喜欢的&#xff0c; 则选择【保留】&#xff0c;即可保存在文档中 如果答案不是…

Arthas(阿尔萨斯)

Arthas Arthas可以为你做什么&#xff1f; 安装下载 //Linux环境下 wget https://alibaba.github.io/arthas/arthas-boot.jar //Windows环境下可以直接去官网下载压缩包 https://arthas.aliyun.com/doc/download.html//启动命令 java -jar arthas-boot.jar 启动阿尔萨斯&#…

使用Conda管理python环境的指南

1. 准备 .yml 文件 确保你有一个定义了 Conda 环境的 .yml 文件。这个文件通常包括环境的依赖和配置设置。文件内容可能如下所示&#xff1a; name: myenv channels:- defaults dependencies:- python3.8- numpy- pandas- scipy- pip- pip:- torch- torchvision- torchaudio2…