linux系統編程---進程總結

進程控制總結

  • 1 進程創建的三種方式
    • fork
    • vfrok
    • clone
  • 2 進程終止
    • 進程正常退出
      • return
      • exit
      • _exit
    • 進程異常退出
      • 進程收到某個信號,而該信號使進程終止
      • abort
  • 3 進程等待
    • 進程等待的方法
      • wait
      • waitpid
  • 4 進程替換
    • 替換原理
    • 替換函數
    • 制作一個簡單的shell

1 進程創建的三種方式

參考文章:
https://zhuanlan.zhihu.com/p/498427466?utm_source=wechat_session&utm_medium=social&utm_oi=977698418977746944&utm_campaign=shareopn

https://blog.csdn.net/gogokongyin/article/details/51178257

在linux中主要提供了fork、vfork、clone三個進程創建方法。在Linux源碼中,這三個調用的執行過程是執行fork()、vfork()、clone()時,通過一個系統調用表映射到sys_fork()、sys_vfork()和sys_clone(),再在這三個函數中去調用do_fork()去做具體的創建進程工作。

fork

fork創建一個進程時,復制出來的子進程有自己的task_struct結構體和pid,然后復制父進程其他所有的資源。
例如,要是父進程打開了五個文件,那么子進程也有五個打開的文件,而且這些文件的當前讀寫指針也停在相同的地方。

這樣得到的子進程獨立于父進程,具有良好的并發性。但是子進程需要復制父進程很多資源,所以fork是一個開銷很大的系統調用,這些開銷并不是所有的情況下都是必須的,比如某進程fork出一個子進程,其子進程僅僅是為了調用exec執行另一個可執行文件,那么fork過程對于虛擬空間的復制將是一個多余的過程。

但由于現在Linux采取了copy-on-write(寫時復制)技術,fork最初不會真的產生兩個不同的拷貝。寫時復制是在推遲真正的數據拷貝,若后來確實發生了寫入,那意味著父進程和子進程的數據不一致了,就需要產生復制動作,每個進程拿到屬于自己的那一份。所以有了寫時復制后,vfork其實現意義就不大了。

fork調用一次,返回兩個值,對于父進程,返回的是子進程的pid值,對于子進程,返回的是0 。 在fork之后,子進程和fork都會繼續執行fork調用之后的指令。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>int main(void)
{int a=5,b=2;pid_t pid;pid = fork();if(pid==0){/*這是子進程 */a = a-4;printf("child process  PID = %d,a=%d,b=%d\n",getpid(),a,b);}else if(pid >0){/* 這是父進程 */printf("parent process PID = %d, a=%d,b=%d\n",getpid(),a,b);}else{perror("fork error");exit(1);}return 0;
}

在這里插入圖片描述
可見,子進程中將變量a的值該為1,而進程中則保持不變。

vfrok

vfork系統調用不同于fork,用vfork創建的子進程與父進程共享地址空間,也就是說子進程完全運行在父進程的地址空間上,如果這時子進程修改了某個變量,這將影響父進程。

因此,如果fork的例程改用vfork的話,那么兩次打印a、b的值是相同的,所在地址也是相同的。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>int main(void)
{int a=5,b=2;pid_t pid;pid = vfork();if(pid==0){/*這是子進程 */a = a-4;printf("child process  PID = %d,a=%d,b=%d\n",getpid(),a,b);exit(0);}else if(pid >0){/* 這是父進程 */printf("parent process PID = %d, a=%d,b=%d\n",getpid(),a,b);}else{perror("fork error");exit(1);}return 0;
}

在這里插入圖片描述

但此處有一點要注意的是,用vfork創建的子進程必須先調用exit()來結束,否則子進程將不能結束,fork則不存在這個情況。

vfork也是在父進程中返回子進程的進程號,在子進程中返回0,用vfork創建子進程后,父進程會被阻塞直到子進程調用exec(exec將一個新的可執行文件載入到地址空間并執行)或exit。vfork的好處是在子進程被創建后往往僅僅是為了調用exec執行另一個程序,因為它就不會對父進程的地址空間由任何引用,因此通過vfork共享內存可以減少不必要的開銷。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>int main(void)
{int a=5,b=2;pid_t pid;pid = fork();if(pid==0){/*這是子進程 */if(execl("./vfork_example","example",NULL)<0){perror("exec error");exit(1);}}else if(pid >0){/* 這是父進程 */printf("parent process  a=%d,b=%d,the address a = %p ,b=%p\n",a,b,&a,&b);}else{perror("vfork error");exit(1);}return 0;
}

vfork_example.c

#include <stdio.h>
#include <unistd.h>
int main(void)
{int a=1,b=2;sleep(3);printf("child process,a=%d,b=%d,the address a =%p,b =%p\n",a,b,&a,&b);return 0;
}

在這里插入圖片描述
子進程調用了exec,父進程會繼續執行,子進程sleep(3),所以父進程會提前結束。

clone

系統調用fork()和vfork()是無參數的,而clone()則帶有參數。fork()是全部復制,vfork()是共享內存,而clone是可以將父進程資源有選擇地復制給子進程,而沒有復制的數據結構則通過指針的復制讓子進程共享,具體要復制那些資源給子進程,由參數列表中的clone_flags來決定。

int clone(int (*fn)(void *), void *child_stack,int flags, void *arg, .../* pid_t *ptid, void *newtls, pid_t *ctid */ );
 fn為函數指針,此指針指向一個函數體,即想要創建進程的靜態程序(我們知道進程的4要素,這個就是指向程序的指針,就是所謂的“劇本", );child_stack為給子進程分配系統堆棧的指針(在linux下系統堆棧空間是2頁面,就是8K的內存,其中在這塊內存中,低地址上放入了值,這個值就是進程控制塊task_struct的值);arg就是傳給子進程的參數一般為(0);flags為要復制資源的標志,描述你需要從父進程繼承那些資源(是資源復制還是共享,在這里設置參數:

下面是flags可以取的值

標志含義
CLONE_PARENT創建的子進程的父進程是調用者的父進程,新進程與創建它的進程成了“兄弟”而不是“父子”
CLONE_FS子進程與父進程共享相同的文件系統,包括root、當前目錄、umask
CLONE_FILES子進程與父進程共享相同的文件描述符(file descriptor)表
CLONE_NEWNS在新的namespace啟動子進程,namespace描述了進程的文件hierarchy
CLONE_SIGHAND子進程與父進程共享相同的信號處理(signal handler)表
CLONE_PTRACE若父進程被trace,子進程也被trace
CLONE_VFORK父進程被掛起,直至子進程釋放虛擬內存資源
CLONE_VM子進程與父進程運行于相同的內存空間
CLONE_PID子進程在創建時PID與父進程一致
CLONE_THREADLinux 2.4中增加以支持POSIX線程標準,子進程與父進程共享相同的線程群
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>int variable,fd;int do_something(void*arg)
{variable = 42;printf("in child process\n");close(fd);return 0;
}int main(void)
{void *child_stack;char tempch;variable = 9;fd = open("./test.txt",O_RDONLY);child_stack=(void *)malloc(16384);printf("The varibale is %d\n",variable);clone(do_something,child_stack,CLONE_VM|CLONE_FILES,NULL);sleep(3);printf("The variable is now %d\n",variable);if(read(fd,&tempch,1)<1){perror("file read error");exit(1);}printf("we could read from the file\n");return 0;
}

在這里插入圖片描述
我們在clone指定了CLONE_VM和CLONE_FILES,所以子進程與父進程共享相同的文件描述符(file descriptor)表以及子進程與父進程運行于相同的內存空間,所以會出現上述情況。

2 進程終止

參考文章:
https://zhuanlan.zhihu.com/p/435709371

https://zhuanlan.zhihu.com/p/63424197

進程正常退出

return

在main函數中使用return退出進程。return num等同于exit(num),所做的事可以看下面的exit介紹。

exit

exit函數可以在代碼中任何位置使進程退出,并且exit在退出進程前還會做一系列工作:

  1. 調用用戶通過atexit或on_exit定義的函數
  2. 關閉所有打開的流,所有的緩存數據均被刷新
  3. 調用_exit函數終止進程。
#include <stdio.h>
#include <stdlib.h>void show()
{printf("hello  world");exit(1);
}
int main(void)
{show();return 0;
}

終止進程前會將緩沖區當中的數據輸出。
在這里插入圖片描述

_exit

_exit函數也可以在代碼中的任何地方退出進程,但是_exit函數會直接終止進程,并不會在退出進程前會做任何收尾工作。
我們將上面代碼中的exit函數改成_exit函數,運行會沒有輸出。

進程異常退出

進程收到某個信號,而該信號使進程終止

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

abort

調用abort()函數,會使進程異常終止。

3 進程等待

https://zhuanlan.zhihu.com/p/435709371

進程等待的方法

wait

pid_t wait(int* status);

等待任意子進程退出,status保存子進程的退出碼。所以父進程會被阻塞,直到子進程退出。WEXITSTATUS(status)宏可以獲取子進程的退出值。

on success, returns the process ID of the terminated child; on error, -1 is returned.

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>int main(void)
{pid_t pid = fork();if(pid==0){int count = 10;while(count--){printf("Child process : PID = %d; PPID : %d\n",getpid(),getppid());sleep(1);}}else if(pid>0){int status;pid_t ret = wait(&status);if(ret>0){printf("wati child success \n");printf("child process pid = %d,return status=%d\n",ret,WEXITSTATUS(status));}}else{printf("fork error\n");exit(1);}exit(0);
}

在這里插入圖片描述

waitpid

函數原型:

pid_t waitpid(pid_t pid, int *wstatus, int options);

參數含義:
pid:

     < -1   meaning wait for any child process whose process group ID is equal to the absolute value of pid.-1     meaning wait for any child process.0      meaning wait for any child process whose process group ID is equal to that of the calling process.> 0    meaning wait for the child whose process ID is equal to the value of pid.

options的值是下面0個或多個或(OR)值

  • WNOHANG (wait no hung): 即使沒有子進程退出,它也會立即返回,直接返回0,不會像wait那樣永遠等下去。
  • WUNTRACED :用于調試。
    如果孩子已經停止(但沒有通過 ptrace(2) 跟蹤),也會返回。 即使未指定此選項,也會提供已停止的跟蹤子項的狀態。

state和wait一樣。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>int main(void)
{pid_t pid = fork();if(pid==0){int count = 10;while(count--){printf("Child process : PID = %d; PPID : %d\n",getpid(),getppid());sleep(1);}}else if(pid>0){int status;pid_t ret = waitpid(pid,&status,0);if(ret>0){printf("wati child success \n");printf("child process pid = %d,return status=%d\n",ret,WEXITSTATUS(status));}}else{printf("fork error\n");exit(1);}exit(0);
}

運行的結果和wait一樣。

4 進程替換

原文鏈接:
https://zhuanlan.zhihu.com/p/435709371

替換原理

用fork創建子進程后,子進程執行的是和父進程相同的程序(但有可能執行不同的代碼分支),如想讓子進程執行另一個程序,往往需要調用一種exec函數。

當進程調用exec函數時,該進程的用戶空間代碼和數據完全被新程序替換,并從新程序的啟動代碼開始執行。
請添加圖片描述

  1. 當進程程序被替換后,有沒有創建新的進程?
    進程程序被替換之后,該進程對應的PCB、進程地址空間 以及頁表等數據結構都沒法發生改變,只是進程在物理內存當中的數據和代碼發生了改變,所有并沒有創建新的進程,而且進程程序替換前后該進程的pid并沒發生改變。

  2. 子進程進行進程程序替換后,會影響父進程的代碼和數據嗎?
    子進程剛被創建時,與父進程共享代碼和數據,但當子進程需要進行進程程序替換時,也就意味著子進程需要對其數據和代碼進行寫入操作,這時便需要將父子進程共享的代碼和數據進行寫時拷貝,此后父子進程的代碼和數據也就分離了,因此子進程進行程序替換后不會影響父進程的代碼和數據。

替換函數

替換函數有六種以exec開頭的函數,它們統稱為exec函數。

	   int execl(const char *path, const char *arg, .../* (char  *) NULL */);int execlp(const char *file, const char *arg, .../* (char  *) NULL */);int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);int execv(const char *path, char *const argv[]);int execvp(const char *file, char *const argv[]);int execve(const char *file, char *const argv[],char *const envp[]);

exec函數的后綴含義如下:

  • l(list):表示參數采用列表的形式
  • v(vector):表示參數采用數組的形式
  • p(path):表示能自動搜素環境變量PATH,進行程序查找
  • e(env):表示可以傳入自己設置的環境變量。

事實上,只有execve才是真正的系統調用,其它五個函數最終都是調用的execve,所以execve在man手冊的第2節,而其它五個函數在man手冊的第3節,也就是說其他五個函數實際上是對系統調用execve進行了封裝,以滿足不同用戶的不同調用場景的。
請添加圖片描述

制作一個簡單的shell

shell也就是命令行解釋器,其運行原理就是:當有命令需要執行時,shell創建子進程,讓子進程執行命令,而shell只需等待子進程退出即可。
請添加圖片描述
其實shell需要執行的邏輯非常簡單,其只需循環執行以下步驟:

  1. 獲取命令行。
  2. 解析命令行。
  3. 創建子進程。
  4. 替換子進程。
  5. 等待子進程退出。

其中,創建子進程使用fork函數,替換子進程使用exec系列函數,等待子進程使用wait或者waitpid函數。

#include <stdio.h>
#include <pwd.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define LEN 1024 //命令最大長度
#define NUM 32 //命令拆分后的最大個數
int main()
{char cmd[LEN]; //存儲命令char* myargv[NUM]; //存儲命令拆分后的結果char hostname[32]; //主機名char pwd[128]; //當前目錄while (1){//獲取命令提示信息struct passwd* pass = getpwuid(getuid());gethostname(hostname, sizeof(hostname)-1);getcwd(pwd, sizeof(pwd)-1);int len = strlen(pwd);char* p = pwd + len - 1;while (*p != '/'){p--;}p++;//打印命令提示信息printf("[%s@%s %s]$ ", pass->pw_name, hostname, p);//讀取命令fgets(cmd, LEN, stdin);cmd[strlen(cmd) - 1] = '\0';//拆分命令myargv[0] = strtok(cmd, " ");int i = 1;while (myargv[i] = strtok(NULL, " ")){i++;}pid_t id = fork(); //創建子進程執行命令if (id == 0){//childexecvp(myargv[0], myargv); //child進行程序替換exit(1); //替換失敗的退出碼設置為1}//shellint status = 0;pid_t ret = waitpid(id, &status, 0); //shell等待child退出if (ret > 0){printf("exit code:%d\n", WEXITSTATUS(status)); //打印child的退出碼}}return 0;
}

請添加圖片描述
說明:
當執行./myshell命令后,便是我們自己實現的shell在進行命令行解釋,我們自己實現的shell在子進程退出后都打印了子進程的退出碼,我們可以根據這一點來區分我們當前使用的是Linux操作系統的shell還是我們自己實現的shell

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

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

相關文章

銀行賬務轉賬系統(事務處理)

流程如下&#xff1a; 創建項目工程如下&#xff1a; transfer包下的代碼如下&#xff1a; package beyond.transfer.dao;import java.sql.Connection; import java.sql.SQLException;import org.apache.commons.dbutils.QueryRunner;import beyond.utils.DataSourceUtils;pu…

【msdn wpf forum翻譯】TextBox中文本 中對齊 的方法

原文鏈接&#xff1a;http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/49864e35-1dbf-4292-a361-93f1a8400558問題&#xff1a;TextBox中文本中對齊&#xff0c;使用 TextBox.HorizontalContentAlignment"Center"行不通&#xff08;TextBox.VerticalConte…

wifi操作及實例

1.什么事WIFI 利用無線路由器上網的協議2.獲取WIFI網卡的狀態 WIFI網卡的狀態是由一系列的整形常量來表示的 有狀態&#xff1a; 網卡不可用WIFI_STATE_DISABLED 對應值為1 網卡正在關閉WIFI_STATE_DISABLING 對應值為0 網卡可用WIFI_STATE_ENABLED 對應的值為3 …

c語言 函數的參數傳遞示例_C語言中帶有示例的remove()函數

c語言 函數的參數傳遞示例C語言中的remove()函數 (remove() function in C) The remove() function is defined in the <stdio.h> header file. remove()函數在<stdio.h>頭文件中定義。 Prototype: 原型&#xff1a; int remove(const char* filename);Parameter…

使用ThreadLocal綁定連接資源(事務)

dao層代碼如下&#xff1a; package beyond.transfer.dao;import java.sql.Connection; import java.sql.SQLException;import org.apache.commons.dbutils.QueryRunner;import beyond.utils.DataSourceUtils; import beyond.utils.MyDataSourceUtils;public class TransferDa…

算法---棧和隊列

棧和隊列1 棧棧的順序存儲棧的鏈式存儲2 隊列隊列的順序存儲隊列的鏈式存儲3 棧和隊列的應用用棧實現隊列用隊列實現棧最小棧1 棧 參考文章&#xff1a; https://zhuanlan.zhihu.com/p/346164833 https://zhuanlan.zhihu.com/p/120965372#:~:text%E6%A0%88%E6%98%AF%E4%B8%80%…

學習網站LIST

面向對象的腳本語言Rubyhttp://rubycn.ce-lab.net/20020101.htmlRUBY文檔中心http://www.moer.net/ruby/doc/TCL腳本http://www.tclchina.com/Python快速入門http://wiki.woodpecker.org.cn/moin/WeiZhong/2006-01-17Python 研究(Dive Into Python)http://www.woodpecker.org.c…

再次參加(第七屆)商學院徒步戈壁挑戰賽,賦詞幾首

2012年5月21-25日&#xff0c;再次踏上甘肅莫賀延磧戈壁&#xff0c;參加第七屆商學院徒步戈壁挑戰賽。時隔五年&#xff0c;時空轉換。 少年游 ——戈壁緣 江南物華&#xff0c;遠水碧山&#xff0c;燈火相掩映。暮宴朝歡&#xff0c;酒綠燈紅&#xff0c;躑躅夜歸人。 孤城落…

Java StackTraceElement toString()方法與示例

StackTraceElement類的toString()方法 (StackTraceElement Class toString() method) toString() method is available in java.lang package. toString()方法在java.lang包中可用。 toString() method is used to represent stack trace element as a string or in other word…

增刪改查

web層代碼如下&#xff1a; package beyondwsq.web;import java.io.IOException; import java.sql.SQLException; import java.util.List;import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; imp…

在WebBrowser中通過模擬鍵盤鼠標操控網頁中的文件上傳控件

引言 這兩天沉迷了Google SketchUp&#xff0c;剛剛玩夠&#xff0c;一時興起&#xff0c;研究了一下WebBrowser。 我在《WebBrowser控件使用技巧分享》一文中曾談到過“我現在可以通過WebBrowser實現對各種Html元素的操控&#xff0c;唯獨無法控制Html的上傳控件”&#xff0c…

編寫最簡單的字符設備驅動

編寫最簡單的字符設備驅動1 編寫驅動代碼2 編寫makefile3 編譯和加載驅動4 編寫應用程序測試驅動參考文章&#xff1a; linux驅動開發第1講&#xff1a;帶你編寫一個最簡單的字符設備驅動 linux驅動開發第2講&#xff1a;應用層的write如何調用到驅動中的write 1 編寫驅動代碼…

Java ObjectStreamField toString()方法與示例

ObjectStreamField類toString()方法 (ObjectStreamField Class toString() method) toString() method is available in java.io package. toString()方法在java.io包中可用。 toString() method is used to return a string that defines this field. toString()方法用于返回定…

linux內核文件描述符fd、文件索引節點inode、文件對象file關系

文件描述符fd、文件索引節點inode、文件對象file關系1 VFS對象1.1 超級塊對象1.2 索引節點對象1.3 文件對象1.4 進程描述符1.5 files_struct2 如何根據文件描述符fd找到文件&#xff1f;1 VFS對象 在說fd、inode和file關系之前&#xff0c;我們先了解VFS的幾個概念。分別是進程…

sql2005 獲取表字段信息和視圖字段信息

獲取表字段名,和字段說明SELECT[Table Name]OBJECT_NAME(c.object_id), [ColumnName]c.name, [Description]ex.value FROMsys.columns c LEFTOUTERJOINsys.exte…

解析css之position

CSS的很多其他屬性大多容易理解&#xff0c;比如字體&#xff0c;文本&#xff0c;背景等。有些CSS書籍也會對這些簡單的屬性進行大張旗鼓的介紹&#xff0c;而偏偏忽略了對一些難纏的屬性講解&#xff0c;有避重就輕的嫌疑。CSS中主要難以理解的屬性包括盒型結構&#xff0c;以…

Java ObjectInputStream readLong()方法(帶示例)

ObjectInputStream類readLong()方法 (ObjectInputStream Class readLong() method) readLong() method is available in java.io package. readLong()方法在java.io包中可用。 readLong() method is used to read 8 bytes (i.e. 64 bit) of long value from this ObjectInputSt…

交換瓶子(藍橋杯)

有N個瓶子&#xff0c;編號 1 ~ N&#xff0c;放在架子上。 比如有5個瓶子&#xff1a; 2 1 3 5 4 要求每次拿起2個瓶子&#xff0c;交換它們的位置。 經過若干次后&#xff0c;使得瓶子的序號為&#xff1a; 1 2 3 4 5 對于這么簡單的情況&#xff0c;顯然&#xff0c;至少…

Linux設備驅動開發---字符設備驅動程序

字符設備驅動程序1 主設備和次設備的概念設備號的注冊和釋放靜態方法動態方法區別2 設備文件操作struct file_operations與struct file、struct inode關系3 分配和注冊字符設備class_createcdev_adddevice_create4 字符設備驅動程序字符設備通過字符&#xff08;一個接一個的字…

Java LinkedHashMap getOrDefault()方法與示例

LinkedHashMap類的getOrDefault()方法 (LinkedHashMap Class getOrDefault() method) getOrDefault() method is available in java.util package. getOrDefault()方法在java.util包中可用。 getOrDefault() method is used to get the value associated with the given key el…