前言:
? ? ? ? 上文我們講到了Linux中的虛擬空間地址,知道了一個進程對應一個虛擬地址空間,虛擬空間地址與物理地址之間通過頁表映射....【Linux】虛擬地址空間-CSDN博客
? ? ? ? 本文我們來講一講Linux系統是如何控制進程的!
? ? ? ? 如果喜歡本期文章,請點點關注吧!非常感謝佬的支持? ?_(:з」∠)_? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
進程創建
fork函數
? ? ? ? fork函數是Linux系統提供的接口,其功能就是創建子進程。
? ? ? ? 既調用fork函數,系統就自動為我們創建好了子進程。
#include<unistd.h>
pid_t fork();其中pid_t是Linux中的數據類型,相當于int,即為整型
? ? ? ? fork的返回值有兩個,對于父進程:返回子進程的pid,對于子進程:返回0。
#include <stdio.h>
#include <unistd.h>int main()
{pid_t pid = fork();if(pid<0){ printf("創建失敗");} else if(pid == 0){ //子進程printf("我是一個子進程:%d,這是我的父進程:%d\n",getpid(),getppid());} else if(pid > 0){ printf("我是一個父進程:%d,這是我的父進程:%d\n",getpid(),getppid());}
}
? ? ? ? 對于fork原理的詳細介紹可參考【Linux】初見,進程概念-CSDN博客中的第四節“如何創建進程”,這里面有超級詳細的介紹!
fork的用法
? ? ? ? 一個父進程希望復制自己,使父子進程同時執行不同的代碼段。e.g. 父進程等待客戶端響應,生成子進程處理請求。
? ? ? ? 一個進程想要執行多個不同的代碼。e.g. 生成子進程調用exec函數。
fork失敗原因
? ? ? ? 系統中的進程太多了
? ? ? ? 用戶的進程數量超過了限制
進程終止
進程終止的本質就是進程結束,系統釋放資源:釋放進程申請的相關數據結構和對應的代碼與數據
進程退出的場景
? ? ? ? 1.代碼運行完畢,結果正確(退出碼為0)
? ? ? ? 2.代碼運行完畢,結果不正確(退出碼為非0)
? ? ? ? 3.代碼異常終止(進程接收到型號終止,退出碼無意義)
進程退出方法
正常退出:
? ? ? ? 1.從main函數返回
? ? ? ? 2.調用exit
? ? ? ? 3._exit
異常退出:
? ? ? ? 信號終止
退出碼
? ? ? ? 退出碼可以告訴我們進程終止時的狀態,0代表執行成功,非0則代表不成功。
? ? ? ? 非0的退出碼中,一個值對應一個錯誤原因。可以使用strerror函數獲取退出碼對應的信息。
#include <stdio.h>
#include<string.h>int main()
{for(int i=0;i<200;i++){ printf("%d -> %s\n",i,strerror(i)); }
}
0 -> Success
1 -> Operation not permitted
2 -> No such file or directory
3 -> No such process
4 -> Interrupted system call
5 -> Input/output error
6 -> No such device or address
7 -> Argument list too long
8 -> Exec format error
9 -> Bad file descriptor
10 -> No child processes
11 -> Resource temporarily unavailable
12 -> Cannot allocate memory
13 -> Permission denied
14 -> Bad address
15 -> Block device required
16 -> Device or resource busy
17 -> File exists
18 -> Invalid cross-device link
19 -> No such device
20 -> Not a directory
21 -> Is a directory
22 -> Invalid argument
23 -> Too many open files in system
24 -> Too many open files
25 -> Inappropriate ioctl for device
26 -> Text file busy
27 -> File too large
28 -> No space left on device
29 -> Illegal seek
30 -> Read-only file system
31 -> Too many links
32 -> Broken pipe
33 -> Numerical argument out of domain
34 -> Numerical result out of range
35 -> Resource deadlock avoided
36 -> File name too long
37 -> No locks available
38 -> Function not implemented
39 -> Directory not empty
40 -> Too many levels of symbolic links
41 -> Unknown error 41
42 -> No message of desired type
43 -> Identifier removed
44 -> Channel number out of range
45 -> Level 2 not synchronized
46 -> Level 3 halted
47 -> Level 3 reset
48 -> Link number out of range
49 -> Protocol driver not attached
50 -> No CSI structure available
51 -> Level 2 halted
52 -> Invalid exchange
53 -> Invalid request descriptor
54 -> Exchange full
55 -> No anode
56 -> Invalid request code
57 -> Invalid slot
58 -> Unknown error 58
59 -> Bad font file format
60 -> Device not a stream
61 -> No data available
62 -> Timer expired
63 -> Out of streams resources
64 -> Machine is not on the network
65 -> Package not installed
66 -> Object is remote
67 -> Link has been severed
68 -> Advertise error
69 -> Srmount error
70 -> Communication error on send
71 -> Protocol error
72 -> Multihop attempted
73 -> RFS specific error
74 -> Bad message
75 -> Value too large for defined data type
76 -> Name not unique on network
77 -> File descriptor in bad state
78 -> Remote address changed
79 -> Can not access a needed shared library
80 -> Accessing a corrupted shared library
81 -> .lib section in a.out corrupted
82 -> Attempting to link in too many shared libraries
83 -> Cannot exec a shared library directly
84 -> Invalid or incomplete multibyte or wide character
85 -> Interrupted system call should be restarted
86 -> Streams pipe error
87 -> Too many users
88 -> Socket operation on non-socket
89 -> Destination address required
90 -> Message too long
91 -> Protocol wrong type for socket
92 -> Protocol not available
93 -> Protocol not supported
94 -> Socket type not supported
95 -> Operation not supported
96 -> Protocol family not supported
97 -> Address family not supported by protocol
98 -> Address already in use
99 -> Cannot assign requested address
100 -> Network is down
101 -> Network is unreachable
102 -> Network dropped connection on reset
103 -> Software caused connection abort
104 -> Connection reset by peer
105 -> No buffer space available
106 -> Transport endpoint is already connected
107 -> Transport endpoint is not connected
108 -> Cannot send after transport endpoint shutdown
109 -> Too many references: cannot splice
110 -> Connection timed out
111 -> Connection refused
112 -> Host is down
113 -> No route to host
114 -> Operation already in progress
115 -> Operation now in progress
116 -> Stale file handle
117 -> Structure needs cleaning
118 -> Not a XENIX named type file
119 -> No XENIX semaphores available
120 -> Is a named type file
121 -> Remote I/O error
122 -> Disk quota exceeded
123 -> No medium found
124 -> Wrong medium type
125 -> Operation canceled
126 -> Required key not available
127 -> Key has expired
128 -> Key has been revoked
129 -> Key was rejected by service
130 -> Owner died
131 -> State not recoverable
132 -> Operation not possible due to RF-kill
133 -> Memory page has hardware error
? ? ? ? 使用echo $?,可以打印出最近一個程序的退出碼。
? ? ? ? 退出碼是進程的性質之一,所以退出碼會保存到進程的PCB中。
_exit函數
#include<unistd.h>
void _exit(int status);在任何地方調用_exit函數,都會讓當前進程結束
并以給定的值作為退出碼退出
exit函數
#include<unistd.h>
void exit(int status);與_exit函數功能類似
都是以指定的退出碼,退出當前進程
區別
? ? ? ? _exit函數系統調用,而exit是C語言提供。
? ? ? ? 使用exit函數退出時,會進行緩沖區的刷新。反之_exit則不會。
舉個例子說明:
????????程序結束刷新緩沖區,信息打印在屏幕上,反之沒有信息打印。
????????感興趣的朋友可以看看這篇文章,這里有詳細的緩沖區刷新介紹【Linux】LInux下第一個程序:進度條-CSDN博客
#include <stdio.h>
#include<string.h>
#include<unistd.h>int main()
{printf("yuzuriha");sleep(1);exit(0);
}
#include <stdio.h>
#include<string.h>
#include<unistd.h>int main()
{printf("yuzuriha"); sleep(1);_exit(0);
}
進程等待
進程等待的必要性
? ? ? ? 1.之前我們講過子進程的退出后,子進程回進入僵尸狀態,必須要被父進程回收才行。若子進程一直沒有被父進程回收,就會一直處于僵尸狀態,進而造成內存泄漏。
? ? ? ? 2.進程被創造出來執行任務,結果如何,是成功、失敗還是異常,這是父進程要得知的必要信息。
? ? ? ? 所以回收子進程是必要的,父進程通過進程等待的方式,回收子進程進程資源、獲取子進程的退出信息。
進程等待的方法
wait方法
#include<sys/types.h>
#include<sys/wait.h>pid_t wait(int* status);返回值:等待成功返回對應的子進程pid,失敗則返回-1參數status:為輸出型參數,可以獲取子進程的退出碼若不需要,可以傳NULL忽略這個參數
waitpid函數
#include<sys/types.h>
#include<sys/wait.h>pid_ t waitpid(pid_t pid, int *status, int options);
下面詳細介紹一下每一個參數:
pid_t pid
參數pid:-1,表示可以等待任意一個子進程。
參數pid:>0,表示只能等待當前這個進程的子進程。
status
? ? ? ? 如上面所講,status是一個輸出型參數,由操作系統填充,我們可以通過status獲取進程狀態。
? ? ? ? status并不是一個簡單的整型,細節如下:
? ? ? ? 1.status是一個低16位有效的int,通過位劃分存儲子進程的信息
? ? ? ? 2.正常終止的情況下:低7位全為0,15~8存放退出碼。
? ? ? ? ? ? ? ? 獲取退出碼:WEXITSTATUS(status)
? ? ? ? 3.非正常退出:退出碼無意義,第7位存放標志,6~0存放信號編號
? ? ? ? ? ? ? ? 判斷一個進程是否位正常退出:WIFEXITED(status),正常為真。
options
????????options的默認值是0,表示阻塞等待。
????????options為:WNOHANG時表示非阻塞等待。
? ? ? ? 簡而言之,阻塞等待就是等待子進程結束的過程中,父進程不能運行。而非阻塞等待,就是等待過程中父進程可以執行自己的代碼。
? ? ? ? 當參數為:WNOHANG時,waitpid會進行非阻塞輪詢。等待結束,返回>0(pid);本次調用結束,但子進程還沒有退出,返回0;調用失敗,返回<0;。
進程程序替換?
? ? ? ? 程序替換就是字面上的意思,替換掉原有的程序,先來直接看看效果
#include <stdio.h>
#include<unistd.h>int main()
{printf("開始\n");execl("/usr/bin/ls","ls","-l",NULL);printf("結束\n");
}
? ? ? ? ?我們可以看到執行就第一個printf打印出了結果,第二行就執行就程序替換,將原來的程序替換為了ls指令。
替換原理
????????exec系列函數,其功能就是用新的程序替換原本的程序。
? ? ? ? 如圖,替換程序的本質是將物理內存中的數據,用新數據覆蓋,頁表中的物理地址重新覆蓋填寫,其他的不動。
注意:
? ? ? ? 1.一旦程序替換成功,就會去執行新代碼了,舊代碼以及不復存在了。
? ? ? ? 2.exec系列函數只有在失敗的時候才有返回值,既只要返回就是失敗。
? ? ? ? 3.程序替換并沒有創建新進程,只是進行了數據的覆蓋。
認識全部exec函數
execl
int execl(const char* path , const char* arg , ...)l:代表list,以鏈表的形式傳參
path:路徑+程序名,表示要執行誰
arg:平時怎么寫指令,這里就怎么寫。表示如何執行注:一定要以NULL結尾!!!
#include <stdio.h>
#include<unistd.h>int main()
{printf("開始\n");execl("/usr/bin/ls","ls","-l",NULL);printf("結束\n");
}
不僅可以替換系統程序,也可以替換我們自己寫的程序:
#include <stdio.h>
#include<unistd.h>int main()
{printf("開始\n");execl("./x","./x",NULL); printf("結束\n");
}
? ? ? ? 我們知道數據和代碼父子進程默認是共享的,那程序替換對父進程有影響嗎?
? ? ? ? 沒有,因為數據和代碼都會進行寫時拷貝
execlp
int execlp(const char* file , const char* arg , ...)l:表示list,以鏈表的形式傳參
p:表示環境變量PATHfile:有了PATH環境變量,我們就不用寫路徑了。直接寫我們要執行的程序名即可
arg:同上,與我們平時寫的指令無異但必須以NULL結尾
#include <stdio.h>
#include<unistd.h>int main()
{printf("開始\n");execlp("ls","ls","-l",NULL); printf("結束\n");
}
execv
int execv(const char* path , char* const argv[])v:表示vector,用數組的方式傳參數組中,依舊想要以NULL結尾
#include <stdio.h>
#include<unistd.h>int main()
{char* const argv[]={(char* const)"ls",(char* const)"-l",NULL}; printf("開始\n");execv("/usr/bin/ls",argv); printf("結束\n");
}
?execvp
int execvp(const char* file , char* const argv[])v:vector,以數組形式傳參
p:環境變量PATH
同上,就不舉例了
execvpe
int execvpe(const char* flie , char* const argv[] , char* cosnt envp[])相比上面,多了一個e
e:環境變量!傳入envp數組,那被替換的進程的環境變量會被evnp[]直接覆蓋
這會導致我們無法使用原來的環境變量
? ? ? ? 所以我們一般采用新增環境變量的方式,而不是直接覆蓋。
? ? ? ? 法一:調用系統調用:putevn(char* string)新增環境變量,再調用其他exec函數實現
? ? ? ? 法二:調用系統調用:putevn(char* string)新增環境變量,調用execvpe時,傳入環境變量指針:environ
execle
int execle(const char* path , char* const argv .... , char* cosnt envp[])同上
execve
int execve(const char* path , char* const argv[] ... , char* cosnt envp[])同上
值得一提的是execve的系統調用,而其他函數都是C語言提供的