Linux驅動學習筆記(四)

高級字符設備進階

1.一個完整的IO過程包含以下幾個步驟:1應用程序向操作系統發起IO調用請求(系統調用);2操作系統準備數據,把IO設備的數據加載到內核緩沖區;3操作系統拷貝數據,把內核緩沖區的數據從內核空間拷貝到應用空間。IO模型有阻塞IO、非阻塞IO、IO多路復用、信號驅動IO、異步IO,其中前四個被稱之為同步IO,只有最后一種是真正的異步IO,因為無論是多路復用IO還是信號驅動模型,IO操作的第2個階段都會引起用戶線程阻塞,也就是內核進行數據拷貝的過程都會讓用戶線程阻塞。示意圖如下:

  • 阻塞IO:

  • 非阻塞IO:

  • IO多路復用:IO多路復用可以實現一個進程監視多個文件描述符,一旦其中一個文件描述符準備就緒,就通知應用程序進行相應的操作

  • 信號驅動IO:信號驅動IO不需要應用程序去查詢設備的狀態。一旦設備準備就緒,就觸發SIGIO信號,該信號會通知應用程序數據已經到來

  • 異步IO:

2.等待隊列:等待隊列是內核實現阻塞和喚醒的內核機制,等待隊列以循環鏈表為基礎結構,鏈表頭和鏈表項分別為等待隊列頭和等待隊列元素,整個等待隊列由等待隊列頭進行管理。等待隊列頭使用結構體 wait_queue_head_t來表示, 等待隊列頭就是一個等待隊列的頭部,Linux中與等待隊列定義在文件include/linux/wait.h里面,如下圖:

結構體wait_queue_entry_t表示等待隊列項,結構體內容如下:

list_head的結構為:

所以整個等待隊列的結構如下圖所示:

初始化一個等待隊列頭的方法有兩種:1先定義一個等待隊列頭wait_queue_head_t head;,然后用void init_waitqueue_head(wait_queue_head_t *q)函數將其初始化;2使用宏定義DECLARE_WAIT_QUEUE_HEAD(name)一次性定義并初始化一個等待隊列頭,這里的name的類型是結構體 wait_queue_head_t。等待隊列項一般使用宏DECLARE_WAITQUEUE(name, task)來創建,name就是等待隊列項的名字,其類型是結構體wait_queue_entry_t。task表示這個等待隊列項屬于哪個任務(進程),一般設置為current,在Linux內核中current相當于一個全局變量,表示當前進程。因此DECLARE_WAITQUEUE就是給當前正在運行的進程創建并初始化了一個等待隊列項。例如:DECLARE_WAITQUEUE(wait,current);表示給當前正在運行的進行創建一個名為wait的等待隊列項,再使用add_wait_queue(&wq,&wait);表示將wait這個等待隊列項加到wq這個等待隊列當中。void add_wait_queue(wait_queue_head_t *q, wait_queue_entry_t *wait)函數可將隊列項加入某個隊列中,q是待加入隊列的等待隊列(頭),wait是要加入的等待隊列項。void remove_wait_queue(wait_queue_head_t *q, wait_queue_entry_t *wait)函數用于從等待隊列中刪除等待隊列項,其中q是目標隊列的等待隊列頭,wait是要刪除的等待隊列項。當設備可以使用的時候就要喚醒進入休眠態的進程,喚醒可以使用如下兩個函數:wak_up(wait_queue_head_t *q)函數用于喚醒所有休眠進程;wake_up_interruptible(wait_queue_head_t *q)用于喚醒可中斷的休眠進程。等待事件:1 wait_event (wq,condition)宏,不可中斷的阻塞等待,讓調用進程進入不可中斷的睡眠狀態, 在等待隊列里面睡眠直到condition變成真,被內核喚醒,這里的wq的類型是結構體wait_queue_head_t,在condition為1時該函數不會阻塞而是會立即返回。2 wait_event_interruptible(wq,condition)宏,可中斷的阻塞等待,讓調用進程(當前進程)進入可中斷的睡眠狀態,直到condition 變成真被內核喚醒或信號打斷喚醒,這里的wq的類型是結構體wait_queue_head_t,在condition為1時該函數不會阻塞而是會立即返回。等待隊列的使用方法為:1.初始化等待隊列頭,并將條件置成假(condition=0);2.在需要阻塞的地方調用wait_event(),使進程進入休眠;3.當條件滿足時,需要解除休眠,先將條件置成真(condition=1),然后調用wake_up函數喚醒等待隊列中的休眠進程(可參考訊為Linux驅動視頻第四期P2)。

3.應用程序可以使用如下所示示例代碼來實現阻塞訪問:fd=open("/dev/xxx_dev”,0RDWR);,ret =read(fd,&data,sizeof(data));,可以看出對于設備驅動文件的默認讀取方式就是阻塞式的。如果應用程序要采用非阻塞的方式來訪問驅動設備文件,可以使用如下所示代碼:fd =open("/dev/xxx_dev",O_RDWR|O_NONBLOCK);、ret = read(fd, &data, sizeof(data)),上述代碼在使用open數打開“/dev/xxx_dev”設備文件的時候添加了參數“O_NONBLOCK”表示以非阻塞方式打開設備,這樣從設備中讀取數據的時候就是非阻塞方式的了。在驅動程序中的讀寫函數里面,他們的參數中有一個struct file *file,這個結構體有一個成員file->flags用來記錄打開文件時傳入的O_RDWR|O_NONBLOCK,可以在驅動程序中根據這個來判斷是否是以非阻塞方式實現IO(可參考訊為Linux驅動視頻第四期P3)。

4.在應用層使用select(int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);)和poll(int poll(struct pollfd *fds, nfds_t nfds, int timeout);)等系統調用會觸發設備驅動中的poll()函數被執行,file_operations結構體中有一個函數指針成員poll:__poll_t (*poll) (struct file *, struct poll_table_struct *);,一般在驅動程序中將其綁定到驅動程序中自定義的poll函數以實現應用層和驅動層的互通。驅動中poll函數要進行兩項工作,第一項工作:對可能引起設備文件狀態變化的等待隊列調用poll_wait(void poll_wait(struct file *filp, wait_queue_head_t *queue, poll_table *wait);),該函數將對應的等待隊列頭添加到poll_table,等待特定事件的發生(如設備準備好讀取或寫入數據),poll_table 結構用于跟蹤該進程的等待狀態。poll_wait 函數本身是非阻塞的,它只是將當前進程加入到等待隊列中,實際上阻塞當前進程的工作是在內核中的內核中的其他函數(do_poll)中完成的,事件發生時需要調用wake_up_interruptible等函數來喚醒被阻塞的進程。當應用層調用 poll 時,內核會依次檢查文件描述符對應的設備是否已經準備好(例如是否可以讀取數據,當應用層調用poll()函數時,它會遍歷傳入的文件描述符集合,并將每個文件描述符對應的設備驅動的poll()函數調用一遍)。如果設備尚未準備好(例如沒有數據可讀),進程會被加入到等待隊列中,并且會進入阻塞狀態。第二項工作:返回表示是否能對設備進行無阻塞讀寫訪問的掩碼。Linux中與poll有關的定義在include/linux/poll.h中。如下圖所示(可參考訊為Linux驅動視頻第四期P4,及實驗18_poll):

5.應用程序使用信號驅動IO的步驟:1注冊信號處理函數,應用程序使用signal函數(sighandler_t signal(int signum, sighandler_t handler);)來注冊SIGIO信號的信號處理函數,SIGIO信號用于通知進程與IO相關的事件。這是一個事件驅動信號,通常用于告知進程,某個文件描述符已經準備好進行IO操作(例如,數據可以讀取或可以寫入),使得進程可以在不阻塞的情況下處理IO操作;2設置能夠接收這個信號的進程,通過fcntl(fd,F_SETOWN,getpid());實現,這句的作用是將指定的文件描述符fd的IO操作的信號通知目標設置為當前進程。這意味著,當該文件描述符上的IO事件(如可讀、可寫等)發生時,操作系統會發送一個信號(通常是SIGIO)給指定的進程;3開啟信號驅動IO,通常使用fcnt1的F_SETFL命令打開FASYNC標志,如:flags=fcntl(fd,F_GETFD);,fcntl(fd,F_SETFL,flags|FASYNC);。驅動程序中要執行以下步驟:1當應用程序開啟信號驅動IO時,會觸發驅動中的fasync函數,所以首先將file_operations結構體中的成員fasync(int (*fasync) (int, struct file *, int);)綁定到在驅動程序中自定義的fasync函數。2在自定義的fasync函數中調用fasync_helper函數來操作fasync_struct結構體,fasync_helper函數原型為int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp),該函數用于管理與文件描述符相關的IO事件,它根據文件描述符的標志位,控制是否將IO事件的信號(如SIGIO)發送給進程,fasync_helper會在文件描述符注冊時,通過fasync_struct將文件描述符與進程進行綁定,確保在文件描述符準備好時,能夠通知進程(通過發送SIGIO信號)。其中fd是這是需要設置IO的文件描述符,filp是指向struct file結構體的指針,該結構體代表一個已經打開的文件,filp是內核中的文件句柄,用于表示內核中的文件對象,on參數指定是否啟用IO,fapp是一個指向fasync_struct指針的指針,fasync_struct用于存儲與IO相關的數據,如進程的異步通知隊列。3當設備準備好的時候,驅動程序需要調用kill_fasync函數通知應用程序,此時應用程序的SIGIO信號處理函數就會被執行,kill_fasync負責發送指定的信號。函數原型為void kill_fasync(struct fasync_struct **fp, int sig, int band);,其中fp是要操作的fasync_struct,sig是要發送的信號,band在可讀的時候設置成POLLIN,可寫的時候設置成POLLOUT(可參考訊為Linux驅動視頻第四期P5)。

6.Linux內核定時器是一種基于未來時間點的計時方式,基于未來時間點的計時是以當前時刻為計時開始的時間點,以未來的某一時刻為計時的終點。比如,現在是早上7點,我用手機定時五分鐘,定時時間就是7點+5分鐘=7點5分。內核定時器的精度不高,所以不能作為高精度定時器使用,并且內核定時器不是周期性運行的,到計時終點后會自動關閉。如果想要實現周期性定時,就需要定時處理函數中重新開啟定時器。Linux內核使用timer_list結構體表示內核定時器(這個定時器只能用在內核,用戶空間的定時器相關函數是timer_create()、timer_settime()等),timer_list定義在include/linux/timer.h頭文件當中,定義如下:

在timer_list結構體中,expires為計時終點的時間,單位是節拍數。Linux內核中有一個宏HZ,這個宏用來表示一秒鐘對應的節拍的數量,利用這個宏就可以把時間轉換成節拍數。比如定時一秒鐘換成節拍數就是expires=jiffies+5*HZ,其中jiffies為系統當前時間對應的節拍數。宏HZ的值是可以設置的,也就是說一秒鐘對應多少個節拍數是可以設置的。進入內核源碼目錄,打開menuconfig圖形化配置界面,按照->Kernel Features->Timer frequency(<choice>[=y])就可以設置。全局變量jiffies用來記錄自系統啟動以來產生的節拍的總數。系統啟動時,內核將該變量初始化為0,此后每次時鐘中斷處理程序都會增加該變量的值。 因為一秒內時鐘中斷的次數為HZ(節拍數),所以jiffies一秒內增加的值也就為HZ(節拍數),系統運行時間以秒為單位計算, 就等于 jiffies/HZ。jiffies=seconds*HZ。jiffies定義在文件include/linux/jiffies.h 中,定義如下(jiffies_64和jiffies分別對應64和32位系統):

與全局變量jiffies相關的轉換函數如下圖:

內核定時器的使用步驟為:1初始化內核定時器(struct timer_list),可以直接使用宏DEFINE_TIMER(_name, _function)來初始化內核定時器(對應于5以上的內核版本),其中_name為定時器名字,_function為回調函數,然后用_name.expires=jiffies_64 +msecs_to_jiffies(ms);來設置定時時間。2調用void add_timer(struct timer_list *timer)函數向Linux內核注冊定時器。3在驅動出口函數中調用int del_timer(struct timer_list * timer)刪除定時器。4如果想修改定時時間,可以調用int mod_timer(struct timer_list *timer, unsigned long expires)進行修改(可參考訊為Linux驅動視頻第四期P7、P8)。

7.Linux中的dmesg命令用于顯示內核的環形緩沖區(ring buffer)中的消息,這些消息通常是內核在系統啟動時、驅動加載時、硬件設備初始化時等階段產生的日志信息。通過dmesg命令,可以查看到系統啟動過程中的各種硬件設備識別、驅動加載以及其他內核層面的信息。dmesg命令的參數如下圖所示:

在ubuntu中直接執行insmod命令安裝內核模塊是看不到打印信息的,而cat /proc/kmsg 命令用于查看 Linux 系統中的內核日志,它直接從 /proc/kmsg 文件讀取內核緩沖區中的日志消息,實時顯示系統運行時內核產生的實時日志輸出在終端。所以可以開啟兩個終端在其中一個中執行cat /proc/kmsg 命令,在另一個終端進行內核模塊的安裝就能看到打印信息了。

8.Linux內核日志的打印是有打印等級的,可以通過調整內核的打印等級來控制打印日志的輸出,使用命令cat /proc/sys/kernel/printk可以查看默認的打印等級。如下圖所示:

打印等級有四個數字,這四個數字分別代表console_loglevel(當前控制臺日志等級,它控制了內核消息被輸出到控制臺的最低日志等級)、default_message_loglevel(默認消息等級,若沒指定輸出日志的等級,內核將使用這個默認值)、minimum_console_loglevel(控制臺日志等級可被設置的最小值(最高優先級))、default_console_loglevel(默認控制臺日志的等級)。這四個等級定義在kernel/printk/printk.c文件當中,如下圖所示:

Linux內核提供了8中不同的日志級別,分別對應0到7,數字越小級別就越高,定義在include/linux/kern_levels.h文件當中。如下圖:

在內核打印的時候,只有數值小于(級別高)當前系統的設置的打印等級,打印信息才可以被顯示到控制臺上,大于或者等于(級別低)的打印信息不會被顯示到終端上。可以用以下三種方法來修改Linux內核打印等級:1通過make menuconfig圖形化配置界面修改默認的日志級別default_console_loglevel,menuconfig圖形化配置界面路徑:Kernel hacking ->printk and dmesg options ->Default message log level();2在調用printk的時候設置打印等級,例如printk(KERN_EMERG "hello!\n”);;3使用echo直接修改打印等級,具體步驟為:首先可以用命令cat /proc/sys/kernel/printk查看內核打印等級,然后按需求修改控制臺打印等級。例如要屏蔽所有打印,只需要將第一個數值調整到0即可,使用命令為echo 0 4 1 7 >/proc/sys/kernel/printk。再如要打開控制臺的所有打印,使用命令為echo 7 4 1 7 >/proc/sys/kernel/printk。

9.用戶空間的lseek函數:off_t lseek(int fd,off_t offset,int whence)其中,fd為文件描述符,offset為偏移量(單位為字節,可正可負),whence的值可為SEEK_SET、SEEK_CUR、SEEK_END分別表示文件開頭、文件當前偏移位置、文件結尾,這個函數可以用來改變文件的當前偏移位置,若成功返回文件當前相對于文件開頭的偏移量失敗則返回-1,同一個文件“讀”和“寫”使用的是同一偏移位置(lseek函數還可以用來查詢當前文件的大小,如lseek(fd,0,SEEK_END)返回的就是當前文件大小,還可以用此函數拓展文件大小,但是要想真正拓展文件大小,必須引起IO操作)。在用戶空間中調用lseek函數會調用驅動中的file_operations結構體中的成員loff_t (*llseek) (struct file *, loff_t, int);,可以在驅動程序中自定義llseek函數并將其綁定到file_operations的成員llseek,如下圖:

同時需要對驅動中的read和write進行修改,驅動中的read函數原型為ssize_t (*read)(struct file *filp,char __user *buffer,size_t size,loff_t *p);,其中file指向打開的文件,buffer為存放數據的緩沖區,size為要讀取的數據長度,p為讀的位置,也就是相對于文件的開頭的偏移,在讀完數據以后,這個指針要進行移動,動的值為讀取信息的長度。file結構體中有一個成員file-> f_pos指向文件當前偏移量。一個read函數示例如下圖:

驅動中的write函數原型為ssize_t (*write)(struct file *filp, const char __user *buffer, size_t count, loff_t *ppos);,其中file指向打開的文件,buffer為寫入數據的緩沖區,size為要寫入的數據長度,p為寫的位置,也就是相對于文件的開頭的偏移。下圖是一個例子(可參考訊為Linux驅動視頻第四期P11):

10.Linux中對非數據的操作通常通過ioctl操作來實現。應用層的ioctl函數在頭文件sys/ioctl.h中定義,函數原型為int ioctl(int fd,unsigned int cmd, unsigned long request, ...);,其中fd為打開設備節點獲得的文件描述符,cmd為給驅動傳遞的命令,后面為可變參數,該函數調用成功返回0失敗返回-1。調用用戶空間的ioctl最終會調用驅動程序中file_operations結構體中的成員long (*unlocked_ioctl) (struct file *file, unsigned int cmd, unsigned long arg);,unlocked_ioctl的三個參數和ioctl的參數是對應的。ioctl或unlocked_ioctl函數中參數cmd命令的格式如下圖:

上圖中,設備類型代表一類設備,一般用一個字母或者一個8bit的數字來表示;序列號代表的是這類設備的第幾個命令;方向表示命令的方向,如:只讀(10)、只寫(01)、寫讀(11)、無數據(00);數據大小表示用戶數據的大小,注意這里傳遞的不是數字,而是數據類型,比如要傳遞四個字節,就可以寫入int。Linux定義了幾個宏來構建和分解上面的cmd,ioctl的參數cmd的合成宏包括:合成沒有數據傳遞的命令的宏_IO(type,nr)、合成從驅動中讀取數據的命令的宏_IOR(type,nr,size)、合成向驅動中寫數據的命令的宏_IOW(type,nr,size)、合成先寫入數據再讀取數據的命令的宏_IOWR(type,nr,size),其中type為設備類型,nr為序列號,size為數據尺寸。ioctl的參數cmd的分解宏包括:獲取方向的宏_IOC_DIR(nr)、獲取設備類型的宏_IOC_TYPE(nr)、獲取序列號的宏_IOC_NR(nr)、獲取數據尺寸的宏_IOC_SIZE(nr),其中nr為前面合成的cmd命令。當想要通過ioctl函數傳遞多個參數時,可以將這些參數封裝成一個結構體,然后傳入結構體的地址即可(可參考訊為Linux驅動視頻第四期P15)。

11.可以通過靜態庫將驅動程序進行封裝,提供API函數供應用層使用(可參考訊為Linux驅動視頻第四期P17)。靜態庫的制作及使用:首先把將要制作成靜態庫的.c文件轉換成.o文件:如gcc -c add.c -o add.o,然后使用ar工具制作靜態庫:如ar -rcs libname.a add.o sub.o div.o(-r選項會將指定的目標文件add.o、sub.o、div.o插入到 libname.a文件中。如果這些目標文件已經存在于該文件中,則會被更新,如果libname.a文件不存在,則使用-c將創建一個新的文件,-s表示創建文件時加入符號表,該符號表用于鏈接時解析符號引用),最后編譯靜態庫到可執行文件中:如gcc test.c libname.a -o a.out(注意在命令中靜態鏈接庫位于源程序后面,否則編譯不成功,這與鏈接器工作時的算法有關,源程序用到的靜態庫函數需要聲明,否則會報警告,一般可以寫入頭文件來聲明)(靜態庫都以.a結尾,默認庫名以lib開頭,如果在生成可執行文件中沒有加入靜態庫,而源程序需要用到靜態庫的某些函數,則編譯時會在鏈接階段報錯,一般程序編譯過程中只會在4個步驟中的編譯階段或鏈接階段報錯,前者有報錯的行號后者沒有)。

12.優化驅動的穩定性和效率:通過在一些函數調用后面添加錯誤處理可以增加程序的穩定性,但這樣會導致增加許多if條件分支。現在的CPU都有I-Cache和流水線機制,運行當前的指令時,I-Cache會預讀取后面的指令,從而提升效率。但是如果條件分支的結果是跳轉到了其他指令,那預取下一條指令就浪費時間了。如果使用likely和unlikely來讓編譯器總是將大概率執行的代碼放在靠前的位置,就可以提高效率。likely(condition)表示 condition 這個條件是大概率為真的,即程序流大概率會走到這個分支。unlikely(condition)表示 condition 這個條件是大概率為假的,即程序流大概率不會走到這個分支。如下圖是一個例子:

access_ok(addr, size)函數可用于檢查用戶空間指針是否可用,其中addr為用戶空間的指針變量,指向一個要檢查的內存塊的開始,size是要檢查的內存塊的大小,如果待檢查用戶空間的內存塊可用該函數返回真,否則返回假。

13.內核驅動程序中添加調試信息的幾種方法:

  • 使用printk函數進行打印
  • 使用dump_stack()函數:dump_stack() 會打印出當前執行棧的調用信息,顯示從當前函數到棧頂的所有函數調用路徑
  • 使用WARN(condition, fmt...) 和 WARN_ON(condition):這是Linux內核中的調試宏,主要用于在代碼中進行條件檢查,當某個條件不滿足時,向內核日志輸出警告信息,并可以觸發調試處理。其中condition是要檢查的條件,如果條件為真,宏會打印警告信息。fmt...為可變參數,用于格式化打印的消息,類似 printf 的格式。這兩個宏的打印信息內容與dump_stack()函數類似。WARN_ON(condition)的返回值是true或false,具體取決于條件condition是否為真
  • 使用BUG()和BUG_ON(condition)函數:它們都是 Linux 內核中的調試宏,用于在遇到嚴重錯誤時觸發內核的調試功能,主要目的是標識和報告致命的錯誤,并在發生錯誤時終止內核的執行。BUG() 宏用于在發生嚴重錯誤時立即觸發內核崩潰,并生成一個內核 panic,它沒有條件檢查,因此它總是會執行,并且不會返回。BUG_ON(condition) 是一個帶有條件檢查的宏,用于在給定條件condition為真時觸發內核崩潰。這兩個宏會將寄存器、堆棧等內容都打印出來
  • 使用panic(fmt...)函數:用于在發生嚴重錯誤時觸發內核崩潰,它輸出一條格式化的錯誤信息,并終止內核的執行。panic() 的調用通常標志著程序進入了一個不可恢復的錯誤狀態,系統會停止執行,并且可能會生成一個內核轉儲供后續分析

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

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

相關文章

el-table的行向上移動向下移動,刪除選定行

<template><el-table :data"tableData" border style"width: 100%"><!-- 其他列 --><el-table-column label"ID"><template slot-scope"scope">{{ scope.$index }}</template></el-table-colu…

人工智能之數學基礎:矩陣的降維

本文重點 在現實世界中,我們經常會遇到高維數據。例如,圖像數據通常具有很高的維度,每個像素點都可以看作是一個維度。高維數據不僅會帶來計算和存儲上的困難,還可能會導致 “維數災難”,即隨著維度的增加,數據的稀疏性和噪聲也會增加,從而影響數據分析的效果。因此,我…

2025年,電腦還需要分區嗎?

隨著2025年的到來&#xff0c;電腦存儲空間已經不像以前那么金貴&#xff0c;固態硬盤&#xff08;SSD&#xff09;容量更大、速度更快&#xff0c;云存儲也成了日常標配。許多人開始質疑&#xff1a;電腦還需要像以前那樣分區嗎&#xff1f; 一、分區到底是什么意思&#xff…

Springboot項目集成maven-assembly-plugin進行打包

通常我們將應用部署到服務器的某個目錄下&#xff0c;一般情況下我們會提供像target&#xff08;存放應用jar包&#xff09;&#xff0c;bin&#xff08;項目啟動/停止腳本&#xff09;&#xff0c;config&#xff08;項目配置文件&#xff09;&#xff0c;logs&#xff08;項目…

CSS3 基礎布局技術與響應式設計

1. CSS3 基礎與布局技術 1.1 Flexbox 布局 Flexbox 是一種一維布局模型&#xff0c;適合用于在一個方向上&#xff08;行或列&#xff09;排列元素。 基本概念&#xff1a; 容器&#xff08;Container&#xff09;&#xff1a;應用 display: flex; 的元素。項目&#xff08…

鴻蒙NEXT項目實戰-百得知識庫01

代碼倉地址&#xff0c;大家記得點個star IbestKnowTeach: 百得知識庫基于鴻蒙NEXT穩定版實現的一款企業級開發項目案例。 本案例涉及到多個鴻蒙相關技術知識點&#xff1a; 1、布局 2、配置文件 3、組件的封裝和使用 4、路由的使用 5、請求響應攔截器的封裝 6、位置服務 7、三…

【DeepSeek應用】本地部署deepseek模型后,如何在vscode中調用該模型進行代碼撰寫,檢視和優化?

若已成功在本地部署了 DeepSeek 模型(例如通過 vscode-llm、ollama 或私有 API 服務),在 VS Code 中調用本地模型進行代碼撰寫、檢視和優化的完整流程如下: 1. 準備工作:確認本地模型服務狀態 模型服務類型: 若使用 HTTP API 服務(如 FastAPI/Flask 封裝),假設服務地址…

jenkins 配置郵件問題整理

版本&#xff1a;Jenkins 2.492.1 插件&#xff1a; A.jenkins自帶的&#xff0c; B.安裝功能強大的插件 配置流程&#xff1a; 1. jenkins->系統配置->Jenkins Location 此處的”系統管理員郵件地址“&#xff0c;是配置之后發件人的email。 2.配置系統自帶的郵件A…

Android Coil3階梯preload批量Bitmap拼接扁平寬圖,Kotlin

Android Coil3階梯preload批量Bitmap拼接扁平寬圖&#xff0c;Kotlin <uses-permission android:name"android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name"android.permission.READ_EXTERNAL_STORAGE" /><uses-p…

C++基礎 [八] - list的使用與模擬實現

目錄 list的介紹 List的迭代器失效問題 List中sort的效率測試 list 容器的模擬實現思想 模塊分析 作用分析 list_node類設計 list 的迭代器類設計 迭代器類--存在的意義 迭代器類--模擬實現 模板參數 和 成員變量 構造函數 * 運算符的重載 運算符的重載 -- 運…

【系統架構設計師】操作系統 - 特殊操作系統 ③ ( 微內核操作系統 | 單體內核 操作系統 | 內核態 | 用戶態 | 單體內核 與 微內核 對比 )

文章目錄 一、微內核操作系統1、單體內核 操作系統2、微內核操作系統 引入3、微內核操作系統 概念4、微內核操作系統 案例 二、單體內核 與 微內核 對比1、功能對比2、單體內核 優缺點3、微內核 優缺點 一、微內核操作系統 1、單體內核 操作系統 單體內核 操作系統 工作狀態 : …

系統思考:惡性循環

去年&#xff0c;我給一家知名人力資源公司交付了兩個項目——一個在6月&#xff0c;另一個在8月&#xff0c;至今半年多了依然沒有收到課酬。催促多次&#xff0c;得到的答復卻各式各樣&#xff1a;銷售說老板卡了額度&#xff0c;老板說具體情況還需了解。每一次的推諉&#…

基于springboot的房屋租賃系統(008)

摘 要 社會的發展和科學技術的進步&#xff0c;互聯網技術越來越受歡迎。網絡計算機的生活方式逐漸受到廣大人民群眾的喜愛&#xff0c;也逐漸進入了每個用戶的使用。互聯網具有便利性&#xff0c;速度快&#xff0c;效率高&#xff0c;成本低等優點。 因此&#xff0c;構建符…

視頻翻譯器免費哪個好?輕松玩轉視頻直播翻譯

你是不是覺得看外語視頻很麻煩&#xff1f;每次遇到喜歡的外語電影、電視劇或動漫&#xff0c;總是要等字幕組的翻譯&#xff0c;或者因為語言不通而錯過精彩的情節。 這個時候&#xff0c;掌握多語種直播翻譯方案就顯得尤為重要&#xff0c;有了實時字幕&#xff0c;看外語視…

在cherry studio中使用MCP——本地文件管理FileSystem

cherry studio是一款開源的AI助手工具&#xff0c;可以便捷地利用API訪問各種LLM&#xff0c;有關cherry studio的使用這里不再多說&#xff0c;可以參考這篇文章https://blog.csdn.net/m0_65494437/article/details/145478823 官網&#xff1a;https://cherry-ai.com/ MCP是什…

從點燈開始的51單片機生活

陵谷紛紜新事改&#xff0c;筑臺土石未應遲。 目錄 sfr與sbit&#xff1f;不靠定時器的delay_ms延時函數所謂寄存器 sfr與sbit&#xff1f; 這第一課咱們主要來先理解一下sfr與sbit&#xff0c;以下可能是咱們這些新手朋友常見的點燈代碼&#xff1a; #include<regx52.h&g…

Django系列教程(13)——Cookie和Session應用場景及案例

目錄 什么是cookie&#xff0c;cookie的應用場景及缺點 Django中如何使用cookie Cookie使用示例 什么是session及session的工作原理 Django中如何使用會話session Session使用示例 小結 HTTP協議本身是”無狀態”的&#xff0c;在一次請求和下一次請求之間沒有任何狀態保…

c++類和對象(下篇)下

下面就來補充一下c雷和對象最后一點內容. 首先先補充一下上一篇博客上c類和對象(下篇)上-CSDN博客最后學習的靜態成員變量的小練習求123...n_牛客題霸_牛客網 (nowcoder.com)下面就是題解.靈活的運用了靜態成員變量不銷毀的特點,建立數組利用構造函數來完成n次相加. class A{ …

《TCP/IP網絡編程》學習筆記 | Chapter 19:Windows 平臺下線程的使用

《TCP/IP網絡編程》學習筆記 | Chapter 19&#xff1a;Windows 平臺下線程的使用 《TCP/IP網絡編程》學習筆記 | Chapter 19&#xff1a;Windows 平臺下線程的使用內核對象內核對象的定義內核對象歸操作系統所有 基于 Windows 的線程創建進程與線程的關系Windows 中線程的創建方…

分布式事務解決方案:Seata原理詳解與實戰教程

一、為什么需要Seata&#xff1f; 在微服務架構中&#xff0c;跨服務的事務管理成為核心痛點&#xff1a; 傳統事務失效&#xff1a;服務拆分導致無法使用本地事務數據不一致風險&#xff1a;網絡抖動、服務宕機等情況導致數據錯亂復雜場景處理難&#xff1a;涉及多個數據庫、…