linux 设备驱动的分层思想

一、        概述

        

                像这样的分层设计在linux的input、RTC、MTD、I2c、SPI、tty、USB等诸多类型设备驱动中屡见不鲜,下面对这些驱动进行详细的分析。

二、        输入设备驱动

        输入设备(如按键、键盘、触摸屏、鼠标等)是典型的字符设备,其一般的工作机理是底层在按键、触摸等动作发送时产生一个中断(或驱动通过Timer定时查询),然后通过CPU通过SPI、I2c或外部存储器读取键值、坐标等数据,并将它们放入一个缓冲区,字符设备驱动管理该缓冲区,而驱动的read()接口让用户可以读取键值、坐标等数据。

        显然,在这些工作中,只是中断、读键值/坐标值是与设备相关的,而输入事件的缓冲区管理以及字符设备驱动的file_operations接口则对输入设备是通用的。基于此,内核设计了输入子系统,由核心层处理公共的工作。Linux内核输入子系统如图所示:

        

        输入核心提供了底层输入设备驱动程序所需要的API,如分配/释放一个输入设备:

         

         

        input_allocate_device()返回的是1个input_dev的结构体,此结构体用于表征1个输入设备。注册/注销输入设备用的接口如下:

         

        报告输入事件用的接口如下:             

        而对于所有的输入事件,内核都用统一都数据结构来描述,这个数据结构是input_event,如代码:    

        drivers/input/keyboard/gpio_keys.c基于input架构实现了通用的GPIO按键驱动。该驱动是基于platform_driver架构的,名为”gpio-keys“它将硬件相关的信息(如使用GPIO号,按下和抬起时的电平等)屏蔽在板文件platform_device的platform_data中,因此该驱动应用于各个处理器,具有良好的跨平台性。以下是该驱动的probe()函数。

 851 static int gpio_keys_probe(struct platform_device *pdev)852 {853     struct device *dev = &pdev->dev;854     const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);855     struct fwnode_handle *child = NULL;856     struct gpio_keys_drvdata *ddata;857     struct input_dev *input;858     int i, error;859     int wakeup = 0;860 861     if (!pdata) {862         pdata = gpio_keys_get_devtree_pdata(dev);863         if (IS_ERR(pdata))864             return PTR_ERR(pdata);865     }866 867     ddata = devm_kzalloc(dev, struct_size(ddata, data, pdata->nbuttons),868                  GFP_KERNEL);869     if (!ddata) {870         dev_err(dev, "failed to allocate state\n");871         return -ENOMEM;872     }873 874     ddata->keymap = devm_kcalloc(dev,875                      pdata->nbuttons, sizeof(ddata->keymap[0]),876                      GFP_KERNEL);877     if (!ddata->keymap)878         return -ENOMEM;879 880     input = devm_input_allocate_device(dev);881     if (!input) {882         dev_err(dev, "failed to allocate input device\n");883         return -ENOMEM;884     }885 886     ddata->pdata = pdata;887     ddata->input = input;888     mutex_init(&ddata->disable_lock);889 890     platform_set_drvdata(pdev, ddata);891     input_set_drvdata(input, ddata);892 893     input->name = pdata->name ? : pdev->name;894     input->phys = "gpio-keys/input0";895     input->dev.parent = dev;896     input->open = gpio_keys_open;897     input->close = gpio_keys_close;898 899     input->id.bustype = BUS_HOST;900     input->id.vendor = 0x0001;901     input->id.product = 0x0001;902     input->id.version = 0x0100;903 904     input->keycode = ddata->keymap;905     input->keycodesize = sizeof(ddata->keymap[0]);906     input->keycodemax = pdata->nbuttons;907 908     /* Enable auto repeat feature of Linux input subsystem */909     if (pdata->rep)910         __set_bit(EV_REP, input->evbit);911 912     for (i = 0; i < pdata->nbuttons; i++) {913         const struct gpio_keys_button *button = &pdata->buttons[i];914 915         if (!dev_get_platdata(dev)) {916             child = device_get_next_child_node(dev, child);917             if (!child) {918                 dev_err(dev,919                     "missing child device node for entry %d\n",920                     i);921                 return -EINVAL;922             }923         }924 925         error = gpio_keys_setup_key(pdev, input, ddata,926                         button, i, child);927         if (error) {928             fwnode_handle_put(child);929             return error;930         }931 932         if (button->wakeup)933             wakeup = 1;934     }935 936     fwnode_handle_put(child);937 938     error = input_register_device(input);939     if (error) {940         dev_err(dev, "Unable to register input device, error: %d\n",941             error);942         return error;943     }944 945     device_init_wakeup(dev, wakeup);946 947     return 0;948 }
 880     input = devm_input_allocate_device(dev);886     ddata->pdata = pdata;887     ddata->input = input;888     mutex_init(&ddata->disable_lock);889 890     platform_set_drvdata(pdev, ddata);891     input_set_drvdata(input, ddata);892 893     input->name = pdata->name ? : pdev->name;894     input->phys = "gpio-keys/input0";895     input->dev.parent = dev;896     input->open = gpio_keys_open;897     input->close = gpio_keys_close;898 899     input->id.bustype = BUS_HOST;900     input->id.vendor = 0x0001;901     input->id.product = 0x0001;902     input->id.version = 0x0100;903 904     input->keycode = ddata->keymap;905     input->keycodesize = sizeof(ddata->keymap[0]);906     input->keycodemax = pdata->nbuttons;907 925         error = gpio_keys_setup_key(pdev, input, ddata,926                         button, i, child);912     for (i = 0; i < pdata->nbuttons; i++) {913         const struct gpio_keys_button *button = &pdata->buttons[i];914 915         if (!dev_get_platdata(dev)) {916             child = device_get_next_child_node(dev, child);917             if (!child) {918                 dev_err(dev,919                     "missing child device node for entry %d\n",920                     i);921                 return -EINVAL;922             }923         }924 925         error = gpio_keys_setup_key(pdev, input, ddata,926                         button, i, child);927         if (error) {928             fwnode_handle_put(child);929             return error;930         }931 932         if (button->wakeup)933             wakeup = 1;934     }938     error = input_register_device(input);

        上诉代码的第880行分配了1个输入设备,第886~907行初始化了该input_dev的一些属性,第925行注册了这个输入设备。第912~934行则初始化了所用到的GPIO,第23行完成了这个输入设备的注册 。

        在注册输入设备后,底层输入设备驱动的核心工作只剩下在按键、触摸等人为动作发送时报告事件。下方展示了GPIO按键中断发送时的事件报告代码。

  1. GPIO指通用输入输出接口(General-purpose input/output)
  2. Keys表示按键设备
  3. IRQ是中断请求(Interrupt Request)的缩写
  4. ISR即中断服务程序(Interrupt Service Routine)
 469 static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)470 {471     struct gpio_button_data *bdata = dev_id;472     struct input_dev *input = bdata->input;473     unsigned long flags;474 475     BUG_ON(irq != bdata->irq);476 477     spin_lock_irqsave(&bdata->lock, flags);478 479     if (!bdata->key_pressed) {480         if (bdata->button->wakeup)481             pm_wakeup_event(bdata->input->dev.parent, 0);482 483         input_report_key(input, *bdata->code, 1);484         input_sync(input);485 486         if (!bdata->release_delay) {487             input_report_key(input, *bdata->code, 0);488             input_sync(input);489             goto out;490         }491 492         bdata->key_pressed = true;493     }494 495     if (bdata->release_delay)496         hrtimer_start(&bdata->release_timer,497                   ms_to_ktime(bdata->release_delay),498                   HRTIMER_MODE_REL_HARD);499 out:500     spin_unlock_irqrestore(&bdata->lock, flags);501     return IRQ_HANDLED;502 }
 483         input_report_key(input, *bdata->code, 1);484         input_sync(input);

        GPIO按键驱动通过input_report_key()、input_sync()这样的函数来汇报按键事件以及同步事件。从底层的GPIO按键驱动可以看出,该驱动没有任何file_operations的动作,也没有各种I/O模式,注册进入系统也用的是input_register_device()这样与input相关的API。这是由于与linux VFS接口的这一部分代码全部都在drivers/input/evdev.c中实现了。

 558 static ssize_t evdev_read(struct file *file, char __user *buffer,559               size_t count, loff_t *ppos)560 {561     struct evdev_client *client = file->private_data;562     struct evdev *evdev = client->evdev;563     struct input_event event;564     size_t read = 0;565     int error;566 567     if (count != 0 && count < input_event_size())568         return -EINVAL;569 570     for (;;) {571         if (!evdev->exist || client->revoked)572             return -ENODEV;573 574         if (client->packet_head == client->tail &&575             (file->f_flags & O_NONBLOCK))576             return -EAGAIN;577 578         /*579          * count == 0 is special - no IO is done but we check580          * for error conditions (see above).581          */582         if (count == 0)583             break;584 585         while (read + input_event_size() <= count &&586                evdev_fetch_next_event(client, &event)) {587 588             if (input_event_to_user(buffer + read, &event))589                 return -EFAULT;590 591             read += input_event_size();592         }593 594         if (read)595             break;596 597         if (!(file->f_flags & O_NONBLOCK)) {598             error = wait_event_interruptible(client->wait,599                     client->packet_head != client->tail ||600                     !evdev->exist || client->revoked);601             if (error)602                 return error;603         }604     }605 606     return read;607 }1292 static const struct file_operations evdev_fops = {
1293     .owner      = THIS_MODULE,
1294     .read       = evdev_read,
1295     .write      = evdev_write,
1296     .poll       = evdev_poll,
1297     .open       = evdev_open,
1298     .release    = evdev_release,
1299     .unlocked_ioctl = evdev_ioctl,
1300 #ifdef CONFIG_COMPAT
1301     .compat_ioctl   = evdev_ioctl_compat,
1302 #endif
1303     .fasync     = evdev_fasync,
1304     .llseek     = no_llseek,
1305 };

        上诉代码574-576行在检查出是非阻塞访问后,立即返回EAGAIM错误, 而586和598~600行都代码则处理了阻塞的睡眠情况。回过头来想,其实gpio_keys驱动里面调用的input_event()、input_sync()有间接唤醒这个等待队列evdev->wait的功能,只不过这些代码都隐藏在其内部实现里了。

 三、        RTC设备驱动

        RTC(实时钟)借助电池供电,在系统掉电都情况下依然可以正常计时。它通常还具有产生周期性中断以及闹钟(Alarm)中断的能力,是一种典型的字符设备。作为一种字符设备驱动,RTC需要有file_operations中接口函数的实现,如open()、release()、read()、poll()、ioctl()等,而典型的IOCTL包括RTC_SET_TIME、RTC_ALM_READ、RTC_ALM_SET、RTC_IRQP_SET、RTC_IRQP_READ等,这些对于所有的RTC是通用的,只用底层的具体实现是与设备相关的。

        

四、        Framebuffer设备驱动

        Framebuff(帧缓冲)是Linux系统为显示设备提供设备的一个接口,它将显示缓冲器抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。对于帧缓冲设备而言,只要在显示缓冲区中与显示点对应的区域内写入区域颜色值,对应的颜色会自动在屏幕上显示。

        下图为Linux帧缓冲设备驱动的主要结构,帧缓冲设备提供给用户空间file_operations结构体由

drivers/video/fbdev/core/fbmem.c中的file_operations提供,而特定帧缓冲设备fb_info结构的注册、注销以及其中成员的维护,尤其是fb_ops中成员函数的实现则由对应的xxxfb.c文件实现,fb_ops中的函数最终会操作LCD控制其硬件寄存器。

        多数显存的操作方法都是规范的,可以按照像素点格式的要求顺序写帧缓冲区。但是有少量LCD的显存写法可能比较特殊,这时候,在核心层drivers/video/fbdev/core/fb_chrdev.c实现的fb_write()中,实际上可以给底层提供一个重写自己的机会,如下:

 46 static ssize_t fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)47 {48     struct fb_info *info = file_fb_info(file);49 50     if (!info)51         return -ENODEV;52 53     if (info->state != FBINFO_STATE_RUNNING)54         return -EPERM;55 56     if (info->fbops->fb_write)57         return info->fbops->fb_write(info, buf, count, ppos);58 59     return fb_io_write(info, buf, count, ppos);60 }

        第56~57行是一个检查底层LCD有没有实现自己特殊显存写法的代码,如果有,直接调底层的;如果没有,用中间层标准的显存写法就搞定了底层的那个不特殊的LCD。

五、        终端设备驱动

        在linux系统中,终端是一种字符型设备,它有多种类型,通常使用tty来简称各种类型的终端设备。对于嵌入式系统而言,最普通采用的是UART(Univer Asynchronous Receiver/Transmitter)串行端口,日常生活中简称串口。

        Linux内核中tty的层次结构如图所示,它包含tty核心tty_io.c、tty线路规程n_tty.c(实现N_TTY线路规程)和tty驱动实例,tty线路规程的工作是以特殊的方式格式化从一个用户或者硬件收到的数据,这个格式化常常采用一个协议转换的形势。

        tty_io.c本身是一个标准的字符设备驱动,它对上字符设备的职责,实现file_operations成员函数。但是tty核心层对下又定义了tty_driver的架构,这样tty设备驱动的主体工作就变成了填充tty_driver结构体中的成员,实现其中的tty_operations的成员函数,而不再是去实现file_operations这一级的工作。tty_driver结构体和tty_operations的定义分别如下: 

        

        

433 struct tty_driver {
434     struct kref kref;
435     struct cdev **cdevs;
436     struct module   *owner;
437     const char  *driver_name;
438     const char  *name;
439     int name_base;
440     int major;
441     int minor_start;
442     unsigned int    num;
443     short   type;
444     short   subtype;
445     struct ktermios init_termios;
446     unsigned long   flags;
447     struct proc_dir_entry *proc_entry;
448     struct tty_driver *other;
449 
450     /*
451      * Pointer to the tty data structures
452      */
453     struct tty_struct **ttys;
454     struct tty_port **ports;
455     struct ktermios **termios;
456     void *driver_state;
457 
458     /*
459      * Driver methods
460      */
461 
462     const struct tty_operations *ops;
463     struct list_head tty_drivers;
464 } __randomize_layout;
350 struct tty_operations {
351     struct tty_struct * (*lookup)(struct tty_driver *driver,
352             struct file *filp, int idx);
353     int  (*install)(struct tty_driver *driver, struct tty_struct *tty);
354     void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
355     int  (*open)(struct tty_struct * tty, struct file * filp);
356     void (*close)(struct tty_struct * tty, struct file * filp);
357     void (*shutdown)(struct tty_struct *tty);
358     void (*cleanup)(struct tty_struct *tty);
359     ssize_t (*write)(struct tty_struct *tty, const u8 *buf, size_t count);
360     int  (*put_char)(struct tty_struct *tty, u8 ch);
361     void (*flush_chars)(struct tty_struct *tty);
362     unsigned int (*write_room)(struct tty_struct *tty);
363     unsigned int (*chars_in_buffer)(struct tty_struct *tty);
364     int  (*ioctl)(struct tty_struct *tty,
365             unsigned int cmd, unsigned long arg);
366     long (*compat_ioctl)(struct tty_struct *tty,
367                  unsigned int cmd, unsigned long arg);
368     void (*set_termios)(struct tty_struct *tty, const struct ktermios *old);
369     void (*throttle)(struct tty_struct * tty);
370     void (*unthrottle)(struct tty_struct * tty);
371     void (*stop)(struct tty_struct *tty);
372     void (*start)(struct tty_struct *tty);
373     void (*hangup)(struct tty_struct *tty);
374     int (*break_ctl)(struct tty_struct *tty, int state);
375     void (*flush_buffer)(struct tty_struct *tty);
376     void (*set_ldisc)(struct tty_struct *tty);
377     void (*wait_until_sent)(struct tty_struct *tty, int timeout);
378     void (*send_xchar)(struct tty_struct *tty, char ch);
379     int (*tiocmget)(struct tty_struct *tty);
380     int (*tiocmset)(struct tty_struct *tty,
381             unsigned int set, unsigned int clear);
382     int (*resize)(struct tty_struct *tty, struct winsize *ws);
383     int (*get_icount)(struct tty_struct *tty,
384                 struct serial_icounter_struct *icount);
385     int  (*get_serial)(struct tty_struct *tty, struct serial_struct *p);
386     int  (*set_serial)(struct tty_struct *tty, struct serial_struct *p);
387     void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
388 #ifdef CONFIG_CONSOLE_POLL
389     int (*poll_init)(struct tty_driver *driver, int line, char *options);
390     int (*poll_get_char)(struct tty_driver *driver, int line);
391     void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
392 #endif
393     int (*proc_show)(struct seq_file *m, void *driver);
394 } __randomize_layout;

        tty设备发送数据的流程为:tty核心从一个用户获取将要发送给一个tty设备的数据,tty核心将数据传递给tty线路规程驱动,接着数据被传递到tty驱动,tty驱动将数据转化为可以发送给硬件的格式。接收数据的流程为:从tty硬件接收到的数据向上交给tty驱动,接着进入tty线程驱动,再进入tty核心,在这里它被一个用户获取。 

        尽管一个特定的底层UART设备驱动完全可以遵循上述tty_driver的方法来设计,即定义tty_driver并实现tty_operations中的成员函数,但是鉴于串口之间的共性,Linux考虑在文件drivers/tty/serial/serial_core.c中实现了UART设备的通用tty驱动层(我们也可以称其为串口核心层)。这样,UART驱动的主要任务就进一步演变成了serial-core.c中定义的一组uart_xxx接口而不是tty_xxx接口,如下图所示,按照面向对象的思想,可以认为tty_driver是字符设备的泛华、serial-core是tty_driver的泛化,而具体的串口驱动又是serial-core的泛化。

         

        串口核心层又定义了新的uart_driver结构体和其操作集uart_ops。一个底层的UART驱动需要创建和通过uart_register_driver注册一个uart_driver而不是tty_driver,代码给出uart_driver的定义。

 732 struct uart_driver {733     struct module       *owner;734     const char      *driver_name;735     const char      *dev_name;736     int          major;737     int          minor;738     int          nr;739     struct console      *cons;740 741     /*742      * these are private; the low level driver should not743      * touch these; they should be initialised to NULL744      */745     struct uart_state   *state;746     struct tty_driver   *tty_driver;747 };

        uart_driver结构体在本质上是派生uart_driver结构体,因此,它的第746行也包含了一个tty_driver的结构体成员。tty_operations在UART这个层面也被进一步泛化为了uart_ops,其定义如代码如下:

 374 struct uart_ops {375     unsigned int    (*tx_empty)(struct uart_port *);376     void        (*set_mctrl)(struct uart_port *, unsigned int mctrl);377     unsigned int    (*get_mctrl)(struct uart_port *);378     void        (*stop_tx)(struct uart_port *);379     void        (*start_tx)(struct uart_port *);380     void        (*throttle)(struct uart_port *);381     void        (*unthrottle)(struct uart_port *);382     void        (*send_xchar)(struct uart_port *, char ch);383     void        (*stop_rx)(struct uart_port *);384     void        (*start_rx)(struct uart_port *);385     void        (*enable_ms)(struct uart_port *);386     void        (*break_ctl)(struct uart_port *, int ctl);387     int     (*startup)(struct uart_port *);388     void        (*shutdown)(struct uart_port *);389     void        (*flush_buffer)(struct uart_port *);390     void        (*set_termios)(struct uart_port *, struct ktermios *new,391                        const struct ktermios *old);392     void        (*set_ldisc)(struct uart_port *, struct ktermios *);393     void        (*pm)(struct uart_port *, unsigned int state,394                   unsigned int oldstate);395     const char  *(*type)(struct uart_port *);396     void        (*release_port)(struct uart_port *);397     int     (*request_port)(struct uart_port *);398     void        (*config_port)(struct uart_port *, int);399     int     (*verify_port)(struct uart_port *, struct serial_struct *);400     int     (*ioctl)(struct uart_port *, unsigned int, unsigned long);401 #ifdef CONFIG_CONSOLE_POLL402     int     (*poll_init)(struct uart_port *);403     void        (*poll_put_char)(struct uart_port *, unsigned char);404     int     (*poll_get_char)(struct uart_port *);405 #endif406 };

        由于drivers/tty/serial/serial_core.c是一个tty_driver,因此在serial_core.c中,存在一个tty_operations的实例,这个实例的成员函数会进一步调用struct uart_ops的成员函数,这样就把file_operations里的成员函数、tty_operations的成员函数和uart_ops的成员函数串起来。

六、        misc设备驱动

        由于Linux驱动倾向与分层设计,所有各个具体的设备都可以找到它归属都类型,从而套到它相应的架构里面去,并且只需要实现最底层的那 一部分。但是,也有部分类似globalmem、globalfifo的字符设备,确实不知道它属于什么类型,一般推荐大家采用miscdevice框架结构。miscdevice本质上也是字符设备,只是在miscdevice核心层的misc_init()函数中,通过register_chrdev(MISC_MAJOR,"misc",&misc_fops)注册了字符设备,而具体miscdevice实例调用misc_register()的时候又自动完成了device_create()、获取动态次设备号的动作。

        miscdevice的主设备号是固定的,MISC_MAJOR定义为10,在linux内核中,大概可以找到200多处使用miscdevice框架结构的驱动。

        miscdevice的结构体定义如下,第82行,指向了一个file_operations的结构体。miscdevice结构体内file_operations中的成员函数实际上是由dirvers/char/misc.c中的misc驱动的核心层的misc_ops成员函数间接调用的,比如misc_open()就会间接调用底层注册的miscdevice的fops->open。

 79 struct miscdevice  {80     int minor;81     const char *name;82     const struct file_operations *fops;83     struct list_head list;84     struct device *parent;85     struct device *this_device;86     const struct attribute_group **groups;87     const char *nodename;88     umode_t mode;89 };

         如果上诉代码第80行的minor为MISC_DYNAMIC_MINOR,miscdevice核心层会自动找一个空闲的次设备号,否则用minor指定的次设备号。第81行的name是设备的名称。

        miscdevice驱动的注册和注销分别用下面两个API:

        

        因此miscdevice驱动的一般结构形如:

         

        在调用misc_register(&xxx_dev)时,该函数内部会自动调用device_create(),而device_create会以xxx_dev作为drvdata参数。其次,在miscdevice核心层misc_open()函数的帮助下,在file_operations的成员函数中,xxx_dev会自动生成为file的private_data(misc_open会完成file->private_data的赋值操作)。

        如果我们用面向对象的封装思想把一个设备的属性、自旋锁、互斥体、等待队列、miscdevice等封装在一个结构体里面:

         

        在file_operations的成员函数中,就可以通过container_of()和file->private_data反推出xxx_dev的实例。  

         

七、        驱动核心层

        分析了上诉多个实例,我们可以归纳出核心层肩负的3大职责:

        1)对上提供接口。file_operations的读、写、ioctl都被中间层搞定,各种I/O模型也被处理掉了。

        2)中间层实现通用逻辑。可以被底层各种实例共享都代码都被中间层搞定,避免底层重复实现。

        3)对下定义框架。底层的驱动不再需要关心linux内核VFS的接口和各种可能的I/O模型,而只需处理与具体硬件相关的访问。

        这种分层有时候还不是两层,可以有更多层,在软件上呈现为面向对象里类继承和多态的状态。上面介绍的终端设备驱动类似下图一样的结果。

         

  

         

         

        

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

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

相关文章

【嵌入式硬件实例】-555定时器驱动直流无刷电机

555定时器驱动直流无刷电机 文章目录 555定时器驱动直流无刷电机 1、555定时器介绍 2、BLDC,无刷直流电机 3、DRV10866 驱动器 4、硬件准备与接线 5、电路工作原理 在这个项目中,我们将使用 555 定时器 IC 和 DRV10866 驱动器 IC 制作 BLDC、无刷直流电机驱动电路。无刷电机可…

Helm 常用命令 + Bitnami 中间件部署速查表

文章目录一、Helm 常用命令速查表1.1. 仓库管理1.2. Chart 搜索1.3. 应用部署1.4. 应用管理二、Bitnami 常用中间件部署示例三、常用自定义参数&#xff08;values.yaml 配置项&#xff09;四、安装后的访问方式五、一键安装脚本 install-middleware.sh5.1. 完整脚本5.2. 使用方…

Ansible 自动化运维实战系列(六):Valut详解

Ansible 自动化运维实战系列&#xff08;六&#xff09;&#xff1a;Valut详解&#x1f4da; 系列导航一&#xff1a;概述二&#xff1a;命令1&#xff09;创建加密文件2&#xff09;加密已有文件3&#xff09;查看加密文件4&#xff09;编辑加密文件5&#xff09;解密文件6&am…

《探秘浏览器Web Bluetooth API设备发现流程》

网页若需与蓝牙设备通信,往往需依赖本地客户端或专用驱动程序作为中介,不仅增加了用户操作成本,也限制了Web应用在跨设备场景中的拓展。而Web Bluetooth API的出现,直接赋予了网页与低功耗蓝牙(BLE)设备对话的能力,从智能手环的健康数据同步,到智能家居设备的远程控制,…

Jenkins+Python自动化持续集成详细教程

Python接口自动化测试零基础入门到精通&#xff08;2025最新版&#xff09;Jenkins安装 ​ Jenkins是一个开源的软件项目&#xff0c;是基于java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件的持续…

C++面试——内存

一、简述堆和栈的区别维度栈&#xff08;Stack&#xff09;堆&#xff08;Heap&#xff09;生命周期随函数调用自动创建/销毁由程序员或垃圾回收器控制分配速度极快&#xff08;仅移动指针&#xff09;慢&#xff08;需查找空闲块、维护元数据&#xff09;空间大小较小&#xf…

UVM验证(三)—UVM机制(1)

目录 &#xff08;一&#xff09;Factory工厂机制 1. 工厂机制核心逻辑&#xff1a;“注册 - 创建 - 覆盖” 2. 代码映射&#xff1a;从概念到实现 3. 实验目标&#xff1a;用 dadd_fixen_driver 固定 data_en1 4. 工厂机制的价值&#xff1a;“灵活验证的基石” 5. 常见…

前往中世纪 送修改器(Going Medieval)免安装中文版

网盘链接&#xff1a; 前往中世纪 免安装中文版 名称&#xff1a;前往中世纪 送修改器&#xff08;Going Medieval&#xff09;免安装中文版 描述&#xff1a; 在Going Medieval的世界中&#xff0c;黑暗时代的社会已濒临崩溃。14世纪末瘟疫肆虐&#xff0c;全球95%的人口因…

Font Awesome 参考手册

Font Awesome 参考手册 引言 Font Awesome 是一个功能强大的图标库,它允许开发者通过简单的 CSS 类来添加图标到网页中。本手册旨在为开发者提供全面的 Font Awesome 使用指南,包括图标选择、样式定制以及常见问题解答。 图标选择 图标分类 Font Awesome 提供了多种类别…

源网荷储一体化零碳智慧工业园区建设

针对传统工业园区等电力消纳大户存在的供电模式单一、能源管理错杂、园区人员设备安全统筹不到位等诸多问题&#xff0c;通过AI分析及物联网等新技术和自研交直流关键设备的应用&#xff0c;在三维场景中构建集智慧能源、智慧安防、碳排放管理及智慧运营等功能于一体的新型零碳…

MySQL表操作(DDL)

MySQL表操作创建表查看表结构修改表结构增加一列删除一列修改某一列的属性修改某一列的名字修改某一列的属性和名字插入几条信息删除表创建表 语法&#xff1a; CREATE TABLE table_name ( field1 datatype, field2 datatype, field3 datatype ) character set 字符集 collat…

【总结】Python多线程

【总结】Python多线程备注一、基本概念二、备注 2025/08/15 星期五 最近用到了python的多线程发现和其他语言有点不同记录一下 一、基本概念 首先要理解一下线程、进程和协程的概念 线程&#xff08;Thread&#xff09;&#xff1a;是计算机能够调度的最小计算单位 进程&…

【c++深入系列】:万字详解模版(下)

&#x1f525; 本文专栏&#xff1a;c &#x1f338;作者主页&#xff1a;努力努力再努力wz &#x1f4aa; 今日博客励志语录&#xff1a; 成功没有标准答案&#xff0c;但坚持永远是必选项 ★★★ 本文前置知识&#xff1a; 模版(上&#xff09; 那么在之前的文章中我们展示…

Docker部署美化SunPanel导航页

使用Cloudflare Tunnels穿透的地址:星霜导航 由于是使用的iStore里面的SunPanel导航页,只是基本的功能 页脚配置 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" conte…

支持向量机的原理和案例解析

支持向量机的原理和案例解析一、支持向量机的核心目标&#xff1a;间隔最大化步骤1&#xff1a;定义分离超平面步骤2&#xff1a;定义样本到超平面的距离&#xff08;间隔&#xff09;步骤3&#xff1a;间隔最大化的目标步骤4&#xff1a;简化目标函数二、通过拉格朗日乘子法求…

【教程】Nginx 源码安装

开发环境&#xff1a;VMWare 操作系统&#xff1a;红帽 Linux 8   前言 以离线环境安装为前提&#xff0c;需准备以下 rmp 包内容&#xff1a; gccmakepcre-develzlib-developenssl-devel 如何准备可参考【教程】准备离线可用的 RPM 包   流程 准备离线包 # 安装 rpm yu…

俄罗斯信封套娃问题-二维最长递增子序列

354. 俄罗斯套娃信封问题 - 力扣&#xff08;LeetCode&#xff09; Solution 对一个维度从小到大排序&#xff0c;然后对另外一个维度求最长上升子序列即可。 class Solution { public:struct node {int w, h;node(int w, int h) {this->w w;this->h h;}};static bool…

区块链:用数学重构信任的数字文明基石

在数字经济浪潮席卷全球的今天&#xff0c;虚拟与现实的融合正面临一个根本性挑战——如何让数字世界的"承诺"拥有与现实世界同等的可信度&#xff1f; 当我们在电商平台下单时&#xff0c;如何确保商品质量与描述一致&#xff1f;当企业签署电子合同时&#xff0c;如…

Go语言defer机制详解与应用

一、defer作用Go语言的defer关键字提供了一种延迟执行机制&#xff0c;它能确保指定的函数调用在当前函数返回前被执行。这一特性常用于资源释放和异常处理场景。二、defer基本特性&#xff08;1&#xff09;执行时机&#xff1a;defer 语句会在外层函数返回前执行&#xff0c;…

服务器安全防护详细介绍

一、方案概述随着信息技术的飞速发展&#xff0c;服务器作为企业数据存储、业务运行的核心载体&#xff0c;其安全性至关重要。本服务器安全防护方案旨在通过多层次、全方位的安全防护策略&#xff0c;构建一个完整的服务器安全防护体系&#xff0c;有效抵御各类安全威胁&#…