Linux(進程控制)

進程控制

  • 進程創建
    • fork函數初識
    • fork函數返回值
    • 寫時拷貝
    • fork常規用法
    • fork調用失敗的原因
  • 進程終止
    • 進程退出碼
    • 進程常見退出方法
  • 進程等待
    • 進程等待必要性
    • 獲取子進程status
    • 進程等待的方法
  • 阻塞等待與非阻塞等待
    • 阻塞等待
    • 非阻塞等待
  • 進程替換
    • 替換原理
    • 替換函數
    • 函數解釋
    • 命名理解
  • 做一個簡易的shell

進程創建

fork函數初識

在linux中fork函數時非常重要的函數,它從已存在進程中創建一個新進程。新進程為子進程,而原進程為父進程。
返回值:子進程中返回0,父進程返回子進程id,出錯返回-1。
在這里插入圖片描述
在這里插入圖片描述
進程調用fork,當控制轉移到內核中的fork代碼后,內核做:

  • 分配新的內存塊與內存數據給子進程;
  • 將父進程的部分數據內容拷貝進子進程;
  • 添加子進程到系統列表當中;
  • fork返回,開始調度器調度。

當一個進程調用fork之后,就有兩個二進制代碼相同的進程。而且它們都運行到相同的地方。但每個進程都將可以開始它們自己的旅程:
在這里插入圖片描述
在這里插入圖片描述
我們會發現,fork之前,父進程是單獨執行的,fork以后父進程和子進程就分流進行執行了,但是要注意的是父子代碼共享是fork以后共享,并不是程序所有的都共享,而且fork以后誰先執行是由調度器決定的。

fork函數返回值

那么為什么要給父進程返回子進程PID呢?

一個子進程只能擁有一個父進程,但是一個父進程可以擁有多個子進程,父進程創建子進程是為了給子進程指派任務,返回子進程的PID就可以很好的對諸多子進程進行管理。

為什么fork以后就會有兩個返回值呢?

父進程在調用fork函數以后,fork函數就會進行一系列操作,創建子進程PCB,創建子進程虛擬地址空間,創建頁表…,也就是說,在return之前,子進程就已經創建完成了,return就需要父進程子進程都執行,而return的本質就是對id的寫入,父進程返回一個id,子進程返回一個id,對于父子進程返回的id程序都需要進行執行,所以此時就會有兩個返回值。
在這里插入圖片描述

寫時拷貝

父進程創建子進程,并不會對所有代碼和數據都進行拷貝,因為有些東西子進程只需要進行讀取,并不需要修改,與父進程共享即可,我們只有在需要修改的時候,對數據進行拷貝即可,這種延時拷貝策略,極大的提升了效率。
在這里插入圖片描述

fork常規用法

  • 一個父進程希望復制自己,使父子進程同時執行不同的代碼段。例如,父進程等待客戶端請求,生成子進程來處理請求。
  • 一個進程要執行一個不同的程序。例如子進程從fork返回后,調用exec函數。

fork調用失敗的原因

  • 系統中有太多的進程。
  • 實際用戶的進程數超過了限制。

進程終止

進程退出場景

  • 代碼運行完畢,結果正確。
  • 代碼運行完畢,結果不正確。
  • 代碼異常終止。

進程退出碼

我們平時寫代碼過程中,一直是return 0,這是為什么呢?main函數也是個函數,系統要調用他,就需要有返回值,而return 0就表示代碼執行成功,結果正確,我們一般用非0表示結果不正確,原因在于成功了就成功了,只有一種可能,但是失敗確有多種原因。

我們可以使用echo $?命令查看最近一次進程退出的退出碼信息:
在這里插入圖片描述
在這里插入圖片描述
C語言當中的strerror函數可以通過錯誤碼,獲取該錯誤碼在C語言當中對應的錯誤信息:
在這里插入圖片描述
在這里插入圖片描述
實際上我們Linux中各種指令也是可執行程序,我們也可以看見相應的退出碼:
在這里插入圖片描述

進程常見退出方法

正常退出

  1. return退出
    在main函數中使用return終止是我們最常見的方式。
    在這里插入圖片描述

在這里插入圖片描述

  1. 調用exit
    (1)執行用戶通過 at;
    (2) 關閉所有打開的;
    (3) 調用_exit;
    我們要注意,return只能在main函數中退出,在其他位置都是返回值,而exit可以再任意位置退出,包括調用的函數內部。

在這里插入圖片描述
在這里插入圖片描述
3. 調用_exit

在這里插入圖片描述
在這里插入圖片描述
我們會發現exit與_exit的區別就是_exit會直接終止進程,不做任何后續處理,而exit會刷新緩沖區。
在這里插入圖片描述
我們需要知道的是,保存數據的緩沖器并不是操作系統再給我們維護,因為_exit之后,并沒有刷新緩沖區,而是C標準庫給我們維護的。
異常退出

ctrl + c,信號終止

在進程運行過程中向進程發生kill -9信號使得進程異常退出,或是使用Ctrl+C使得進程異常退出等。

進程等待

進程等待必要性

  • 子進程退出,父進程如果不管不顧,就可能造成‘僵尸進程’的問題,進而造成內存泄漏。
  • 進程一旦變成僵尸狀態,那就刀槍不入,“殺人不眨眼”的kill -9 也無能為力,因為誰也沒有辦法殺死一個已經死去的進程。
  • 父進程派給子進程的任務完成的如何,我們需要知道。子進程運行完成,結果對還是不對, 或者是否正常退出。
  • 父進程通過進程等待的方式,回收子進程資源,獲取子進程退出信息。

獲取子進程status

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

在這里插入圖片描述
在status的低16比特位當中,高8位表示進程的退出狀態,即退出碼。進程若是被信號所殺,則低7位表示終止信號,而第8位比特位是core dump標志。

進程退出碼:(status >> 8) & 0xFF
進程退出信號:status & 0x7F

進程等待的方法

1. wait方法

pid_t wait(int*status);
//返回值:成功返回被等待進程pid,失敗返回-1。
//參數:輸出型參數,獲取子進程退出狀態,不關心則可以設置成為NULL

在這里插入圖片描述
在這里插入圖片描述
此時程序處于僵尸狀態,父進程并沒有對子進程進行回收,當我們使用wait以后:

  1 #include<stdio.h>2 #include<unistd.h>3 #include<stdlib.h>4 #include<sys/wait.h>5 #include<sys/types.h>6 7 int main()8 {9     pid_t id = fork();10     if(id == -1)11     {12         perror("fork()");13         return 1;14     }15     else if(id == 0)16     {17         int cnt = 5;18         while(cnt)19         {20             printf("I am chlid: cnt:%d, pid:%d, ppid:%d\n", cnt, getpid(), getppid());21             sleep(1);22             cnt--;23         }24         exit(1);25     }26     else27     {28         pid_t ret = wait(NULL);                                                                          29         if(ret > 0)30         {31             printf("wait child sucess: ret:%d\n", ret);32         }33         while(1)34         {35             printf("I am father: pid:%d, ppid:%d\n", getpid(), getppid());36             sleep(1);37         }38     }39     return 0;40 }

在這里插入圖片描述
在這里插入圖片描述
父進程一直在等待當子進程運行完成,子進程運行以后,父進程對子進程進行了回收,此時程序中就只剩下父進程,子進程已經成功被回收了。

2. waitpid方法

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:
WNOHANG: 若pid指定的子進程沒有結束,則waitpid()函數返回0,不予以等待。若正常結束,則返回該子進程的ID。

創建子進程后,父進程可使用waitpid函數一直等待子進程(此時將waitpid的第三個參數設置為0),直到子進程退出后讀取子進程的退出信息。

  1 #include<stdio.h>2 #include<unistd.h>3 #include<sys/wait.h>4 #include<stdlib.h>5 int main()6 {7     pid_t id = fork();8     if(id < 0)9     {10         perror("fork()");11     }12     else if(id == 0)13     {14         int cnt = 5;15         while(cnt)16         {17             printf("I am child: pid:%d, ppid:%d\n", getpid(), getppid());18             sleep(1);19             cnt--;20         }21         exit(1);22     }23     else24     {25         int status = 0;26         pid_t result = waitpid(id, &status, 0);27         if(result > 0)28         {29             printf("wait child sucess: result:%d\n", result);                                        30             if(WIFEXITED(status))31             {32                 printf("子進程退出碼:%d\n",WEXITSTATUS(status));33             }34             else35             {36                 printf("子進程收到的退出信號:%d\n", status & 0x7F);37             }38         }39     }40     return 0;41 }

當子進程正常退出時,父進程等待子進程成功:
在這里插入圖片描述
我們可以嘗試使用kill -9命令將子進程殺死,這時父進程也能等待子進程成功,但是子進程屬于異常退出。
在這里插入圖片描述
注意: 被信號殺死而退出的進程,其退出碼將沒有意義。

阻塞等待與非阻塞等待

阻塞等待

上述例子中,我們可以發現,當子進程未退出時,父進程一直就在等待子進程,其他什么事都沒有做,此時進程父進程就會進入阻塞狀態,當子進程運行完畢,父進程立馬被喚醒,接收子進程pid,此時狀態就叫做阻塞狀態。

非阻塞等待

父進程調用waitpid函數來進行等待,如果子進程沒有退出,waitpid這個系統調用立馬返回,父進程可以干其他事情,而且父進程會不間斷的調用waitpid函數來獲取子進程的運行情況,一旦子進程運行結束,父進程就立馬接收到,這就叫做非阻塞等待。

做法很簡單,向waitpid函數的第三個參數potions傳入WNOHANG,這樣一來,等待的子進程若是沒有結束,那么waitpid函數將直接返回0,不予以等待。而等待的子進程若是正常結束,則返回該子進程的pid。下面以一段偽代碼展示一下:

在這里插入圖片描述

  1 #include<iostream>2 #include<vector>3 #include<stdio.h>4 #include<unistd.h>5 #include<sys/wait.h>6 #include<stdlib.h>7 8 typedef void (*hander_t)();9 std::vector<hander_t> handers;10 void fun_one()11 {12     printf("這是一個臨時任務1\n");13 }14 void fun_two()15 {16     printf("這是一個臨時任務2\n");17 }18 void Load()19 {20     handers.push_back(fun_one);21     handers.push_back(fun_two);22 }23 int main()24 {25     pid_t id = fork();26     if(id == 0)27     {28         int cnt = 5;29         while(cnt)30         {31             printf("I am child: %d\n", cnt);32             sleep(1);33             cnt--;34         }35         exit(1);36     }37     else38     {39         int quit = 0;40         while(!quit)41         {42             int status = 0;43             pid_t ret = waitpid(-1, &status, WNOHANG);44             if(ret > 0)45             {46                 printf("wait child sucess: exit code:%d\n", WIFEXITED(status));47                 break;48             }                                                                                                                                                                                                                                                                                                                                        49             else if(ret == 0)50             {51                 printf("The child process is still running, the parent processcan handle other things!!\n");52                 if(handers.empty())53                 {54                     Load();55                 }56                 for(auto e : handers)57                 {58                     e();59                 }60             }61             else62             {63                 printf("wait failed\n");64                 break;65             }66             sleep(1);67         }68     }69 }

此刻在運行程序我們可以發現,在等待期間,父進程也可以處理其他事情:
在這里插入圖片描述

進程替換

替換原理

用fork創建子進程后執行的是和父進程相同的程序(但有可能執行不同的代碼分支),子進程往往要調用一種exec函數以執行另一個程序。當進程調用一種exec函數時,該進程的用戶空間代碼和數據完全被新程序替換,從新程序的啟動例程開始執行。調用exec并不創建新進程,所以調用exec前后該進程的id并未改變。
在這里插入圖片描述

當程序替換之后,有沒有創建新的進程?

程序替換只是將物理內存空間的代碼以及數據進行了替換,程序的PCB,虛擬地址空間以及頁表并沒發生改變,只是改變當前頁表的映射關系,所以并沒有創建新進程。

子進程進行替換后,會影響父進程的代碼和數據嗎?

子進程剛被創建時,與父進程共享代碼和數據,此時子進程需要被替換,就需要將父子進程共享的代碼和數據進行寫時拷貝,父子進程的代碼和數據就發生了分離,所以對子進程替換是不會影響父進程的代碼和數據的。

替換函數

其實有六種以exec開頭的函數,統稱exec函數:

1.int execl(const char* path, const char* arg, ...);

第一個參數為需要執行程序的路徑,第二個參數為可變參數列表,表示你要如何執行這個程序,并以NULL結尾。

在這里插入圖片描述
在這里插入圖片描述

調用execl函數以后,當前進程的所有數據和代碼都會被進行替換,包括已經執行的和未執行的,上述程序中printf已經被執行完畢,打印出來了,所以會顯示出來,本質上其實他已經被替換了。

2. int execv(const char* path, char* const argv[]);

第一個參數表示可執行程序的路徑,第二個參數是一個指針數組,存放的是你要如何執行這個可執行程序,數組以NULL結尾。

在這里插入圖片描述
在這里插入圖片描述

3. int execlp(const char* file, const char* arg, ...);

第一個參數是要執行程序的名字,第二個參數是可變參數列表,表示你要如何執行這個程序,并以NULL結尾。
在這里插入圖片描述
在這里插入圖片描述

4. int execvp(const char* file, const char* const argv[]);

第一個參數表示可執行程序的路徑,第二個參數是一個指針數組,存放的是你要如何執行這個可執行程序,數組以NULL結尾。

在這里插入圖片描述
在這里插入圖片描述

5. int execle(const char *path, const char *arg, ...,char *const envp[]);

第一個參數是要執行程序的路徑,第二個參數是可變參數列表,表示你要如何執行這個程序,并以NULL結尾,第三個參數是你自己設置的環境變量。

在這里插入圖片描述
在這里插入圖片描述

6. int execve(const char *path, char *const argv[], char *const envp[]);

第一個參數是要執行程序的路徑,第二個參數是一個指針數組,數組當中的內容表示你要如何執行這個程序,數組以NULL結尾,第三個參數是你自己設置的環境變量。

在這里插入圖片描述
在這里插入圖片描述
下面我們可以看見子進程進行替換是的狀態:
在這里插入圖片描述
在這里插入圖片描述

函數解釋

  • 這些函數如果調用成功則加載新的程序從啟動代碼開始執行,不再返回。
  • 如果調用出錯則返回-1
  • 所以exec函數只有出錯的返回值而沒有成功的返回值。

命名理解

  • l(list) : 表示參數采用列表 ;
  • v(vector) : 參數用數組 ;
  • p(path) : 有p自動搜索環境變量 PATH;
  • e(env) 表示自己維護環境變量;
函數名參數格式是否帶路徑是否使用當前環境變量
execl列表不是
execlp列表
execle列表不是不是,必須自己組裝環境變量
execv數組不是
execvp數組不是,必須自己組裝環境變量
execve數組不是

事實上,只有execve是真正的系統調用,其它五個函數最終都調用 execve,所以execve在man手冊 第2節,其它函數在man手冊第3節。這些函數之間的關系如下圖所示:
在這里插入圖片描述

做一個簡易的shell

shell建立一個新的進程,然后在那個進程中運行ls程序并等待那個進程結
束,然后shell讀取新的一行輸入,建立一個新的進程,在這個進程中運行程序 并等待這個進程結束。
所以要寫一個shell,需要循環以下過程:

  1. 獲取命令行
  2. 解析命令行
  3. 建立一個子進程(fork)
  4. 替換子進程(execvp)
  5. 父進程等待子進程退出(wait)

在這里插入圖片描述

    1 #include<stdio.h>2 #include<stdlib.h>3 #include<string.h>4 #include<unistd.h>5 #include<sys/wait.h>6 7 #define NUM 10248 #define SIZE 329 #define SEP " "10 //保存完整的字符11 char cmd_line[NUM];12 //保存打散之后的字符串13 char* g_argv[SIZE];14 15  int main()16 {17     while(1)18     {19         //1.打印出提示信息:"[root@localhost myshell]# "20         printf("[root@localhost myshell]# ");21         fflush(stdout);22         memset(cmd_line, '\0', sizeof cmd_line);23         //2.獲取用戶輸入的各種鍵盤指令:"ls -a -l -i"24         if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)25         {26             continue;27         }28         cmd_line[strlen(cmd_line) - 1] = '\0';29         //3.命令行字符串解析:"la -a -l -i" -> "ls" "-a" "-l" "-i"30         g_argv[0] = strtok(cmd_line, SEP);31         int index = 1;32         if(strcmp(g_argv[0], "ls") == 0)33         {
W> 34             g_argv[index++] = "--color=auto";35         }36         if(strcmp(g_argv[0], "ll") == 0)37         {
W> 38             g_argv[0] = "ls";
W> 39             g_argv[index++] = "-l";
W> 40             g_argv[index++] = "--color=auto";41         }
W> 42         while(g_argv[index++] = strtok(NULL, SEP));//第二次解析原始字符串,出入NULL43         //4.讓父進程自己執行命令44         if(strcmp(g_argv[0], "cd") == 0)\45         {46             if(g_argv[1] != NULL)47             {48                 chdir(g_argv[1]);49             }50             continue;51         }52         //5. fork()53         pid_t id = fork();54         if(id < 0)55         {56             perror("fork()");57             return 1;58         }59         else if(id == 0)60         {61             printf("下面程序是由子進程運行的\n");62             execvp(g_argv[0], g_argv);63             exit(1);64         }                                                                                                                                                                                                                                                                                                                                                                                                                     65         else66         {67             int status = 0;68             pid_t ret = waitpid(-1, &status, 0);69             if(ret > 0)70             {71                 printf("wait sucesss: exit code:%d\n", WEXITSTATUS(status));72             }73         }74 75     }76     return 0;77 }

結果演示:
在這里插入圖片描述

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

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

相關文章

re學習(32)【綠城杯2021】babyvxworks(淺談花指令)

鏈接&#xff1a;https://pan.baidu.com/s/1msA5EY_7hoYGBEema7nWwA 提取碼&#xff1a;b9xf wp:首先找不到main函數&#xff0c;然后尋找特殊字符串&#xff0c; 交叉引用 反匯編 主函數在sub_3D9當中&#xff0c;但是IDA分析錯了 分析錯誤后&#xff0c;刪除函數 創建函數 操…

【Linux】多線程1——線程概念與線程控制

文章目錄 1. 線程概念什么是線程Linux中的線程線程的優點線程的缺點線程的獨立資源和共享資源 2. 線程控制Linux的pthread庫用戶級線程 &#x1f4dd; 個人主頁 &#xff1a;超人不會飛)&#x1f4d1; 本文收錄專欄&#xff1a;《Linux》&#x1f4ad; 如果本文對您有幫助&…

無腦入門pytorch系列(三)—— nn.Linear

本系列教程適用于沒有任何pytorch的同學&#xff08;簡單的python語法還是要的&#xff09;&#xff0c;從代碼的表層出發挖掘代碼的深層含義&#xff0c;理解具體的意思和內涵。pytorch的很多函數看著非常簡單&#xff0c;但是其中包含了很多內容&#xff0c;不了解其中的意思…

SpringBoot復習:(46)全局的bean懶加載是怎么實現的?

在application.properties中配置&#xff1a; spring.main.lazy-initializationtrue在運行SpringApplication的run方法時&#xff0c;代碼如下&#xff1a; 其中調用了prepareContext,prepareContext代碼如下&#xff1a; 當在配置文件中配置了spring.main.lazy-initializat…

JavaScript實現在線Excel的附件上傳與下載

摘要&#xff1a;本文由葡萄城技術團隊于CSDN原創并首發。轉載請注明出處&#xff1a;葡萄城官網&#xff0c;葡萄城為開發者提供專業的開發工具、解決方案和服務&#xff0c;賦能開發者。 前言 在本地使用Excel時&#xff0c;經常會有需要在Excel中添加一些附件文件的需求&am…

Clickhouse基于文件復制寫入

背景 目前clickhouse社區對于數據的寫入主要基于文件本地表、分布式表方式為主&#xff0c;但缺乏大批量快速寫入場景下的數據寫入方式&#xff0c;本文提供了一種基于clickhouse local 客戶端工具分布式處理hdfs數據表文件&#xff0c;并將clickhouse以文件復制的方式完成寫入…

解決并發沖突:Java實現MySQL數據鎖定策略

在并發環境下&#xff0c;多個線程同時對MySQL數據庫進行讀寫操作可能會導致數據沖突和不一致的問題。為了解決這些并發沖突&#xff0c;我們可以采用數據鎖定策略來保證數據的一致性和完整性。下面將介紹如何使用Java實現MySQL數據鎖定策略&#xff0c;以及相關的注意事項和最…

開源低代碼平臺Openblocks

網友 HankMeng 想看低代碼工具&#xff0c;正好手上有一個&#xff1b; 什么是 Openblocks &#xff1f; Openblocks 是一個開發人員友好的開源低代碼平臺&#xff0c;可在幾分鐘內構建內部應用程序。 傳統上&#xff0c;構建內部應用程序需要復雜的前端和后端交互&#xff0c;…

如何保證微信小游戲存檔不丟失?

引言 微信小游戲的興起為玩家提供了一個輕松便捷的娛樂方式&#xff0c;然而&#xff0c;存檔丟失問題一直以來都是開發者和玩家關注的焦點。為了確保玩家的游戲體驗和投入能夠得到充分的保障&#xff0c;開發團隊需要采取一系列方法來保障微信小游戲存檔不丟失。本文將介紹一…

學習筆記十五:基于YUM文件運行POD應用

基于YUM文件運行POD應用 通過資源清單文件創建第一個Pod更新資源清單文件查看pod是否創建成功查看pod的ip和pod調度到哪個節點上假如pod里有多個容器&#xff0c;進入到pod里的指定容器查看pod詳細信息查看pod具有哪些標簽&#xff1a;刪除pod通過kubectl run創建Pod Pod資源清…

word之插入尾注+快速回到剛才編輯的地方

1-插入尾注 在編輯文檔時&#xff0c;經常需要對一段話插入一段描述或者附件鏈接等&#xff0c;使用腳注經常因占用篇幅較大導致文檔頁面內容雜亂&#xff0c;這事可以使用快捷鍵 ControlaltD 即可在 整個行文的末尾插入尾注&#xff0c;這樣文章整體干凈整潔&#xff0c;需…

【枚舉邊+MST+組合計數】CF1857G

Problem - 1857G - Codeforces 題意&#xff1a; 思路&#xff1a; 首先觀察一下樣例&#xff1a; 可以發現對于每一對點&#xff0c;貢獻是 s - 這對點對應的環的最大邊 1 那么這樣就有了 n^2 的做法 然后&#xff0c;根據慣用套路&#xff0c;枚舉樹上的點對問題可以轉…

Prometheus的搭建與使用

一、安裝Prometheus 官網下載地址&#xff1a;Download | Prometheus 解壓&#xff1a;tar -zxvf prometheus-2.19.2.linux-amd64.tar.gz重命名&#xff1a; mv prometheus-2.19.2.linux-amd64 /home/prometheus進入對應目錄&#xff1a; cd /home/prometheus查看配置文件&am…

淺析市面電商CRM系統|排單系統存在的不足

筆者做CRM尤其是電商CRM系統7年&#xff0c;相信我的分享能夠幫助大家對電商CRM有個清晰的認知。 系統本身是用來提升效率的&#xff0c;針對不少電商賣家或服務商&#xff0c;都有使用CRM系統來管理粉絲鏈接與營銷、銷售推廣等環節&#xff0c;來實現完整的CRM鏈路。尤其是在當…

OpenCV-Python中的圖像處理-傅里葉變換

OpenCV-Python中的圖像處理-傅里葉變換 傅里葉變換Numpy中的傅里葉變換Numpy中的傅里葉逆變換OpenCV中的傅里葉變換OpenCV中的傅里葉逆變換 DFT的性能優化不同濾波算子傅里葉變換對比 傅里葉變換 傅里葉變換經常被用來分析不同濾波器的頻率特性。我們可以使用 2D 離散傅里葉變…

2308C++對稱轉移

原文 了解對稱轉移 協程組提供了個編寫異步代碼的絕妙方法,與同步代碼一樣.只需要在合適地點加上協待,編譯器就會負責掛起協程,跨掛起點保留狀態,并在操作完成后恢復協程. 但是,最初有個令人討厭的限制,如果不小心,很容易導致棧溢出.如果想避免它,則必須引入額外同步成本,以…

Unity Spine幀事件

SpinePro中添加事件幀 首先 選中右上角的層級樹 然后選擇事件選項 最后在右下角看到 新建 點擊它 新建一個事件 點擊左上角的設置按鈕 彈出編輯窗口 編輯窗口 在右上角 動畫欄 可以切換對應的動畫 點坐邊的那個小灰點來切換 亮點代表當前動畫 選中幀 添加事件 點擊對應事件…

突破防線!泛微OA任意文件上傳Getshell

子曰&#xff1a;“巧言令色&#xff0c;鮮矣仁。” 漏洞復現 訪問漏洞url&#xff1a; 存在漏洞的路徑為 /weaver/weaver.common.Ctrl/.css?arg0com.cloudstore.api.service.Service_CheckApp&arg1validateApp漏洞利用&#xff1a; 漏洞證明&#xff1a; 文筆生疏&…

ubuntu 20.0.4 搭建nvidia 顯卡環境

一、安裝docker 1、安裝dokcer sudo apt install docker.io2、docker 添加到用戶組 創建docker用戶組 sudo groupadd docker添加當前用戶加入docker用戶組 sudo usermod -aG docker ${USER}重啟docker服務 sudo systemctl restart docker切換或者退出當前賬戶再從新登入 …

openGauss學習筆記-41 openGauss 高級數據管理-匿名塊

文章目錄 openGauss學習筆記-41 openGauss 高級數據管理-匿名塊41.1 語法41.2 參數說明41.3 示例 openGauss學習筆記-41 openGauss 高級數據管理-匿名塊 匿名塊&#xff08;Anonymous Block&#xff09;是存儲過程的字塊之一&#xff0c;沒有名稱。一般用于不頻繁執行的腳本或…