imx6ull-驅動開發篇23——Linux 內核定時器實驗

目錄

實驗程序編寫

修改設備樹文件

定時器驅動程序

timer.c

測試 timerApp.c

Makefile 文件

運行測試


實驗程序編寫

本講實驗,我們使用正點原子I.MX6U-ALPHA 開發板,通過linux內核定時器周期性的點亮和熄滅開發板上的 LED 燈, LED 燈的閃爍周期由內核定時器來設置,測試應用程序可以控制內核定時器周期。

修改設備樹文件

和之前文章一致,GPIO子系統驅動LED,主要是以下幾點:

  • 添加 pinctrl 節點
  • 添加 LED 設備節點
  • 檢查 PIN 是否被其他外設使用

定時器驅動程序

timer.c

#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define TIMER_CNT		1		/* 設備號個數 	*/
#define TIMER_NAME		"timer"	/* 名字 		*/
#define CLOSE_CMD 		(_IO(0XEF, 0x1))	/* 關閉定時器 */
#define OPEN_CMD		(_IO(0XEF, 0x2))	/* 打開定時器 */
#define SETPERIOD_CMD	(_IO(0XEF, 0x3))	/* 設置定時器周期命令 */
#define LEDON 			1		/* 開燈 */
#define LEDOFF 			0		/* 關燈 *//* timer設備結構體 */
struct timer_dev{dev_t devid;			/* 設備號 	 */struct cdev cdev;		/* cdev 	*/struct class *class;	/* 類 		*/struct device *device;	/* 設備 	 */int major;				/* 主設備號	  */int minor;				/* 次設備號   */struct device_node	*nd; /* 設備節點 */int led_gpio;			/* key所使用的GPIO編號		*/int timeperiod; 		/* 定時周期,單位為ms */struct timer_list timer;/* 定義一個定時器*/spinlock_t lock;		/* 定義自旋鎖 */
};struct timer_dev timerdev;	/* timer設備 *//** @description	: 初始化LED燈IO,open函數打開驅動的時候* 				  初始化LED燈所使用的GPIO引腳。* @param 		: 無* @return 		: 無*/
static int led_init(void)
{int ret = 0;timerdev.nd = of_find_node_by_path("/gpioled");if (timerdev.nd== NULL) {return -EINVAL;}timerdev.led_gpio = of_get_named_gpio(timerdev.nd ,"led-gpio", 0);if (timerdev.led_gpio < 0) {printk("can't get led\r\n");return -EINVAL;}/* 初始化led所使用的IO */gpio_request(timerdev.led_gpio, "led");		/* 請求IO 	*/ret = gpio_direction_output(timerdev.led_gpio, 1);if(ret < 0) {printk("can't set gpio!\r\n");}return 0;
}/** @description		: 打開設備* @param - inode 	: 傳遞給驅動的inode* @param - filp 	: 設備文件,file結構體有個叫做private_data的成員變量* 					  一般在open的時候將private_data指向設備結構體。* @return 			: 0 成功;其他 失敗*/
static int timer_open(struct inode *inode, struct file *filp)
{int ret = 0;filp->private_data = &timerdev;	/* 設置私有數據 */timerdev.timeperiod = 1000;		/* 默認周期為1s */ret = led_init();				/* 初始化LED IO */if (ret < 0) {return ret;}return 0;
}/** @description		: ioctl函數,* @param - filp 	: 要打開的設備文件(文件描述符)* @param - cmd 	: 應用程序發送過來的命令* @param - arg 	: 參數* @return 			: 0 成功;其他 失敗*/
static long timer_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{struct timer_dev *dev =  (struct timer_dev *)filp->private_data;int timerperiod;unsigned long flags;switch (cmd) {case CLOSE_CMD:		/* 關閉定時器 */del_timer_sync(&dev->timer);break;case OPEN_CMD:		/* 打開定時器 */spin_lock_irqsave(&dev->lock, flags);timerperiod = dev->timeperiod;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerperiod));break;case SETPERIOD_CMD: /* 設置定時器周期 */spin_lock_irqsave(&dev->lock, flags);dev->timeperiod = arg;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg));break;default:break;}return 0;
}/* 設備操作函數 */
static struct file_operations timer_fops = {.owner = THIS_MODULE,.open = timer_open,.unlocked_ioctl = timer_unlocked_ioctl,
};/* 定時器回調函數 */
void timer_function(unsigned long arg)
{struct timer_dev *dev = (struct timer_dev *)arg;static int sta = 1;int timerperiod;unsigned long flags;sta = !sta;		/* 每次都取反,實現LED燈反轉 */gpio_set_value(dev->led_gpio, sta);/* 重啟定時器 */spin_lock_irqsave(&dev->lock, flags);timerperiod = dev->timeperiod;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod)); }/** @description	: 驅動入口函數* @param 		: 無* @return 		: 無*/
static int __init timer_init(void)
{/* 初始化自旋鎖 */spin_lock_init(&timerdev.lock);/* 注冊字符設備驅動 *//* 1、創建設備號 */if (timerdev.major) {		/*  定義了設備號 */timerdev.devid = MKDEV(timerdev.major, 0);register_chrdev_region(timerdev.devid, TIMER_CNT, TIMER_NAME);} else {						/* 沒有定義設備號 */alloc_chrdev_region(&timerdev.devid, 0, TIMER_CNT, TIMER_NAME);	/* 申請設備號 */timerdev.major = MAJOR(timerdev.devid);	/* 獲取分配號的主設備號 */timerdev.minor = MINOR(timerdev.devid);	/* 獲取分配號的次設備號 */}/* 2、初始化cdev */timerdev.cdev.owner = THIS_MODULE;cdev_init(&timerdev.cdev, &timer_fops);/* 3、添加一個cdev */cdev_add(&timerdev.cdev, timerdev.devid, TIMER_CNT);/* 4、創建類 */timerdev.class = class_create(THIS_MODULE, TIMER_NAME);if (IS_ERR(timerdev.class)) {return PTR_ERR(timerdev.class);}/* 5、創建設備 */timerdev.device = device_create(timerdev.class, NULL, timerdev.devid, NULL, TIMER_NAME);if (IS_ERR(timerdev.device)) {return PTR_ERR(timerdev.device);}/* 6、初始化timer,設置定時器處理函數,還未設置周期,所有不會激活定時器 */init_timer(&timerdev.timer);timerdev.timer.function = timer_function;timerdev.timer.data = (unsigned long)&timerdev;return 0;
}/** @description	: 驅動出口函數* @param 		: 無* @return 		: 無*/
static void __exit timer_exit(void)
{gpio_set_value(timerdev.led_gpio, 1);	/* 卸載驅動的時候關閉LED */del_timer_sync(&timerdev.timer);		/* 刪除timer */
#if 0del_timer(&timerdev.tiemr);
#endif/* 注銷字符設備驅動 */gpio_free(timerdev.led_gpio);		cdev_del(&timerdev.cdev);/*  刪除cdev */unregister_chrdev_region(timerdev.devid, TIMER_CNT); /* 注銷設備號 */device_destroy(timerdev.class, timerdev.devid);class_destroy(timerdev.class);
}module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("huax");

關鍵代碼分析如下:

定時器設備結構體timer_dev,定義了一個定時器成員變量 timer:

/* timer設備結構體 */
struct timer_dev{dev_t devid;			/* 設備號 	 */struct cdev cdev;		/* cdev 	*/struct class *class;	/* 類 		*/struct device *device;	/* 設備 	 */int major;				/* 主設備號	  */int minor;				/* 次設備號   */struct device_node	*nd; /* 設備節點 */int led_gpio;			/* key所使用的GPIO編號		*/int timeperiod; 		/* 定時周期,單位為ms */struct timer_list timer;/* 定義一個定時器*/spinlock_t lock;		/* 定義自旋鎖 */
};

函數 timer_open,對應應用程序的 open 函數,應用程序調用 open 函數打開/dev/timer 驅動文件的時候此函數就會執行。

static int timer_open(struct inode *inode, struct file *filp)
{int ret = 0;filp->private_data = &timerdev;	/* 設置私有數據 */timerdev.timeperiod = 1000;		/* 默認周期為1s */ret = led_init();				/* 初始化LED IO */if (ret < 0) {return ret;}return 0;
}

此函數設置文件私有數據為 timerdev,并且初始化定時周期默認為 1 秒,最后調用 led_init 函數初始化 LED 所使用的 IO。

函數 timer_unlocked_ioctl,對應應用程序的 ioctl 函數,應用程序調用 ioctl函數向驅動發送控制信息,此函數響應并執行。

static long timer_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{struct timer_dev *dev =  (struct timer_dev *)filp->private_data;int timerperiod;unsigned long flags;switch (cmd) {case CLOSE_CMD:		/* 關閉定時器 */del_timer_sync(&dev->timer);break;case OPEN_CMD:		/* 打開定時器 */spin_lock_irqsave(&dev->lock, flags);timerperiod = dev->timeperiod;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerperiod));break;case SETPERIOD_CMD: /* 設置定時器周期 */spin_lock_irqsave(&dev->lock, flags);dev->timeperiod = arg;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg));break;default:break;}return 0;
}

此函數有三個參數: filp, cmd 和 arg,其中 :

  • filp是對應的設備文件,
  • cmd 是應用程序發送過來的命令信息,
  • arg 是應用程序發送過來的參數,

在本講定時器實驗里, arg 參數表示定時周期。

cmd?有三種命令:

  • CLOSE_CMD: 關閉定時器命令, 調用 del_timer_sync 函數關閉定時器。
  • OPEN_CMD:打開定時器命令,調用 mod_timer 函數打開定時器,定時周期為 timerdev 的timeperiod 成員變量,定時周期默認是 1 秒。
  • SETPERIOD_CMD:設置定時器周期命令,參數 arg 就是新的定時周期,設置 timerdev 的timeperiod 成員變量為 arg 所表示定時周期指。并且使用 mod_timer 重新打開定時器,使定時器以新的周期運行。

函數 timer_function,定時器服務函數,此函有一個參數 arg, 對應timerdev 的地址,這樣通過 arg 參數就可以訪問到設備結構體。

void timer_function(unsigned long arg)
{struct timer_dev *dev = (struct timer_dev *)arg;static int sta = 1;int timerperiod;unsigned long flags;sta = !sta;		/* 每次都取反,實現LED燈反轉 */gpio_set_value(dev->led_gpio, sta);/* 重啟定時器 */spin_lock_irqsave(&dev->lock, flags);timerperiod = dev->timeperiod;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod)); }

當定時周期到了以后此函數就會被調用。在此函數中將 LED 燈的狀態取反,實現 LED 燈閃爍的效果。

函數 timer_init,驅動入口函數里,初始化定時器,設置定時器的定時處理函數為 timer_function,另外設置要傳遞給 timer_function 函數的參數為 timerdev的地址。

	/* 6、初始化timer,設置定時器處理函數,還未設置周期,所有不會激活定時器 */init_timer(&timerdev.timer);timerdev.timer.function = timer_function;timerdev.timer.data = (unsigned long)&timerdev;

函數 timer_exit里,調用 del_timer_sync 函數刪除定時器。

	del_timer_sync(&timerdev.timer);		/* 刪除timer */
#if 0del_timer(&timerdev.tiemr);
#endif

測試 timerApp.c

測試 APP 我們要實現的內容如下:

  • 運行 APP 以后提示我們輸入要測試的命令,輸入 1 表示關閉定時器、輸入 2 表示打開定時器,輸入 3 設置定時器周期。
  • 如果要設置定時器周期的話,需要讓用戶輸入要設置的周期值,單位為毫秒。

timerApp.c文件代碼如下:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"/* 命令值 */
#define CLOSE_CMD 		(_IO(0XEF, 0x1))	/* 關閉定時器 */
#define OPEN_CMD		(_IO(0XEF, 0x2))	/* 打開定時器 */
#define SETPERIOD_CMD	(_IO(0XEF, 0x3))	/* 設置定時器周期命令 *//** @description		: main主程序* @param - argc 	: argv數組元素個數* @param - argv 	: 具體參數* @return 			: 0 成功;其他 失敗*/
int main(int argc, char *argv[])
{int fd, ret;char *filename;unsigned int cmd;unsigned int arg;unsigned char str[100];if (argc != 2) {printf("Error Usage!\r\n");return -1;}filename = argv[1];fd = open(filename, O_RDWR);if (fd < 0) {printf("Can't open file %s\r\n", filename);return -1;}while (1) {printf("Input CMD:");ret = scanf("%d", &cmd);if (ret != 1) {				/* 參數輸入錯誤 */gets(str);				/* 防止卡死 */}if(cmd == 1)				/* 關閉LED燈 */cmd = CLOSE_CMD;else if(cmd == 2)			/* 打開LED燈 */cmd = OPEN_CMD;else if(cmd == 3) {cmd = SETPERIOD_CMD;	/* 設置周期值 */printf("Input Timer Period:");ret = scanf("%d", &arg);if (ret != 1) {			/* 參數輸入錯誤 */gets(str);			/* 防止卡死 */}}ioctl(fd, cmd, arg);		/* 控制定時器的打開和關閉 */	}close(fd);
}

while(1)循環,讓用戶輸入要測試的命令,然后通過 ioctl 函數發送給驅動程序。

如果是設置定時器周期命令 SETPERIOD_CMD,那么 ioctl 函數的 arg 參數就是用戶輸入的周期值。

Makefile 文件

makefile文件只需要修改?obj-m 變量的值,改為timer.o

KERNELDIR := /home/huax/linux/linux_test/linux-imx-rel_imx_4.1.15_2.1.0_gaCURRENT_PATH := $(shell pwd)
obj-m := timer.obuild: kernel_modules
kernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

運行測試

編譯代碼:

make -j32 //編譯makefile文件
arm-linux-gnueabihf-gcc timerApp.c -o timerApp    //編譯測試程序

編譯成功以后,就會生成一個名為“timer.ko”的驅動模塊文件,和timerApp 這個應用程序。

將編譯出來的 timer.ko 和 timerApp 這兩個文件拷貝到 rootfs/lib/modules/4.1.15 目錄中,重啟開發板。

進入到目錄 lib/modules/4.1.15 中,輸入如下命令加載 timer.ko 驅動模塊:

depmod //第一次加載驅動的時候需要運行此命令
modprobe timer.ko //加載驅動

驅動加載成功以后,輸入命令來測試:

/timerApp /dev/timer

打印如下:

提示我們輸入cmd指令。

輸入“2”,打開定時器,此時 LED 燈就會以默認的 1 秒周期開始閃爍。

再輸入“3”來設置定時周期,根據提示輸入要設置的周期值:

輸入“500”,表示設置定時器周期值為 500ms,設置好以后 LED 燈就會以 500ms 為間隔,開始閃爍。

最后可以通過輸入“1”來關閉定時器。

卸載驅動的話輸入如下命令:

rmmod timer.ko

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

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

相關文章

IPTV系統:開啟視聽與管理的全新篇章

在當今數字化飛速發展的時代&#xff0c;IPTV系統正以前所未有的姿態&#xff0c;重塑著我們的視聽體驗與管理模式。它不僅僅是一套技術系統&#xff0c;更是連接信息、溝通情感、提升效率的橋梁&#xff0c;為各個領域帶來了全新的變革與發展機遇。從電視直播的角度來看&#…

PyTorch筆記9----------Cifar10圖像分類

1.圖像分類網絡模型框架解讀 分類網絡的基本結構 數據加載模塊&#xff1a;對訓練數據加載數據重組&#xff1a;組合成網絡需要的形式&#xff0c;例如預處理、增強、各種網絡處理、loss函數計算優化器 數據加載模塊 使用公開數據集&#xff1a;torchvision.datasets使用自定義…

飛凌OK3568開發板QT應用程序編譯流程

飛凌OK3568開發板QT應用程序編譯流程開發環境&#xff1a;ubuntu20.04&#xff08;主機&#xff09;、飛凌OK3568開發板一般在linux系統下開發用于ARM開發板的QT應用程序時&#xff0c;直接在主機上開發然后進行交叉編譯即可&#xff0c;但有時候ARM開發板的廠家提供的SDK中可能…

飛算JavaAI合并項目實戰:7天完成3年遺留系統重構

引言 企業數字化進程中&#xff0c;遺留系統改造始終是CIO面臨的頭號難題。某電商平臺的實踐數據顯示&#xff1a;3年以上的Java項目平均存在47%的冗余代碼&#xff0c;63%的架構設計不符合當前業務需求&#xff0c;進行系統性重構需要投入相當于原開發量200%的資源。傳統&quo…

衛星速度增量和比沖及推力之間的關系

一、定義1.1.比沖&#xff08;Isp&#xff09;&#xff1a;比沖是衡量發動機性能的重要指標&#xff0c;反映了單位重量推進劑在發動機中產生的沖量&#xff0c;單位為米/秒&#xff08;m/s&#xff09;&#xff0c;代表燃料燃燒時噴流速度。這個單位與速度單位“米/秒”相同&a…

MATLAB繪制各種心形曲線

1.方程(1)心形線的經典隱函數方程為&#xff1a;(2)參數方程&#xff08;更平滑的心形&#xff09;&#xff1a;(3)極坐標心形線(4)參數方程&#xff08;3D心形&#xff09;(5)隱函數3D心形2. MATLAB代碼clc;close all;clear all;warning off;%清除變量 rand(seed, 100); randn…

Django REST Framework視圖

Django REST Framework (DRF) 視圖類詳解DRF 提供了豐富的視圖類來構建 API&#xff0c;從基礎到高級&#xff0c;滿足不同復雜度的需求。以下是 DRF 的主要視圖類及其使用場景&#xff1a;1. 基礎視圖類APIView所有 DRF 視圖的基類&#xff0c;相當于 Django 的 View 類的增強…

Linux面試題及詳細答案 120道(1-15)-- 基礎概念

《前后端面試題》專欄集合了前后端各個知識模塊的面試題&#xff0c;包括html&#xff0c;javascript&#xff0c;css&#xff0c;vue&#xff0c;react&#xff0c;java&#xff0c;Openlayers&#xff0c;leaflet&#xff0c;cesium&#xff0c;mapboxGL&#xff0c;threejs&…

week1-[分支結構]中位數

week1-[分支結構]中位數 題目描述 給定 444 個正整數 a,b,c,da,b,c,da,b,c,d&#xff0c;輸出它們的中位數&#xff0c;答案四舍五入保留 111 位小數。 輸入格式 輸入共 111 行 444 個正整數 a,b,c,da,b,c,da,b,c,d。 輸出格式 輸出共 111 行 111 個浮點數表示答案。 樣例 #1 樣…

[激光原理與應用-259]:理論 - 幾何光學 - 平面鏡的反射、平面透鏡的折射、平面鏡的反射成像、平面透鏡的成像的規律

一、平面鏡的反射規律平面鏡的反射遵循鏡面反射定律&#xff0c;即光線在光滑表面&#xff08;反射面平整度遠大于波長&#xff09;發生反射時&#xff0c;滿足以下條件&#xff1a;反射光線、入射光線與法線共面&#xff1a;反射光線、入射光線和法線&#xff08;垂直于反射面…

相機按鍵功能解析

相機按鍵功能解析佳能相機按鍵機身背面機身正面機身頂部機身側面 佳能相機按鍵 機身背面取景器目鏡&#xff1a;用于拍攝時觀察相機形成的圖像。實拍顯示/視頻拍攝按鈕&#xff1a;按下即可開始拍攝或錄制視頻。光圈/曝光補償鍵&#xff1a;調整光圈大小和曝光補償&#xff0c;…

51單片機-驅動LED模塊教程

本章思維導圖&#xff1a; 51單片機驅動LED燈模塊 LED燈元器件簡介 LED&#xff08;Light Emitting Diode&#xff0c;發光二極管&#xff09; 是一種固態半導體器件&#xff0c;通過P-N結中電子與空穴復合直接將電能轉化為光能。其核心結構由P型半導體&#xff08;空穴主導&a…

Git 完全手冊:從入門到團隊協作實戰(2)

Hello大家好&#xff01;很高興我們又見面啦&#xff01;給生活添點passion&#xff0c;開始今天的編程之路&#xff01; 我的博客&#xff1a;<但凡. 我的專欄&#xff1a;《編程之路》、《數據結構與算法之美》、《C修煉之路》、《Linux修煉&#xff1a;終端之內 洞悉真理…

c語言中堆和棧的區別

1.棧區(stack):由編譯器自動分配釋放&#xff0c;棧主要用于存儲局部變量、函數參數、函數調用和返回信息等。其操作方式類似于數據結構中的棧。 2.堆區(heap):一般由程序員分配釋放&#xff0c;若程序員不釋放&#xff0c;則可能會引起內存泄漏。注堆和數據結構中的堆棧不一樣…

華為實驗WLAN 基礎配置隨練

業務vlan 20 192.168.20.x管理vlan 100 192.168.100.x步驟① 網絡互通Core sw:vlan batch 20 100 dhcp enable int vlanif 20IP add 192.168.20.1 24dhcp select interfaceinterface GigabitEthernet0/0/1/2port link-type trunkport trunk pvid vlan 100port trunk allow-pas…

CMake 如何查找 Python2和Python3

問題 在一個CMakeLists.txt文件里面看到了下面的這句話 find_package(Python2 COMPONENTS Interpreter Development NumPy)這個好有趣啊&#xff0c;Python2也是一個C的庫嗎&#xff0c;也有Python2Config.cmake或者FindPython2.cmake? 回答 find_package(Python2 COMPONENTS …

心靈筆記:刻意練習

心靈筆記&#xff1a;刻意練習提要 所有人都以為“杰出”源于“天賦”&#xff0c;而“天才”卻說&#xff1a;我的成就源于“正確的練習”&#xff01; 定義&#xff1a;刻意練習是一種有目的、有方法、能帶來能力持續提升的結構化訓練方式&#xff0c;它并非簡單的重復勞動&a…

langchain入門筆記03:使用fastapi部署本地大模型后端接口,優化局域網內的問答響應速度

文章目錄前言一、fastapi的簡單入門1&#xff1a;安裝必要的包&#xff08;python3.11&#xff09;&#xff1a;2&#xff1a;快速搭建一個fastapi&#xff1a;二、提升問答的響應速度1. fastapi部署后端接口&#xff0c;在局域網內訪問的方法2. 局域網內的測試&#xff1a;“未…

【CDA 新一級】學習筆記第1篇:數據分析的時代背景

作者&#xff1a;CDA持證人 張九領我們要學習數據分析&#xff0c;就要從當前時代的數據特點&#xff0c;找到在時代特點下企業需要數據分析的痛點&#xff0c;然后理解數據分析在企業中的作用。當前時代&#xff0c;數據分析的特征是哪些呢&#xff1f;我們用VUCA來概括數據分…

Vite 為什么比 Webpack 快?原理深度分析

Hi&#xff0c;我是布蘭妮甜 &#xff01;在現代前端開發中&#xff0c;構建工具的性能直接影響開發體驗和生產力。Webpack 作為傳統打包工具的代表&#xff0c;長期以來主導著前端構建領域&#xff0c;而 Vite 作為新一代的前端構建工具&#xff0c;憑借其出色的開發服務器啟動…