【Linux】系統部分——進程控制

11.進程控制

文章目錄

  • 11.進程控制
      • 一、進程創建
      • 二、進程終止
        • 退出碼
        • 進程終止的方式
      • 三、進程等待
        • 進程等待的方式
        • 獲取?進程status
        • 小程序
        • 阻塞與非阻塞等待
      • 四、進程程序替換
        • 替換原理
        • 進程程序替換的接口——exec替換函數
      • 五、總結

一、進程創建

之前學習了fork()函數創建子進程,進程調?fork,當控制轉移到內核中的fork代碼后,內核做:分配新的內存塊和內核數據結構給?進程;將?進程部分數據結構內容拷???進程;添加?進程到系統進程列表當中;fork返回,開始調度器調度 。其中將?進程部分數據結構內容拷???進程的方法是寫時拷貝。下面進行說明:

通常,??代碼共享,??再不寫?時,數據也是共享的,當任意??試圖寫?,便以寫時拷?的?式各??份副本。 因為有寫時拷?技術的存在,所以??進程得以徹底分離!完成了進程獨?性的技術保證! 寫時拷?,是?種延時申請技術,可以提?整機內存的使?率

在這里插入圖片描述

那么在修改內容之前與修改內容之后,具體做了什么?

在fork時將父進程的所有物理地址權限改為只讀,之后子進程再拷貝父進程的內容。這樣當子進程或父進程對數據進行寫入操作的時候就會觸發系統錯誤。之后系統會觸發缺頁中斷,之后系統檢測,是真的發生錯誤還是發生寫時拷貝。若判定為寫時拷貝,系統會進行申請內存—>發生拷貝(將原數據段內容拷貝到新申請的內存中)—>修改頁表—>恢復執行,同時將數據區的權限設置為讀寫(父子進程都會改)。

二、進程終止

退出碼

main函數的返回值其實是返回給父進程和系統的。

[lisihan@hcss-ecs-b735 lession15]$ cat code1.cpp
#include<iostream>int main()
{std::cout << "hello linux" << std::endl;return 123;
}[lisihan@hcss-ecs-b735 lession15]$ ./code1
hello linux
[lisihan@hcss-ecs-b735 lession15]$ echo $?
123
[lisihan@hcss-ecs-b735 lession15]$ echo $?
0
  1. 通過$?我們可以查到上一次程序執行的退出碼,這里返回的就是123,并用echo顯示出來,這個退出碼用于表明錯誤原因

  2. 同樣echo也是一個可執行程序,所以第二次echo $?打印的值實際上是上一個echo $?的退出碼,即為0

  3. 通常約定:0—成功 非0—失敗

在C語言中提供了一批錯誤碼errno以及查看錯誤碼對應錯字符串的函數接口strerrno()

[lisihan@hcss-ecs-b735 lession15]$ cat code2.c
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{printf("before: errno: %d, strerrno: %s\n", errno, strerror(errno));FILE* fp = fopen("./text.c", "r");if(fp == NULL){printf("before: errno: %d, strerrno: %s\n", errno, strerror(errno));return errno;}return 0;
}
[lisihan@hcss-ecs-b735 lession15]$ ./code2
before: errno: 0, strerrno: Success
before: errno: 2, strerrno: No such file or directory
進程終止的方式
  1. _exit()函數

    這個函數是一個系統調用函數,參數是進程要傳遞的退出碼

  2. exit()函數

    這個函數是C語言自帶的函數,與_exit()的用法是一樣的

  3. return退出

    return是?種更常?的退出進程?法。執?return n等同于執?exit(n),因為調?main的運?時函數會將main的返回值當做 exit的參數。

_exit()與exit()的區別:

exit最后也會調?_exit, 但在調?_exit之前,還做了其他?作 :

  1. 執???通過 atexit或on_exit定義的清理函數
  2. 關閉所有打開的流,所有的緩存數據均被寫?
  3. 調?_exit

在這里插入圖片描述

從這個過程可知:使用exit()退出進程會將緩存區的內容刷新出來,之后再關閉,而_exit()直接關閉進程,沒有上述步驟。

三、進程等待

進程等待的方式

之前演示過當子進程運行結束而父進程沒有回收子進程的話,子進程會一直處于僵尸狀態,進?造成內存泄漏。并且?進程派給?進程的任務完成的如何,我們需要知道。如,?進程運?完成,結果對還是不對,或者是否正常退出。因此?進程需要通過進程等待的?式,回收?進程資源,獲取?進程退出信息。

pid_t wait(int status);*

返回值:成功返回被等待進程pid,失敗返回-1。

參數:輸出型參數,獲取?進程退出狀態,不關?則可以設置成為NULL

*pid_ t waitpid(pid_t pid, int status, int options);

  • 返回值:

    當正常返回的時候waitpid返回收集到的?進程的進程ID;

    如果設置了選項WNOHANG,?調?中waitpid發現沒有已退出的?進程可收集,則返回0;

    如果調?中出錯,則返回-1,這時errno會被設置成相應的值以指?錯誤所在;

  • 參數:

    • pid:

      Pid=-1,等待任?個?進程。與wait等效。
      Pid>0.等待其進程ID與pid相等的?進程。

    • status: 輸出型參數
      WIFEXITED(status): 若為正常終??進程返回的狀態,則為真。(查看進程是否是正常退出)
      WEXITSTATUS(status): 若WIFEXITED?零,提取?進程退出碼。(查看進程的退出碼)

    • options:

      默認為0,表?阻塞等待

      WNOHANG: 若pid指定的?進程沒有結束,則waitpid()函數返回0,不予以等待。若正常結束,則返回該?進程的ID。

使用要點:

  • 如果?進程已經退出,調?wait/waitpid時,wait/waitpid會?即返回,并且釋放資源,獲得?進程退出信息。
  • 如果在任意時刻調?wait/waitpid,?進程存在且正常運?,則進程可能阻塞。
  • 如果不存在該?進程,則?即出錯返回。

在這里插入圖片描述

獲取?進程status
  • wait和waitpid,都有?個status參數,該參數是?個輸出型參數,由操作系統填充。
  • 如果傳遞NULL,表?不關??進程的退出狀態信息。
  • 否則,操作系統會根據該參數,將?進程的退出信息反饋給?進程。
  • status不能簡單的當作整形來看待,可以當作位圖來看待,具體細節如下圖(只研究status低16?特位):

在這里插入圖片描述

解釋:這張圖表示status獲取子進程狀態之后數據的分布情況,可以看到只用到了低16位的bit位,但是分了兩種不同的情況:

  • 正常終止:8~15bit位儲存進程退出信息也就是子進程返回的退出碼
  • 當子進程被信號所殺:0~7bit的位置儲存終止信號的信息。

舉例:

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id < 0){printf("errno : %d, errstring: %s\n", errno, strerror(errno));return errno;}else if(id == 0){int cnt = 5;while(cnt){printf("子進程運行中, pid: %d\n", getpid());cnt--;sleep(1);}exit(123);}else{sleep(3);int status = 0;pid_t rid = waitpid(id, &status, 0); // == waitif(rid > 0){printf("wait sub process success, rid: %d, status code: %d, singal: %d\n", rid, status >> 8 & 0xFF, status & 0x0F );//這里我們可以用這種位運算的方法,也可以使用系統提供的宏WEXITSTATUS(status),這些在前面有//if(WIFEXITED(status))//{//    printf("子進程正常推出!\n");//}//else//{//    //.....//}}elseperror("waitpid");while(1){printf("我是父進程: pid:%d\n", getpid());sleep(1);}}return 0;
}

運行結果:

#運行窗口
[lisihan@hcss-ecs-b735 lession15]$ ./code3
子進程運行中, pid: 16597
子進程運行中, pid: 16597
子進程運行中, pid: 16597
子進程運行中, pid: 16597
子進程運行中, pid: 16597
wait sub process success, rid: 16597, status code: 123, singal: 0
我是父進程: pid:16596
我是父進程: pid:16596
我是父進程: pid:16596
我是父進程: pid:16596
我是父進程: pid:16596#監視窗口PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND						#進程未啟動
14111 16594 16593 14111 pts/2    16593 S+    1000   0:00 grep --color=auto code3PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND						#進程啟動,創建父子進程
12990 16596 16596 12990 pts/1    16596 S+    1000   0:00 ./code3				
16596 16597 16596 12990 pts/1    16596 S+    1000   0:00 ./code3
14111 16601 16600 14111 pts/2    16600 S+    1000   0:00 grep --color=auto code3PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
12990 16596 16596 12990 pts/1    16596 S+    1000   0:00 ./code3
16596 16597 16596 12990 pts/1    16596 S+    1000   0:00 ./code3
14111 16606 16605 14111 pts/2    16605 S+    1000   0:00 grep --color=auto code3PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
12990 16596 16596 12990 pts/1    16596 S+    1000   0:00 ./code3
16596 16597 16596 12990 pts/1    16596 S+    1000   0:00 ./code3
14111 16611 16610 14111 pts/2    16610 S+    1000   0:00 grep --color=auto code3PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
12990 16596 16596 12990 pts/1    16596 S+    1000   0:00 ./code3
16596 16597 16596 12990 pts/1    16596 S+    1000   0:00 ./code3
14111 16616 16615 14111 pts/2    16615 S+    1000   0:00 grep --color=auto code3PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
12990 16596 16596 12990 pts/1    16596 S+    1000   0:00 ./code3
16596 16597 16596 12990 pts/1    16596 S+    1000   0:00 ./code3
14111 16621 16620 14111 pts/2    16620 S+    1000   0:00 grep --color=auto code3PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND						#子進程運行結束,父進程回收子進程
12990 16596 16596 12990 pts/1    16596 S+    1000   0:00 ./code3
14111 16626 16625 14111 pts/2    16625 S+    1000   0:00 grep --color=auto code3PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND			
12990 16596 16596 12990 pts/1    16596 S+    1000   0:00 ./code3
14111 16641 16640 14111 pts/2    16640 S+    1000   0:00 grep --color=auto code3PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND						#進程全部結束14111 16641 16640 14111 pts/2    16640 S+    1000   0:00 grep --color=auto code3

此時我們可以談談關于進程退出:

  1. 子進程代碼跑完之后,結果是否正確是用退出碼判定的
  2. 子進程出現異常,比如說野指針、數據溢出等情況,OS會使用信號直接終止這個進程(有關信號的問題后面再談),進程退出信息中,會記錄自己的退出信號
  3. 進程的退出碼和退出信號都會在進程的內核數據結構task_struct中維護
小程序

學習了上面的內容,我們可以寫一個小程序,用于定時備份一個vector中的內容:

#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <vector>
#include <string>std::vector<int> data;int save()
{pid_t id = fork();if(id == 0)//子進程進行拷貝操作{std::string name = "./backup/";			//拷貝文件放在backup文件夾中name += std::to_string(time(nullptr));	//文件名稱為拷貝的時間#############################################################name += ".backup";						//文件名后綴FILE* fp = fopen(name.c_str(), "w");	//文件創建+打開if(fp == nullptr) return 1;std::string data_str;for(auto d :data)						//把data中的數據轉化為字符串儲存在data_str中{data_str += std::to_string(d);data_str += " ";}fputs(data_str.c_str(), fp);			//文件寫入fclose(fp);								//文件關閉exit(0);}else //父進程阻塞等待子進程拷貝完成{int status;pid_t pid = waitpid(id, &status, 0);if(pid > 0)								//等待成功{printf("wait child process success!, exit code: %d\n", WEXITSTATUS(status));}else 									//等待失敗{printf("wait child process...\n");}}return 0;
}int main()
{int cnt = 0;while(true){data.push_back(cnt++);					//data數據寫入sleep(1);			if(cnt % 10 == 0)save();								//拷貝操作}return 0;
}

程序運行:

#運行窗口
[lisihan@hcss-ecs-b735 lession15]$ g++ -o code4 code4.cpp -std=c++11
[lisihan@hcss-ecs-b735 lession15]$ ./code4
wait child process success!, exit code: 0
wait child process success!, exit code: 0
wait child process success!, exit code: 0
wait child process success!, exit code: 0
wait child process success!, exit code: 0
wait child process success!, exit code: 0
wait child process success!, exit code: 0
wait child process success!, exit code: 0
wait child process success!, exit code: 0#監視窗口
[lisihan@hcss-ecs-b735 backup]$ ls
1748787298.backup  1748787308.backup  1748787354.backup  1748787364.backup  1748787374.backup
[lisihan@hcss-ecs-b735 backup]$ cat  1748787374.backup 
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 [lisihan@hcss-ecs-b735 backup]$ ls
1748787298.backup  1748787364.backup  1748787394.backup  1748787424.backup
1748787308.backup  1748787374.backup  1748787404.backup  1748787434.backup
1748787354.backup  1748787384.backup  1748787414.backup

程序運行正常

阻塞與非阻塞等待

阻塞等待:如果子進程沒有退出或終止,父進程會一直等待直到子進程退出,父進程才會執行之后的代碼

非阻塞等待:即使子進程沒有退出,父進程只會在waitpid函數中判斷一次,不會一直在函數中等待子進程退出,在等待子進程退出的這段時間,父進程可以去做其他事情。

選擇阻塞還是非阻塞等待由waitpid函數中的第三個形參決定的

options:

默認為0,表?阻塞等待

WNOHANG(理解為wait no hang): 若pid指定的?進程沒有結束,則waitpid()函數返回0,不予以等待。若正常結束,則返回該?進程的ID。

具體就不演示了

四、進程程序替換

之前我們使用函數fork()創建子進程, fork()之后,??各?執??進程代碼的?部分如果?進程就想執??個全新的程序呢?進程的程序替換來完成這個功能。程序替換是通過特定的接?,加載磁盤上的?個全新的程序(代碼和數據),加載到調?進程的地址空間中!

execl()函數為例,這個函數用于進程程序替換,后面會詳細介紹,我們執行下面的代碼:

#include <iostream>
#include <unistd.h>int main()
{execl("/bin/ls", "-a", "-l", nullptr); return 0;
}

運行結果:

[lisihan@hcss-ecs-b735 lession15]$ ./code5
total 108
drwxrwxr-x 2 lisihan lisihan  4096 Jun  1 22:17 backup
-rwxrwxr-x 1 lisihan lisihan  9024 May 31 18:31 code1
-rw-rw-r-- 1 lisihan lisihan    94 May 31 18:29 code1.cpp
-rwxrwxr-x 1 lisihan lisihan  8528 May 31 19:05 code2
-rw-rw-r-- 1 lisihan lisihan   313 May 31 19:04 code2.c
-rwxrwxr-x 1 lisihan lisihan  8784 Jun  1 20:40 code3
-rw-rw-r-- 1 lisihan lisihan  1053 Jun  1 21:37 code3.c
-rwxrwxr-x 1 lisihan lisihan 30040 Jun  1 22:15 code4
-rw-rw-r-- 1 lisihan lisihan  1002 Jun  1 22:17 code4.cpp
-rwxrwxr-x 1 lisihan lisihan  8760 Jun  2 20:48 code5
-rw-rw-r-- 1 lisihan lisihan   116 Jun  2 20:46 code5.cpp
-rw-rw-r-- 1 lisihan lisihan   176 Jun  2 20:47 makefile
[lisihan@hcss-ecs-b735 lession15]$ ls -a -l
total 116
drwxrwxr-x  3 lisihan lisihan  4096 Jun  2 20:48 .
drwx------ 22 lisihan lisihan  4096 May 31 18:27 ..
drwxrwxr-x  2 lisihan lisihan  4096 Jun  1 22:17 backup
-rwxrwxr-x  1 lisihan lisihan  9024 May 31 18:31 code1
-rw-rw-r--  1 lisihan lisihan    94 May 31 18:29 code1.cpp
-rwxrwxr-x  1 lisihan lisihan  8528 May 31 19:05 code2
-rw-rw-r--  1 lisihan lisihan   313 May 31 19:04 code2.c
-rwxrwxr-x  1 lisihan lisihan  8784 Jun  1 20:40 code3
-rw-rw-r--  1 lisihan lisihan  1053 Jun  1 21:37 code3.c
-rwxrwxr-x  1 lisihan lisihan 30040 Jun  1 22:15 code4
-rw-rw-r--  1 lisihan lisihan  1002 Jun  1 22:17 code4.cpp
-rwxrwxr-x  1 lisihan lisihan  8760 Jun  2 20:48 code5
-rw-rw-r--  1 lisihan lisihan   116 Jun  2 20:46 code5.cpp
-rw-rw-r--  1 lisihan lisihan   176 Jun  2 20:47 makefile

結果表明這個函數可以實現程序替換,用我們自己寫的code5來執行ls -a -l命令。

替換原理

?fork創建?進程后執?的是和?進程相同的程序(但有可能執?不同的代碼分?),?進程往往要調??種exec函數以執?另?個程序。當進程調??種exec函數時,該進程的??空間代碼和數據完全被新程序替換,從新程序的啟動例程開始執?調?exec并不創建新進程,所以調?exec前后該進程的id并未改變

在這里插入圖片描述

重點

  1. 進程程序替換沒有創建子進程。只是將這個進程的PCB中的虛擬內存通過頁表映射的物理內存中的代碼段和數據段替換成了另一個程序的代碼和數據。由于進程中原有的代碼和數據都被替換了,所以原來程序后面的代碼就不會再運行了

  2. 進程程序替換不僅可以執行系統的程序,同樣可以執行我們自己創建的可執行程序

  3. 調?exec前后該進程的id并未改變,這個同樣我們可以證明一下

    //code5.cpp
    #include <iostream>
    #include <unistd.h>
    int main()
    {printf("my pid: %d, ppid: %d\n", getpid(), getppid());execl("./code3", "code3", nullptr); return 0;
    }
    //code3.c
    #include <stdio.h>
    #include <string.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    int main()
    {pid_t id = fork();if(id < 0){printf("errno : %d, errstring: %s\n", errno, strerror(errno));return errno;}else if(id == 0){int cnt = 3;while(cnt){printf("子進程運行中, pid: %d\n", getpid());cnt--;sleep(1);}exit(123);}else{sleep(20);int status = 0;pid_t rid = waitpid(id, &status, 0); // == waitif(rid > 0)printf("wait sub process success, rid: %d, status code: %d, singal: %d\n", rid, status >> 8 & 0xFF, status & 0x0F );elseperror("waitpid");while(1){printf("我是父進程: pid:%d\n", getpid());sleep(1);}}return 0;
    }
    

    運行結果:

       [lisihan@hcss-ecs-b735 lession15]$ ./code5my pid: 29566, ppid: 28461子進程運行中, pid: 29567子進程運行中, pid: 29567子進程運行中, pid: 29567子進程運行中, pid: 29567子進程運行中, pid: 29567wait sub process success, rid: 29567, status code: 123, singal: 0我是父進程: pid:29566我是父進程: pid:29566我是父進程: pid:29566我是父進程: pid:29566我是父進程: pid:29566
    

    如果想要進程程序替換但是自己本身又要執行后面的代碼,我們可以利用fork()函數創建一個子進程來進程程序替換,自己的進程繼續運行后續的代碼,具體就不演示了。其實這與我們shell的功能有一些類似,shell也是一個進程,這個進程接收到用戶輸入會fork一個子進程,然后根據用戶輸入execl其他程序

  4. 從進程的程序替換我們可以知道,進程的數據和代碼也可以發生寫時拷貝,此時進程就是徹底獨立的

進程程序替換的接口——exec替換函數

所有函數:

include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

這些函數的不同點在于傳參方式的不同,根據函數名可以分為以下傳參方式,這些函數除了execve是系統調用函數,其他函數都是C語言提供的庫函數,這些庫函數都是復用execve函數實現的:

在這里插入圖片描述

l(list) : 表?參數采?列表
v(vector) : 參數?數組
p(path) : 有p?動搜索環境變量PATH
e(env) : 表???維護環境變量

在這里插入圖片描述

這個表中如果帶路徑表示用戶不需要自己寫路徑,對于環境變量也是一樣的

  • 這些函數如果調?成功則加載新的程序從啟動代碼開始執?,不再返回。
  • 如果調?出錯則返回-1
  • 所以exec函數只有出錯的返回值?沒有成功的返回值。
  • 在使用列表類函數的時候傳參的最后一個要加上nullptr

五、總結

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

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

相關文章

【讀論文】U-Net: Convolutional Networks for Biomedical Image Segmentation 卷積神經網絡

摘要1 Introduction2 Network Architecture3 Training3.1 Data Augmentation 4 Experiments5 Conclusion背景知識卷積激活函數池化上采樣、上池化、反卷積softmax 歸一化函數交叉熵損失 Olaf Ronneberger, Philipp Fischer, Thomas Brox Paper&#xff1a;https://arxiv.org/ab…

藍牙音樂(A2DP)音頻延遲的一些感想跟分析,讓你對A2DP體驗更佳深入

零.聲明 最近做藍牙協議棧的過程中遇到一些客戶偶爾提報音頻延遲的問題&#xff0c;所以引發了一些感想&#xff0c;跟大家分享下&#xff0c;音頻延遲主要的影響范圍是對一些要求實時性比較高的場景有比較差的體驗 連接藍牙看視頻的過程中&#xff0c;發現音畫不同步&#x…

MySQL 8.0 綠色版安裝和配置過程

MySQL作為云計算時代&#xff0c;被廣泛使用的一款數據庫&#xff0c;他的安裝方式有很多種&#xff0c;有yum安裝、rpm安裝、二進制文件安裝&#xff0c;當然也有本文提到的綠色版安裝&#xff0c;因綠色版與系統無關&#xff0c;且可快速復制生成&#xff0c;具有較強的優勢。…

AGV|無人叉車工業語音播報器|預警提示器LBE-LEX系列性能與接線說明

LBE-LEX系列AGV|無人叉車工業語音播報器|預警提示器&#xff0c;涵蓋LBE-LEI-M-00、LBE-LESM-00、LBE-LES-M-01、LBE-LEC-M-00、LBE-KEI-M-00、LBE-KES-M-00、LBE-KES-M-01、LBE-KEC-M-00等型號&#xff0c;適用于各種需要語音提示的場景&#xff0c;主要有AGV、AMR機器人、無人…

行為型設計模式之Interpreter(解釋器)

行為型設計模式之Interpreter&#xff08;解釋器&#xff09; 前言&#xff1a; 自己的話理解&#xff1a;自定義一個解釋器用來校驗參數或數據是否合法。 1&#xff09;意圖 給定一個語言&#xff0c;定義它的文法的一種表示&#xff0c;并定義一個解釋器&#xff0c;這個解…

C++常用的企業級日志庫

黃老師跟大家推薦幾款在企業開發中最受歡迎的C++日志庫! 1. spdlog spdlog 是一個非常流行的開源C++日志庫,以其高性能和易用性著稱。它支持多線程、異步日志記錄以及多種格式化選項。 安裝 可以通過包管理器安裝,例如 vcpkg: vcpkg install spdlog示例代碼 #include…

Python讀取PDF:文本、圖片與文檔屬性

在日常的數據采集、文檔歸檔與信息挖掘過程中&#xff0c;PDF格式因其版式固定、內容穩定而被廣泛使用。Python 開發者若希望實現 PDF 內容的自動化提取&#xff0c;選擇一個易用且功能完善的庫至關重要。本文將介紹如何用Python實現 PDF文本讀取、圖片提取 以及 文檔屬性讀取 …

excel中數字不滿六位在左側前面補0的方法

如下圖“代碼”列&#xff0c;想要實現統一的六位&#xff0c;如果不足六位&#xff0c;在前面&#xff08;左側&#xff09;補0。 實現方法&#xff1a; 使用公式TEXT(A2,"000000")注意務必是用雙引號。 目標實現&#xff1a; 如果想要脫離原數據&#xff0c;復制…

軟考 系統架構設計師系列知識點之雜項集萃(82)

接前一篇文章&#xff1a;軟考 系統架構設計師系列知識點之雜項集萃&#xff08;81&#xff09; 第148題 “41”視圖主要用于描述系統邏輯架構&#xff0c;最早由Philippe Kruchten于1995年提出。其中&#xff08; &#xff09;視圖用于描述對象模型&#xff0c;并說明系統應該…

Langgraph實戰--自定義embeding

概述 在Langgraph中我想使用第三方的embeding接口來實現文本的embeding。但目前langchain只提供了兩個類&#xff0c;一個是AzureOpenAIEmbeddings&#xff0c;一個是&#xff1a;OpenAIEmbeddings。通過ChatOpenAI無法使用第三方的接口&#xff0c;例如&#xff1a;硅基流平臺…

(附實例代碼及圖示)混合策略實現 doc-doc 對稱檢索

HyDE 混合策略 在前面的文章中&#xff0c;學習的優化策略都是將對應的 查詢 生成 新查詢&#xff0c;通過 新查詢 來執行相應的檢索&#xff0c;但是在數據庫中存儲的數據一般都是 文檔 層面上的&#xff0c;數據會遠遠比 查詢 要大很多&#xff0c;所以 query 和 doc 之間是…

webui無法注冊如何配置

1. 初始登陸界面 docker部署的腳本為&#xff1a; docker run -d \ -p 8180:8080 --gpusall \ -v ollama:/root/.ollama \ -v /home/pretrained_model/output:/app/backend/output \ --name open-webui \ --restart always ghcr.io/open-webui/open-webui:ollama 2. 新增注冊入…

力扣 88.合并兩個有序數組

文章目錄 題目介紹題解 題目介紹 題解 法一&#xff1a;暴力法 class Solution {public void merge(int[] nums1, int m, int[] nums2, int n) {for(int i 0; i < n; i){nums1[mi] nums2[i];}Arrays.sort(nums1);} }法二&#xff1a;倒序雙指針 時間復雜度為O(mn) 從右…

conda入門

目錄 1. Conda 是什么&#xff1f;2. 為什么需要 Conda&#xff1f;它能解決什么問題&#xff1f;3. Conda 的核心組件和概念4. Conda 基本工作流程和常用命令5. Conda 的主要優勢6. Conda 與 Pip 的關系7. 何時使用 Conda&#xff1f; 1. Conda 是什么&#xff1f; 包管理器&…

UE 5 和simulink聯合仿真,如果先在UE5這一端結束Play,過一段時間以后**Unreal Engine 5** 中會出現顯存不足錯誤

提問 UE5報錯如圖。解析原因 回答 你遇到的這個錯誤提示是&#xff1a; “Out of video memory trying to allocate a rendering resource. Make sure your video card has the minimum required memory, try lowering the resolution and/or closing other applications tha…

第七十三篇 從電影院售票到停車場計數:生活場景解析Java原子類精髓

目錄 一、原子類基礎&#xff1a;電影院售票系統1.1 傳統售票的并發問題1.2 原子類解決方案 二、原子類家族&#xff1a;超市收銀系統2.1 基礎類型原子類2.2 數組類型原子類 三、CAS機制深度解析&#xff1a;停車場管理系統3.1 CAS工作原理3.2 車位計數器實現 四、高性能實踐&a…

Linux(線程控制)

一 線程的操作 1. 創建線程&#xff1a;pthread_create int pthread_create(pthread_t *thread, // 線程 idconst pthread_attr_t *attr, // 線程屬性設置void *(*start_routine) (void *), // 回調函數void *arg // 傳遞…

PL/SQLDeveloper中數值類型字段查詢后顯示為科學計數法的處理方式

PL/SQLDeveloper中數值類型字段查詢后顯示為科學計數法的處理方式 文章目錄 PL/SQLDeveloper中數值類型字段查詢后顯示為科學計數法的處理方式1. 查詢效果2. 處理方式3. 再次查詢 1. 查詢效果 2. 處理方式 3. 再次查詢

centos 9/ubuntu 一次性的定時關機

方法一 # 15 表示15分鐘以后自動關機 sudo shutdown -h 15方法二&#xff1a; sudo dnf install at -y # 晚上十點半關機 echo "shutdown -h now" | at 22:30 # 檢查是否設置成功命令 atq [rootdemo-192 ~]# atq 1 Wed Jun 4 11:12:00 2025 a root # 取消定時計劃…

Riverpod與GetX的優缺點對比

Riverpod 與 GetX 的優缺點對比 在 Flutter 開發領域,Riverpod 和 GetX 都是備受關注的狀態管理與依賴注入框架,它們各有優劣,適用于不同的開發場景。以下從多個維度詳細對比二者的優缺點。 一、Riverpod 的優缺點 (一)優點 架構清晰,數據流向明確:基于 Provider 模…