操作系統 4.1-I/O與顯示器

外設工作起來

操作系統讓外設工作的基本原理和過程,具體來說,它概括了以下幾個關鍵步驟:

  1. 發出指令:操作系統通過向控制器中的寄存器發送指令來啟動外設的工作。這些指令通常是通過I/O指令(如out指令)來實現的。

  2. 控制器處理:控制器接收到指令后,根據寄存器中的內容來操控硬件。控制器內部可能包含有計算電路,能夠根據CPU發出的指令來具體操作設備。

  3. 中斷處理:一旦外設完成其任務,它會向CPU發送一個中斷信號。CPU接收到中斷后,會暫停當前的工作,轉而處理中斷,這可能涉及到數據傳輸等操作。

  4. 統一的文件接口:為了讓外設的使用變得簡單,操作系統提供了一種統一的視圖,即文件視圖。這意味著,無論操作哪種外設,用戶都可以通過統一的文件操作接口(如openreadwrite等)來進行。

總結來說,操作系統讓外設工作的核心原理非常簡單,即通過發出指令讓外設工作,然后編寫中斷處理程序來響應外設完成任務后的中斷信號。此外,操作系統通過提供統一的文件接口,使得用戶可以方便地使用各種外設,而無需關心底層的硬件細節。接下來我們將圍繞這三個方面講解。

外設工作的開始

提取的代碼如下:

int fd = open("/dev/xxx");
for (int i = 0; i < 10; i++) {write(fd, i, sizeof(int));
}
close(fd);

外設工作的開始可以總結為以下幾個步驟:

  1. 打開設備

    • 使用open函數打開指定的設備文件(/dev/xxx),這個文件是系統中外設的接口。

    • open函數返回一個文件描述符fd,用于后續對該設備的操作。

  2. 數據傳輸

    • 通過write函數將數據傳輸到外設。在這個例子中,數據是一個整數i,大小為sizeof(int)

    • 這個過程在一個循環中進行,循環10次,每次寫入一個整數。

  3. 關閉設備

    • 完成數據傳輸后,使用close函數關閉設備文件,釋放文件描述符fd所占用的資源。

文件視圖概念

文件視圖是操作系統提供的兩大視圖之一,它將所有的I/O設備統一抽象為文件,使得用戶可以通過一組標準的文件操作接口(如openreadwriteclose)來訪問和操作這些設備。這種抽象極大地簡化了用戶與硬件設備的交互,并隱藏了底層硬件的具體細節。

在文件視圖中,操作系統將設備屬性數據和設備驅動程序結合在一起,通過系統調用接口與用戶空間進行交互。當用戶程序調用這些系統調用時,操作系統會進行解釋,并將其轉換為對特定設備的命令。這些命令隨后被發送到相應的設備控制器(如鍵盤控制器或磁盤控制器),并由控制器執行具體的硬件操作。

文件視圖的樣貌可以總結如下:

  1. 統一接口:無論什么設備,用戶都通過統一的系統調用接口(openreadwriteclose)來進行操作。

  2. 設備抽象:不同的設備對應不同的設備文件(如/dev/xxx),操作系統根據這些設備文件找到控制器的地址、內容格式等信息。

  3. 設備驅動:設備驅動程序是操作系統與硬件設備之間的橋梁,它負責將系統調用轉換為對特定硬件的操作。

  4. 中斷處理:當設備完成操作后,會通過中斷機制通知操作系統,操作系統再進行相應的中斷處理。

  5. I/O系統:操作系統中的I/O系統負責管理設備屬性數據和設備驅動程序,協調用戶程序與硬件設備之間的交互。

通過這種文件視圖,操作系統為用戶提供了一個簡單、統一的方式來操縱外設,同時隱藏了底層硬件操作的復雜性。這種抽象不僅簡化了用戶程序的開發,還提高了系統的可移植性和可擴展性。

代碼思路講解

提取的代碼如下:

int sys_write(unsigned int fd, char *buf, int count) {struct file* file;file = current->filp[fd]; ?// fd是找到file的索引inode = file->f_inode; ? ? // file的目的是得到inode
}

總結顯示器輸出的過程:

  1. 系統調用

    • 用戶程序通過printf函數輸出信息,printf函數內部會先創建一個緩存區(buf),將格式化后的輸出寫入該緩存區。

  2. 寫入系統調用

    • printf函數最終會調用write系統調用,將緩存區中的數據寫入指定的文件描述符(fd)。

  3. 文件描述符索引

    • 在Linux內核中,sys_write函數通過文件描述符(fd)找到對應的文件結構體(file)。文件描述符是用戶空間和內核空間之間的索引。

  4. 獲取inode

    • 從文件結構體中獲取inode結構體,inode包含了文件的元數據和設備信息。對于設備文件(如顯示器),inode中包含了設備驅動的相關信息。

wirte->filp

提取的代碼如下:

int copy_process(...){*p = *current;for (i = 0; i < NR_OPEN; i++)if ((f = p->filp[i])) f->f_count++;
}
?
void main(void) {if (!fork()) { init(); }
}
?
void init(void) {open("/dev/tty0", O_RDWR, 0);dup(0);dup(0);execve("/bin/sh", argv, envp);
}

fd=1filp從哪里來?

在UNIX和Linux系統中,當一個進程創建一個新的子進程時(通常是通過fork系統調用),子進程會繼承父進程的文件描述符。這意味著子進程會擁有與父進程相同的文件描述符集合,包括指向相同文件結構(struct file)的指針。

在提供的代碼中,copy_process函數負責復制父進程的文件描述符信息到子進程。這是通過遍歷父進程的filp數組(每個元素都是指向struct file的指針)并遞增相應文件結構的引用計數來實現的。這樣做確保了文件在兩個進程間正確共享。

main函數中,通過調用fork創建了一個新的子進程。如果fork返回0(表示這是子進程),則調用init函數。在init函數中,首先打開/dev/tty0(通常是控制臺設備),然后使用dup(0)將標準輸入、輸出和錯誤都重定向到這個控制臺設備。最后,通過execve調用替換當前進程映像為/bin/sh(shell),從而啟動一個新的shell進程。

因此,fd=1(通常用于標準輸出)的filp指針是從父進程繼承來的,并且在子進程中通過dup(0)調用被重定向到/dev/tty0設備。這樣,當shell進程寫入標準輸出時,數據會被發送到控制臺設備。

filp->open

提取的代碼如下:

int sys_open(const char* filename, int flag) {i = open_namei(filename, flag, &inode);current->filp[fd] = f; // 第一個空閑的fdf->f_mode = inode->i_mode;f->f_inode = inode;f->f_count = 1;return fd;
}

open系統調用完成了什么?

open系統調用主要完成了以下步驟:

  1. 解析目錄,找到inode:系統需要解析傳入的文件名,找到對應的inode結構,inode包含了文件的元數據和設備信息。

  2. 分配文件描述符(fd):在進程的文件描述符數組(filp)中找到一個空閑的文件描述符,并將其分配給這個文件。

  3. 建立文件結構體(file):創建一個文件結構體(file),該結構體包含了文件的狀態信息,如文件模式(f_mode)和指向inode的指針(f_inode)。

開始輸出

提取的代碼如下:

// sys_write function in linux/fs/read_write.c
int sys_write(unsigned int fd, char *buf, int cnt) {inode = file->f_inode;if (S_ISCHR(inode->i_mode))return rw_char(WRITE, inode->i_zone[0], buf, cnt);...
}
?
// rw_char function in linux/fs/char_dev.c
int rw_char(int rw, int dev, char *buf, int cnt) {crw_ptr call_addr = crw_table[MAJOR(dev)];call_addr(rw, dev, buf, cnt);...
}
  1. 系統調用:用戶程序通過 write 系統調用向內核請求寫操作,傳遞文件描述符(fd)、緩沖區地址(buf)和要寫入的字節數(cnt)。

  2. 字符設備檢查sys_write 函數中,首先獲取文件結構體的inode,并檢查該inode表示的是否為字符設備(通過 S_ISCHR(inode->i_mode) 判斷)。

  3. 調用設備驅動:如果是字符設備,調用 rw_char 函數,傳入寫操作標志(WRITE)、inode中的設備信息(i_zone[0])、緩沖區地址和字節數。

  4. 設備驅動操作:在 rw_char 函數中,根據設備的主要號碼(MAJOR(dev))從字符設備驅動表(crw_table)中獲取對應的操作函數指針,并調用該函數執行實際的寫操作。

  5. 輸出到屏幕:對于顯示器這樣的字符設備,rw_char 函數最終會調用顯示器的驅動函數,將緩沖區中的數據寫入顯示器的顯存,實現向屏幕的輸出。

這個過程展示了從用戶空間的 printf 調用開始,經過系統調用接口,到內核空間的文件操作,再到設備驅動程序,最終實現數據向硬件設備的輸出。這是操作系統中I/O系統工作的一個典型流程。

rw_char->crw_table

提取的代碼如下:

// 定義字符設備操作函數指針數組
static crw_ptr crw_table[] = {..., rw_ttyx, ...};
?
// 函數指針類型定義
typedef (*crw_ptr)(int rw, unsigned minor, char *buf, int count);
?
// 字符設備讀寫函數
static int rw_ttyx(int rw, unsigned minor, char *buf, int count) {return ((rw == READ) ? tty_read(minor, buf) : tty_write(minor, buf));
}
?
// 真正的寫函數
int tty_write(unsigned channel, char *buf, int nr) {struct tty_struct *tty;tty = channel + tty_table;sleep_if_full(&tty->write_q);...
}

總結代碼所做的事情及用途:

  1. 定義字符設備操作函數指針數組(crw_table

    • crw_table 是一個數組,包含了指向不同字符設備操作函數的指針。這些函數負責對字符設備進行讀寫操作。

  2. 函數指針類型定義(crw_ptr

    • crw_ptr 是一個函數指針類型,用于指向符合特定簽名的函數,即接受讀寫標志、次要設備號、緩沖區指針和計數作為參數的函數。

  3. 字符設備讀寫函數(rw_ttyx

    • rw_ttyx 函數根據傳入的讀寫標志(rw),決定調用 tty_read 還是 tty_write 函數。這個函數作為字符設備的通用入口點,根據操作類型分發到具體的讀寫處理函數。

  4. 真正的寫函數(tty_write

    • tty_write 是實現字符設備(如終端)寫操作的核心函數。它負責將數據從內核緩沖區寫入到設備。

    • 函數首先通過 channeltty_table 獲取到 tty_struct 結構體,該結構體包含了終端設備的相關信息和狀態。

    • 然后檢查輸出隊列(write_q)是否已滿,如果已滿,則調用 sleep_if_full 函數使進程休眠,等待隊列有空間。

    • 一旦隊列有空間,數據就被寫入隊列,后續操作(可能是中斷處理程序)會負責將隊列中的數據實際輸出到設備。

crw_table->tty_write

提取的代碼如下:

// 在 linux/kernel/tty_io.c 中的 tty_write 函數
int tty_write(unsigned channel, char *buf, int nr) {char c, *b = buf;while (nr > 0 && !FULL(tty->write_q)) {c = get_fs_byte(b); // 從用戶緩存區讀if (c == '\r') { PUTCH(13, tty->write_q); continue; }if (O_LCUC(tty)) c = toupper(c);b++; nr--;PUTCH(c, tty->write_q);} // 輸出完事或寫隊列滿tty->write(tty);
}

總結代碼所做的事情及用途:

  1. 初始化

    • 定義字符變量 c 和字符指針 b 指向緩沖區 buf 的起始位置。

  2. 循環處理每個字符

    • 使用 while 循環,當還有字符要寫入(nr > 0)且寫隊列未滿(!FULL(tty->write_q))時,繼續處理。

    • 從用戶空間的緩沖區中讀取一個字符到 c

  3. 處理回車字符

    • 如果字符是回車符('\r'),將其轉換為換行符('\n')并繼續下一個循環。

  4. 字符大小寫轉換

    • 如果終端設置為轉換為大寫(O_LCUC(tty)),將字符 c 轉換為大寫。

  5. 寫入隊列

    • 將處理后的字符放入終端的寫隊列 tty->write_q 中。

    • 更新緩沖區指針 b 和字符計數 nr

  6. 觸發實際寫操作

    • 一旦所有字符都已處理或寫隊列滿,調用 tty->write(tty) 觸發實際的寫操作,將隊列中的數據輸出到屏幕上。

  • 提取的代碼如下:

  • // 在 include/linux/tty.h 中定義的 tty_struct 結構體
    struct tty_struct {void (*write)(struct tty_struct *tty);struct tty_queue read_q, write_q;
    };
    ?
    // tty_struct 結構體數組的初始化
    struct tty_struct tty_table[] = {{con_write, {0,0,0,0,""}, {0,0,0,0,""}},{}, ...
    };
    ?
    // con_write 函數在 linux/kernel/chr_drv/console.c 中的定義
    void con_write(struct tty_struct *tty) {GETCH(tty->write_q, c);if (c > 31 && c < 127) {__asm__ ("movb _attr, %%ah\n\t""movw %%ax, %1\n\t::" "a"(c),"m"(*(short*)pos):"ax");pos += 2;}
    }
    • con_write 函數是 Linux 內核中負責將字符輸出到控制臺顯示器的關鍵函數。它通過直接操作顯存來實現字符的顯示,這是 Linux 內核中實現控制臺輸出的底層機制。

    • 通過這種方式,內核可以將用戶程序的輸出(如通過 printf 函數)轉換為屏幕上的可見字符,實現用戶與系統的交互。

  • 總結代碼所做的事情及用途:

    con_write 函數定義
    1. 如果字符 c 在可打印范圍內(ASCII碼 32 到 126),則通過內聯匯編代碼將其寫入顯存(視頻內存)的特定位置。

    2. 函數從 tty->write_q 隊列中獲取一個字符 c

    3. con_write 函數是 tty_struct 結構體中的 write 函數指針所指向的實際函數,負責將字符寫入顯示器。

tty_write->mov pos

  • 這兩張圖片提供了關于如何在Linux內核中實現向屏幕輸出字符的詳細信息。以下是提取的代碼和總結:

    提取的代碼:

  • // 在 include/linux/tty.h 中定義的 tty_struct 結構體
    struct tty_struct {void (*write)(struct tty_struct *tty);struct tty_queue read_q, write_q;
    };
    ?
    // tty_struct 結構體數組的初始化
    struct tty_struct tty_table[] = {{con_write, {0,0,0,0,""}, {0,0,0,0,""}}, {}, ...
    };
    ?
    // con_write 函數在 linux/kernel/chr_drv/console.c 中的定義
    void con_write(struct tty_struct *tty) {GETCH(tty->write_q, c);if (c > 31 && c < 127) {__asm__ ("movb _attr, %%ah\n\t""movw %%ax, %1\n\t"::"a"(c),"m"(*(short*)pos):"ax");pos += 2;}
    }

    總結代碼的作用:

    用途:

    • con_write 函數是 Linux 內核中負責將字符輸出到控制臺顯示器的關鍵函數。它通過直接操作顯存來實現字符的顯示,這是 Linux 內核中實現控制臺輸出的底層機制。

    • 通過這種方式,操作系統能夠統一管理不同程序的輸出,提供一致的接口給用戶程序,同時隱藏了硬件操作的復雜性。

    • 這種機制是操作系統中設備驅動程序的一部分,它展示了如何通過編程接口與硬件設備進行交互,是學習操作系統工作原理和設備驅動開發的重要內容。

    關于 mov pos 的解釋:

    • mov pos, c 是完成顯示中最核心的秘密,它將字符 c 的值移動到 pos 指向的顯存位置,從而在屏幕上顯示字符。

    • pos 指向顯存的起始地址(例如 0xA0000),每次寫入一個字符后,pos 的值會增加,以指向下一個字符的位置。

    • 這種直接操作顯存的方法是早期計算機系統中常見的屏幕輸出方式,它允許操作系統直接控制屏幕上的每個像素點。

    關于 pos += 2 的解釋:

    • 在彩色圖形適配器(CGA)中,屏幕上的一個字符在顯存中除了字符本身還應該有字符的屬性(如顏色等)。因此,每個字符及其屬性占用兩個字節。

    • pos += 2 表示在寫入一個字符后,pos 的值增加2,以指向下一個字符及其屬性的起始位置。

    • 這種機制確保了字符及其屬性能夠正確地存儲在顯存中,從而在屏幕上正確顯示。

總結

printf 的整個過程涉及多個步驟和組件,具體如下:

  1. 庫函數(printf)

    • 用戶程序調用標準庫中的 printf 函數來輸出格式化的文本。

  2. 系統調用(write)

    • printf 函數處理完格式化字符串后,通過系統調用 write 將數據寫入文件描述符指向的設備。

  3. 字符設備接口(crw_table[])

    • 系統調用 write 通過字符設備接口數組 crw_table[] 找到對應的設備處理函數。

  4. tty設備寫(tty_write)

    • 對于終端設備,tty_write 函數負責將數據寫入 write_q 隊列。

  5. write_q隊列

    • write_q 隊列用于暫存要寫入設備的數據,直到設備準備好接收數據。

  6. 顯示器寫(con_write)

    • con_write 函數負責將 write_q 隊列中的數據實際寫入顯存。

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

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

相關文章

琥珀掃描 2.0.5.0 | 文檔處理全能助手,支持掃描、文字提取及表格識別

琥珀掃描是一款功能強大的文檔處理應用程序。它不僅僅支持基本的文檔掃描功能&#xff0c;還涵蓋了文字提取、證件掃描、表格識別等多種實用功能。無論是學生、職員還是教師&#xff0c;都能從中找到適合自己的功能。該應用支持拍照生成電子件&#xff0c;并能自動矯正文檔邊緣…

jQuery UI 小部件方法調用詳解

jQuery UI 小部件方法調用詳解 引言 jQuery UI 是一個基于 jQuery 的用戶界面和交互庫,它提供了一系列小部件,如按鈕、對話框、進度條等,這些小部件極大地豐富了網頁的交互性和用戶體驗。本文將詳細介紹 jQuery UI 中小部件的方法調用,幫助開發者更好地理解和應用這些小部…

浮點數比較在Eigen數學庫中的處理方法

浮點數比較在Eigen數學庫中的處理方法 在Eigen數學庫中進行浮點數比較時&#xff0c;由于浮點數的精度問題&#xff0c;直接使用運算符通常不是推薦的做法。Eigen提供了幾種更安全的方法來進行浮點數比較&#xff1a; 1. 近似相等比較 使用isApprox()函數進行近似比較&#…

Linux-----驅動

一、內核驅動與啟動流程 1. Linux內核驅動 Nor Flash: 可線性訪問&#xff0c;有專門的數據及地址總線&#xff08;與內存訪問方式相同&#xff09;。 Nand Flash: 不可線性訪問&#xff0c;訪問需要控制邏輯&#xff08;軟件&#xff09;。 2. Linux啟動流程 ARM架構: IRAM…

Wincc腳本全部不運行

Wincc腳本全部不運行 前言解決辦法操作步驟 前言 這里主要是指舊項目移植到Wincc的高版本&#xff0c;移植后界面的一些功能均會失效。&#xff08;例如腳本不執行&#xff0c;項目編輯器不可用等情況&#xff09; 解決辦法 Wincc的項目文件中有Dcf文件&#xff0c;Dcf文件包…

使用numpy構建邏輯回歸模型及訓練流程

邏輯回歸模型構建及訓練流程 關于邏輯回歸的數據&#xff0c;有很多學習?的?例樣本。這?我們使?scikit learn提供的數據集?成函數來創建 具體參數可參照官網 Scikit-learn 是? Python 開發的開源機器學習庫&#xff0c;?泛?于數據挖掘和數據分析。 特點&#xff1a;易…

python的多線程和多進程程序編程

CPU密集型使用多進程&#xff0c;IO密集型使用多線程 查看進程ID和線程ID的命令分別是os.getpid()和threading.current_thread() 多進程使用multiprocessing就可以了&#xff0c;通常使用進程池來完成操作&#xff0c;阻塞主進程使用join方法 多線程使用threading模塊&#…

代碼隨想錄算法訓練營第十五天

LeetCode題目: 654. 最大二叉樹617. 合并二叉樹700. 二叉搜索樹中的搜索98. 驗證二叉搜索樹2843. 統計對稱整數的數目 其他: 今日總結 往期打卡 654. 最大二叉樹 跳轉: 654. 最大二叉樹 學習: 代碼隨想錄公開講解 問題: 給定一個不重復的整數數組 nums 。 最大二叉樹 可以用…

[GN] Uart協議解碼器源碼各個方法

系列文章目錄 sigrokdecode 模塊學習指南 — 準備階段 通訊協議 - Uart sigrokdecode 模塊 UART協議解碼器源碼解析 Uart協議解碼器源碼各個方法 文章目錄 系列文章目錄引入庫parity_ok注解類型枚舉options參數annotations 注解annotation_rows 注解分組接收&#xff08;RX&a…

技術分享|iTOP-RK3588開發板Ubuntu20系統旋轉屏幕方案

iTOP-3588開發板采用瑞芯微RK3588處理器&#xff0c;是全新一代AloT高端應用芯片&#xff0c;采用8nmLP制程&#xff0c;搭載八核64位CPU&#xff0c;四核Cortex-A76和四核Cortex-A55架構&#xff0c;主頻高達2.4GHz。是一款可用于互聯網設備和其它數字多媒體的高性能產品。 在…

Unity IL2CPP內存泄漏追蹤方案(基于Memory Profiler)技術詳解

一、IL2CPP內存管理特性與泄漏根源 1. IL2CPP內存架構特點 內存區域管理方式常見泄漏類型托管堆(Managed)GC自動回收靜態引用/事件訂閱未取消原生堆(Native)手動管理非托管資源未釋放橋接層GCHandle/PInvoke跨語言引用未正確釋放 對惹&#xff0c;這里有一個游戲開發交流小組…

消融實驗_草稿

五列數據 \begin{table}[htbp]\caption{Performance Comparison of Standalone KD Variants vs MIRKD-enhanced Variants on ACNE04 Dataset\label{AblationKD}}\centering\renewcommand{\arraystretch}{1.2}\scriptsize\begin{tabularx}{\linewidth}{{}l *{3}{>{\centering…

面向對象高級(1)

文章目錄 final認識final關鍵字修飾類&#xff1a;修飾方法&#xff1a;修飾變量final修飾變量的注意事項 常量 單例類什么是設計模式&#xff1f;單例怎么寫?餓漢式單例的特點是什么&#xff1f;單例有啥應用場景&#xff0c;有啥好處&#xff1f;懶漢式單例類。 枚舉類認識枚…

不用額外下載jar包,idea快速查看使用的組件源碼

以nacos為例子&#xff0c;在idea中引入了nacos依賴&#xff0c;就可以查看源碼了。 2. idea選擇open&#xff08;不關閉項目直接選擇file-open也可以&#xff09;, 在maven的倉庫里找到對應的包&#xff0c;打開 2.idea中選擇 jar包&#xff0c;選擇 add as library 3.這樣j…

小白學習java第12天:IO流之緩沖流

1.IO緩沖流&#xff1a; 之前我們學習的都是原始流&#xff08;FileInputStream字節輸入流、FileOutputStream字節輸出流、FIleReader字符輸入流、FIleWriter字符輸出流&#xff09;其實我們可以知道對于這些其實性能都不是很好&#xff0c;要么太慢一個一個&#xff0c;要么就…

高速電路設計概述

1.1 低速設計和高速設計的例子 本節通過一個簡單的例子&#xff0c;探討高速電路設計相對于低速電路設計需要考慮哪些不同的問題。希望讀者通過本例&#xff0c;對高速電路設計建立一個表象的認識。至于高速電路設計中各方面的設計要點&#xff0c;將在后續章節展開詳細的討論…

MySQL8.0.31安裝教程,附pdf資料和壓縮包文件

參考資料&#xff1a;黑馬程序員 一、下載 點開下面的鏈接&#xff1a;https://dev.mysql.com/downloads/mysql/ 點擊Download 就可以下載對應的安裝包了, 安裝包如下: 我用夸克網盤分享了「mysql」&#xff0c;鏈接&#xff1a;https://pan.quark.cn/s/ab7b7acd572b 二、解…

在Java項目中,引入【全局異常處理器】

目錄 一.為什么引入全局異常處理器&#xff08;目前項目碰到了什么問題&#xff09;&#xff1f; 1.問題描述 2.與預期的差別 3.解決方案 二.解決上述問題 1.定義【業務異常類】 2.在serviceImpl層&#xff0c;手動拋出【違反唯一性約束】這個異常 3.定義【全局異常處理…

newspaper公共庫獲取每個 URL 對應的新聞內容,并將提取的新聞正文保存到一個文件中

示例代碼&#xff1a; from newspaper import Article from newspaper import Config import json from tqdm import tqdm import os import requestswith open(datasource/api/news_api.json, r) as file:data json.load(file)print(len(data)) save_path datasource/sourc…

前端核心知識:Vue 3 編程的 10 個實用技巧

文章目錄 1. **使用 ref 和 reactive 管理響應式數據**原理解析代碼示例注意事項 2. **組合式 API&#xff08;Composition API&#xff09;**原理解析代碼示例優勢 3. **使用 watch 和 watchEffect 監聽數據變化**原理解析代碼示例注意事項 4. **使用 provide 和 inject 實現跨…