Linux驅動開發2 - 內核定時器驅動

背景

所有驅動開發都是基于全志T507(Android 10)進行開發,用于記錄驅動開發過程。

簡介

定時器是比較常用的一個功能,用來執行周期性任務。一般不太精確的定時可以用系統提供的延時函數進行。如果需要進行較為精確的延時,就要考慮使用系統內核提供的定時器中斷。

在linux中ktime可以實現ms級定時,如果需要us、ns級就沒法使用這個了(這個我們后續在討論)。

驅動實現

首先在Linux驅動開發1 - Platform設備中已經實現了Platform設備驅動的編寫,本文是基于這個驅動進行的定時器功能開發。

步驟1、配置時鐘頻率

首先在內核中可以配置定時器中斷周期,T507中默認配置為300HZ,即最大精度為3.33ms。如上圖所示,timer最大支持的精度為1ms,可以通過啟用CONFIG_HZ_1000進行設置。本文就以默認的300HZ頻率為例。

也可以用make menuconfig在圖形化界面進行配置。

配置文件路徑為/kernel/linux-4.9/arch/arm64/configs/xxx

步驟2、實現platform_driver 結構體。

在longan/kernel/linux-4.9/drivers目錄下創建自己的文件夾,定義一個timer_led_drv.c文件。

這里可以不用修改,只修改了驅動名稱

/* platform驅動結構體 */
static struct platform_driver led_timer_driver = {.driver		= {.name	= "devledtimer",			        	// 無設備樹時,用于設備和驅動間的匹配.of_match_table	= led_timer_of_match,     // 有設備樹后,利用設備樹匹配表},.probe		= led_timer_probe,.remove		= led_timer_remove,
};

在設備數創建設備節點。這里以PI0為例

    led_timer {compatible = "devledtimer";pinctrl-names = "default";status = "okay";//pinctrl-0 = <&timer_led_pin_a>;ledtimer {gpios = <&pio PI 0 1 2 0 1>;state = "on";};};

在PIO節點下定義pinctrl_led_test節點,主要用于配置IO口。

    soc@03000000 {pio: pinctrl@0300b000 {...timer_led_pin_a: led_timer_grp@0 {allwinner,pins = "PI0";allwinner,function = "led_timer_grp";allwinner,muxsel = <0x01>;allwinner,drive = <0x00>;allwinner,pull = <0x01>;allwinner,data = <0x01>;};...}}

步驟3、定義設備節點

主要定義一下led_timer_priv,用來存放節點信息,用于生成/dev/xxx節點。

這里用到了定時器,因此增加了timer_list和timeperiod用來啟用定時器及控制定時器周期。

另外還用到了自旋鎖,作用自己查資料,這里就不詳述了。

/* 存放led信息的結構體 */
struct led_timer_data 
{char                    name[16];       // 設備名字int                     pin;            // gpio編號int                     active;         // 控制亮滅的標志int                     enable;         // 定時器使能標志位
};/* 存放led的私有屬性 */
struct led_timer_priv 
{struct cdev             cdev;           // cdev結構體struct class            *dev_class;     // 自動創建設備節點的類int                     num_leds;       // led的數量struct led_timer_data   led;            // 存放led信息的結構體數組int                     timeperiod;     // 定時周期struct timer_list       timer;          // 定義一個定時器spinlock_t              lock;           // 定義一個自旋鎖};

步驟4、初始化設備

定義一個初始化方法gpio_led_probe,并綁定到步驟2中的.probe

static int led_timer_probe(struct platform_device *pdev)

這個函數和?Linux驅動開發1 - Platform設備中gpio_led_probe一樣。

parser_init_led_timer用于獲取設備樹中的節點信息,并進行初始化。

步驟5、移除設備

常規操作,將所有設備依次注銷回收。

增加del_timer_sync(&priv->timer);用于在移除設備時刪除定時器。

static int led_timer_remove(struct platform_device *pdev)
{struct led_timer_priv *priv = platform_get_drvdata(pdev);int i;dev_t devno = MKDEV(dev_major, 0);/* 注銷設備結構體,class結構體和cdev結構體 */for(i=0; i<priv->num_leds; i++){devno = MKDEV(dev_major, i);device_destroy(priv->dev_class, devno);}class_destroy(priv->dev_class);cdev_del(&priv->cdev); unregister_chrdev_region(MKDEV(dev_major, 0), priv->num_leds);/* 將led的狀態設置為滅 */for (i = 0; i < priv->num_leds; i++) {gpio_set_value(priv->led.pin, 0);}   del_timer_sync(&priv->timer);printk("led_timer_ioctl success to remove driver[major=%d]!\n", dev_major);return 0;
} 

步驟6、字符設備描述file_operations


static struct file_operations led_fops = 
{.owner = THIS_MODULE,.open = led_open,.release = led_release,.unlocked_ioctl = led_ioctl,.compat_ioctl = led_timer_ioctl,
};

實現led_open、led_release、led_ioctl。

在led_open中初始化自旋鎖及初始化定時器timer。修改ioctl邏輯,增加定時器控制命令。

static int led_open(struct inode *inode, struct file *file)
{struct led_timer_priv *priv;priv = container_of(inode->i_cdev, struct led_timer_priv, cdev);file->private_data = priv;priv->timeperiod = 1000;/* 初始化自旋鎖 */spin_lock_init(&priv->lock);/* 初始化timer */setup_timer(&priv->timer, timer_do_in_time, &priv->timer);return 0;
}static int led_release(struct inode *inode, struct file *file)
{return 0;
}static long led_timer_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{struct led_timer_priv *priv;priv = file->private_data;unsigned int flags;printk(">>>>>  led_timer_ioctl command=%d arg=%ld\n", cmd, arg);switch (cmd){case 10:/* 關閉定時器 */del_timer_sync(&priv->timer);priv->led.enable = 0;gpio_set_value(priv->led.pin, 0);priv->led.active = 0;printk(">>>>>  led_timer_ioctl del_timer_sync\n");break;case 11:/* 打開定時器 */del_timer_sync(&priv->timer);mod_timer(&priv->timer, jiffies + msecs_to_jiffies(priv->timeperiod));priv->led.enable = 1;printk(">>>>>  led_timer_ioctl mod_timer = %ld\n", jiffies);break;case 12:/* 設置定時器周期 */spin_lock_irqsave(&priv->lock, flags);priv->timeperiod = arg;spin_unlock_irqrestore(&priv->lock, flags);if (priv->led.enable == 0){del_timer_sync(&priv->timer);mod_timer(&priv->timer, jiffies + msecs_to_jiffies(priv->timeperiod));priv->led.enable = 1;}printk(">>>>>  led_timer_ioctl period = %d\n", priv->timeperiod);break;default:printk("Ioctl command=%d can't be supported\n", cmd);break;}return 0;}

實現定時器中斷處理函數

void timer_do_in_time(unsigned long arg)
{struct timer_list *mytimer = (struct timer_list *)arg;struct led_timer_priv *priv = container_of(mytimer, struct led_timer_priv, timer);// TODO 在這里處理需要做的事情// 根據標志位處理是否重啟定時器if (priv->led.enable == 1){mod_timer(&priv->timer, jiffies + msecs_to_jiffies(priv->timeperiod)); }
}

步驟7、編譯環境MakeFile

在自定義目錄下創建Makefile文件。這里默認加載設備。

obj-y	+= timer_led_drv.o

遇到的問題

問題1、中斷函數中獲取led_timer_priv結構體信息。

主要用container_of獲取到內存中struct led_timer_priv *priv類型指針,然后使用priv中的led_timer_data或者timer進行操作。

源碼

#include <linux/init.h>                  
#include <linux/module.h>          
#include <linux/errno.h>             
#include <linux/types.h>                  
#include <linux/kernel.h>   
#include <linux/timer.h>
#include <linux/jiffies.h>      
#include <linux/fs.h>                  
#include <linux/cdev.h>           
#include <linux/device.h>               
#include <linux/of_gpio.h>             
#include <linux/platform_device.h>    
#include <linux/gpio.h>
#include <linux/of_device.h>
#include <linux/slab.h>#define LED_OFF                     0       
#define LED_ON                      1static int dev_major = 0;/* 存放led信息的結構體 */
struct led_timer_data 
{char                    name[16];       // 設備名字int                     pin;            // gpio編號int                     active;         // 控制亮滅的標志int                     enable;         // 定時器使能標志位
};/* 存放led的私有屬性 */
struct led_timer_priv 
{struct cdev             cdev;           // cdev結構體struct class            *dev_class;     // 自動創建設備節點的類int                     num_leds;       // led的數量struct led_timer_data   led;            // 存放led信息的結構體數組int                     timeperiod;     // 定時周期struct timer_list       timer;          // 定義一個定時器spinlock_t              lock;           // 定義一個自旋鎖};void timer_do_in_time(unsigned long arg)
{struct timer_list *mytimer = (struct timer_list *)arg;struct led_timer_priv *priv = container_of(mytimer, struct led_timer_priv, timer);/* 每次都取反,實現LED燈反轉 */priv->led.active = !priv->led.active;		gpio_set_value(priv->led.pin, priv->led.active);/* 重啟定時器 */if (priv->led.enable == 1){mod_timer(&priv->timer, jiffies + msecs_to_jiffies(priv->timeperiod)); }
}static inline int sizeof_led_timers_priv(int num_leds)
{return sizeof(struct led_timer_priv) + (sizeof(struct led_timer_priv) * num_leds);
}int parser_init_led_timer(struct platform_device *pdev)
{struct device_node *np = pdev->dev.of_node;     // 當前設備節點struct device_node *child;                      // 當前設備節點的子節點struct led_timer_priv *priv;                    // 存放私有屬性int num_leds, gpio;                             // led數量和gpio編號                num_leds = 1;if(num_leds <= 0) {dev_err(&pdev->dev, "fail to find child node\n");return -EINVAL;}priv = devm_kzalloc(&pdev->dev, sizeof_led_timers_priv(num_leds), GFP_KERNEL);if (!priv){return -ENOMEM;}priv->num_leds = 1;/* 找到子節點并傳給child */child = of_get_child_by_name(np, "ledtimer");/* 解析dts并且獲取gpio口,函數返回值就得到gpio號,并且讀取gpio現在的標志 */gpio = of_get_named_gpio(child, "gpios", 0);/* 將子節點的名字,傳給私有屬性結構體中的led信息結構體中的name屬性 */strncpy(priv->led.name, child->name, sizeof(priv->led.name)); printk(">>>>>  led_timer_ioctl gpio=%d name=%s\n", gpio, priv->led.name);/* 將gpio編號和控制亮滅的標志傳給結構體* active屬性,1代表亮,0代表滅,初始屬性為亮*/priv->led.active = 1; priv->led.pin = gpio; /* 申請gpio口,相較于gpio_request增加了gpio資源獲取與釋放功能 */gpio_request(priv->led.pin, "ledtimer");/* 設置gpio為輸出模式,并設置初始狀態 */gpio_direction_output(priv->led.pin, 1);/* 將led的私有屬性放入platform_device結構體的device結構體中的私有數據中 */platform_set_drvdata(pdev, priv);return 0;}static int led_open(struct inode *inode, struct file *file)
{struct led_timer_priv *priv;priv = container_of(inode->i_cdev, struct led_timer_priv, cdev);file->private_data = priv;priv->timeperiod = 1000;/* 初始化自旋鎖 */spin_lock_init(&priv->lock);/* 初始化timer */setup_timer(&priv->timer, timer_do_in_time, &priv->timer);return 0;
}static int led_release(struct inode *inode, struct file *file)
{return 0;
}static long led_timer_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{struct led_timer_priv *priv;priv = file->private_data;unsigned int flags;switch (cmd){case 10:/* 關閉定時器 */del_timer_sync(&priv->timer);priv->led.enable = 0;gpio_set_value(priv->led.pin, 0);priv->led.active = 0;break;case 11:/* 打開定時器 */del_timer_sync(&priv->timer);mod_timer(&priv->timer, jiffies + msecs_to_jiffies(priv->timeperiod));priv->led.enable = 1;break;case 12:/* 設置定時器周期 */spin_lock_irqsave(&priv->lock, flags);priv->timeperiod = arg;spin_unlock_irqrestore(&priv->lock, flags);if (priv->led.enable == 0){del_timer_sync(&priv->timer);mod_timer(&priv->timer, jiffies + msecs_to_jiffies(priv->timeperiod));priv->led.enable = 1;}break;default:printk("Ioctl command=%d can't be supported\n", cmd);break;}return 0;}static struct file_operations led_fops = 
{.owner = THIS_MODULE,.open = led_open,.release = led_release,.unlocked_ioctl = led_timer_ioctl,.compat_ioctl = led_timer_ioctl,
};static int led_timer_probe(struct platform_device *pdev)
{struct led_timer_priv   *priv;          // 臨時存放私有屬性的結構體struct device           *dev;           // 設備結構體dev_t                   devno;          // 設備的主次設備號int                     i, rv = 0;    /* 1)解析設備樹并初始化led狀態 */rv = parser_init_led_timer(pdev);if( rv < 0 )return rv;/* 將之前存入的私有屬性,放入臨時的結構體中 */priv = platform_get_drvdata(pdev);/* 2)分配主次設備號 */if (0 != dev_major) {   /* 靜態分配主次設備號 */devno = MKDEV(dev_major, 0); rv = register_chrdev_region(devno, priv->num_leds, "devledtimer"); }   else {   /* 動態分配主次設備號 */rv = alloc_chrdev_region(&devno, 0, priv->num_leds, "devledtimer"); dev_major = MAJOR(devno); }   if (rv < 0) {   dev_err(&pdev->dev, "major can't be allocated\n"); return rv; }   /* 3)分配cdev結構體 */cdev_init(&priv->cdev, &led_fops);priv->cdev.owner  = THIS_MODULE;rv = cdev_add (&priv->cdev, devno , priv->num_leds); if( rv < 0) {dev_err(&pdev->dev, "struture cdev can't be allocated\n");goto undo_major;}/* 4)創建類,實現自動創建設備節點 */priv->dev_class = class_create(THIS_MODULE, "ledtimer");if( IS_ERR(priv->dev_class) ) {dev_err(&pdev->dev, "fail to create class\n");rv = -ENOMEM;goto undo_cdev;}/* 5)創建設備 */for(i=0; i<priv->num_leds; i++){devno = MKDEV(dev_major, i);dev = device_create(priv->dev_class, NULL, devno, NULL, "ledtimer");if( IS_ERR(dev) ) {dev_err(&pdev->dev, "fail to create device\n");rv = -ENOMEM;goto undo_class;}}printk("led_timer_ioctl success to install driver[major=%d]!\n", dev_major);return 0;undo_class:class_destroy(priv->dev_class);undo_cdev:cdev_del(&priv->cdev);undo_major:unregister_chrdev_region(devno, priv->num_leds);return rv;}static int led_timer_remove(struct platform_device *pdev)
{struct led_timer_priv *priv = platform_get_drvdata(pdev);int i;dev_t devno = MKDEV(dev_major, 0);/* 注銷設備結構體,class結構體和cdev結構體 */for(i=0; i<priv->num_leds; i++){devno = MKDEV(dev_major, i);device_destroy(priv->dev_class, devno);}class_destroy(priv->dev_class);cdev_del(&priv->cdev); unregister_chrdev_region(MKDEV(dev_major, 0), priv->num_leds);/* 將led的狀態設置為滅 */for (i = 0; i < priv->num_leds; i++) {gpio_set_value(priv->led.pin, 0);}   del_timer_sync(&priv->timer);printk("led_timer_ioctl success to remove driver[major=%d]!\n", dev_major);return 0;
} /* 匹配列表 */
static const struct of_device_id led_timer_of_match[] = {{ .compatible = "devledtimer" },{}
};MODULE_DEVICE_TABLE(of, led_timer_of_match);/* platform驅動結構體 */
static struct platform_driver led_timer_driver = {.driver		= {.name	= "devledtimer",			        	// 無設備樹時,用于設備和驅動間的匹配.of_match_table	= led_timer_of_match,     // 有設備樹后,利用設備樹匹配表},.probe		= led_timer_probe,.remove		= led_timer_remove,
};module_platform_driver(led_timer_driver);MODULE_LICENSE("GPL");

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/77483.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/77483.shtml
英文地址,請注明出處:http://en.pswp.cn/web/77483.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Dify 使用 excel 或者 csv 文件創建知識庫

Dify 使用 excel 或者 csv 文件創建知識庫 1. 創建知識庫2. 創建聊天助手3. 其他 1. 創建知識庫 創建知識庫&#xff0c;導入excel/csv文件&#xff0c; 文件內容&#xff0c; 單擊 “預覽塊”&#xff0c;可以確認會生成多個鍵值對的塊&#xff0c; 配置 Embedding 模型和檢索…

23種設計模式-行為型模式之迭代器模式(Java版本)

Java 迭代器模式&#xff08;Iterator Pattern&#xff09;詳解 &#x1f9e0; 什么是迭代器模式&#xff1f; 迭代器模式是一種行為型設計模式&#xff0c;它提供一種方法順序訪問一個聚合對象中的各個元素&#xff0c;而不暴露該對象的內部表示。 &#x1f3af; 使用場景 …

使用nodeJs的express+axios+cors做代理

使用nodeJs的expressaxioscors做代理 前端在請求后端時通常會遇到跨域cors問題&#xff0c;如果只在本地開發可以通過webpack或vite的proxy設置。但如果需要在線上或者其他地方繞過跨域&#xff0c;可以使用代理的方法。 1. 創建文件夾 并創建以下文件 package.json {"…

T檢驗、F檢驗及樣本容量計算學習總結

目錄 〇、碎語一、假設檢驗1.1 兩種錯誤1.2 z檢驗和t檢驗1.3 t檢驗1.3.1 單樣本t檢驗1.3.2 配對樣本t檢驗1.3.3 獨立樣本t檢驗1.4 方差齊性檢驗1.4 卡方檢驗二、樣本容量的計算2.1 AB測試主要的兩種應用場景2.2 絕對量的計算公式2.3 率的計算公式參考資料〇、碎語 聽到最多的檢…

tensorflow使用詳解

一、TensorFlow基礎環境搭建 安裝與驗證 # 安裝CPU版本 pip install tensorflow# 安裝GPU版本&#xff08;需CUDA 11.x和cuDNN 8.x&#xff09; pip install tensorflow-gpu# 驗證安裝 python -c "import tensorflow as tf; print(tf.__version__)"核心概念 Tensor…

Redis的阻塞

Redis的阻塞 Redis的阻塞問題主要分為內在原因和外在原因兩大類&#xff0c;以下從這兩個維度展開分析&#xff1a; 一、內在原因 1. 不合理使用API或數據結構 Redis 慢查詢 Redis 慢查詢的界定 定義&#xff1a;Redis 慢查詢指命令執行時間超過預設閾值&#xff08;默認 10m…

SLAM學習系列——ORB-SLAM3安裝(Ubuntu20-ROS/Noetic)

ORB-SLAM3學習&#xff08;Ubuntu20-ROS&#xff09; 0 主要參考文獻1 ORB-SLAM3安裝環境配置1.0 前言1.0.0 關于ORB-SLAM3安裝版本選擇1.0.1 本文配置操作匯總(快速配置)1.0.1.1 ORB_SLAM3環境配置&#xff1a;1.0.1.2 ORB_SLAM3安裝1.0.1.3 ORB_SLAM的ROS接口 1.1 C&#xff…

【應用密碼學】實驗二 分組密碼(2)

一、實驗要求與目的 1&#xff09; 學習AES密碼算法原理 2&#xff09; 學習AES密碼算法編程實現 二、實驗內容與步驟記錄&#xff08;只記錄關鍵步驟與結果&#xff0c;可截圖&#xff0c;但注意排版與圖片大小&#xff09; 字符串加解密 運行python程序&#xff0c;輸入…

區塊鏈基石解碼:分布式賬本的運行奧秘與技術架構

區塊鏈技術的革命性源于其核心組件——分布式賬本&#xff08;Distributed Ledger&#xff09;。這一技術通過去中心化、透明性和不可篡改性&#xff0c;重塑了傳統數據存儲與交易驗證的方式。本文將從分布式賬本的核心概念、實現原理、應用場景及挑戰等方面展開&#xff0c;揭…

AUTOSAR_RS_ClassicPlatformDebugTraceProfile

AUTOSAR經典平臺調試、跟蹤與分析支持 AUTOSAR組件調試、跟蹤與分析功能詳解 目錄 簡介ARTI核心擴展 核心特定ARTI擴展結構核心參數定義 操作系統和任務擴展 OS特定ARTI擴展任務特定ARTI擴展軟件組件特定擴展 總體架構 組件結構接口定義 錯誤處理 默認錯誤跟蹤器(DET) 總結 1.…

SpringBoot配置RestTemplate并理解單例模式詳解

在日常開發中&#xff0c;RestTemplate 是一個非常常用的工具&#xff0c;用來發起HTTP請求。今天我們通過一個小例子&#xff0c;不僅學習如何在SpringBoot中配置RestTemplate&#xff0c;還會深入理解單例模式在Spring中的實際應用。 1. 示例代碼 我們首先來看一個基礎的配置…

DPIN在AI+DePIN孟買峰會闡述全球GPU生態系統的戰略愿景

DPIN基金會在3月29日于印度孟買舉行的AIDePIN峰會上展示了其愿景和未來5年的具體發展計劃&#xff0c;旨在塑造去中心化算力的未來。本次活動匯集了DPIN、QPIN、社區成員和Web3行業資深顧問&#xff0c;深入探討DPIN構建全球領先的去中心化GPU算力網絡的戰略&#xff0c;該網絡…

央視兩次采訪報道愛藏評級,聚焦生肖鈔市場升溫,評級幣成交易安全“定心丸”

CCTV央視財經頻道《經濟信息聯播》《第一時間》兩檔節目分別對生肖賀歲鈔進行了5分鐘20秒的專題報道。長期以來&#xff0c;我國一直保持著發行生肖紀念鈔和紀念幣的傳統&#xff0c;生肖紀念鈔和紀念幣在收藏市場保持著較高的熱度。特別是2024年初&#xff0c;央行發行了首張賀…

【計算機哲學故事1-2】輸入輸出(I/O):你吸收什么,便成為什么

“我最近&#xff0c;是不是廢了……”她癱在沙發上&#xff0c;手機扣在胸口&#xff0c;盯著天花板自言自語。 我坐在一旁&#xff0c;隨手翻著桌上的雜志&#xff0c;沒接話&#xff0c;等著她把情緒發泄完。 果然&#xff0c;幾秒后&#xff0c;她重重地嘆了口氣&#xf…

封裝el-autocomplete,接口調用

組件 <template><el-autocompletev-model"selectedValue":fetch-suggestions"fetchSuggestions":placeholder"placeholder"select"handleSelect"clearablev-bind"$attrs"/> </template><script lang&…

GPUStack昇騰Atlas300I duo部署模型DeepSeek-R1【GPUStack實戰篇2】

2025年4月25日GPUStack發布了v0.6版本&#xff0c;為昇騰芯片910B&#xff08;1-4&#xff09;和310P3內置了MinIE推理&#xff0c;新增了310P芯片的支持&#xff0c;很感興趣&#xff0c;所以我馬上來搗鼓玩玩看哈 官方文檔&#xff1a;https://docs.gpustack.ai/latest/insta…

Linux進程詳細解析

1.操作系統 概念 任何計算機系統都包含?個基本的程序集合&#xff0c;稱為操作系統(OS)。籠統的理解&#xff0c;操作系統包括&#xff1a; ? 內核&#xff08;進程管理&#xff0c;內存管理&#xff0c;文件管理&#xff0c;驅動管理&#xff09; ? 其他程序&#xff08…

解決兩個技術問題后小有感觸-QZ Tray使用經驗小總結

老朋友都知道&#xff0c;我現在是一家軟件公司銷售部門的項目經理和全棧開發工程師&#xff0c;就是這么“奇怪”的崗位&#xff0c;大概我是公司銷售團隊里比較少有技術背景、銷售業績又不那么理想的銷售。 近期在某個票務系統項目上駐場&#xff0c;原來我是這個項目的項目…

Centos 7.6安裝redis-6.2.6

1. 安裝依賴 確保系統已經安裝了必要的編譯工具和庫&#xff1a; sudo yum groupinstall "Development Tools" -y sudo yum install gcc make tcl -y 2. 解壓 Redis 源碼包 進入 /usr/local/ 目錄并解壓 redis-6.2.6.tar.gz 文件&#xff1a; cd /usr/local/ sudo ta…

Ejs模版引擎介紹,什么是模版引擎,什么是ejs,ejs基本用法

** EJS 模板引擎**&#xff0c;讓你徹底搞明白什么是模板引擎、什么是 EJS、怎么用、語法、最佳實踐等等&#xff1a; &#x1f4da; 一、什么是模板引擎&#xff1f; 模板引擎是前后端分離之前的一種服務器端“渲染技術”。它的主要作用是&#xff1a; 將 HTML 頁面和后端傳遞…