linux執行ls會引起哪些系統呼叫

2022-03-18 13:00:37

在linux中,執行ls會引起read和exec系統呼叫;執行任何一個shell命令都會呼叫fork和exec,但是通過strace去檢視ls引起的系統呼叫並沒有fork,ls命令要列出目錄下的檔案,所以要呼叫read。

本教學操作環境:linux7.3系統、Dell G3電腦。

linux執行ls會引起哪些系統呼叫

答案是read、exec系列

shell命令執行機制就是 fork+exec, fork是分身,execve是變身。ls命令要列出目錄下的檔案,所以read也會呼叫。

shell存取Linux核心就是通過fork和exec命令實現的,fork命令建立可以一個相同的執行緒出來。

通過strace去檢視ls引起的系統呼叫,確實是沒有fork,但是因為執行任何一個shell命令都會呼叫fork

execve的變身就是建立一個新的程序,並用新的程序去替換掉原來的程序。

首先我們討論一下什麼是系統呼叫(system calls)?
使用者藉助UNIX/linux直接提供的少量函數可以對檔案和裝置進行存取和控制,這些函數就是系統呼叫[1]。

使用strace ls命令我們可以檢視ls命令使用到的系統呼叫[2],其中一部分輸出內容如下:

open(".", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY|O_CLOEXEC) = 3
getdents64(3, /* 68 entries */, 32768)  = 2240
getdents64(3, /* 0 entries */, 32768)   = 0
close(3)                                = 0

open系統呼叫開啟當前目錄檔案,返回獲得的檔案描述符。可以看到該檔案使用O_RDONLY標誌開啟。

只要該檔案是用O_RDONLY或O_RDWR標誌開啟的,就可以用read()系統呼叫從該檔案中讀取位元組[3]。

所以ls要用到read系統呼叫。除此之外,任何shell命令都會建立程序,都會用到exec系統呼叫。

回過頭來梳理一下我們對於這些概念可能產生的疑惑:

  1. 包括ls在內,一個程式是如何執行的?
  2. open系統呼叫開啟當前目錄檔案,返回獲得的檔案描述符。那什麼是檔案描述符?

1 程序是如何執行的

每個執行中的程式被稱為程序[1]

Unix將程序建立與載入一個新程序映象分離。這樣的好處是有更多的餘地對兩種操作進行管理。當我們建立了一個程序之後,通常將子程序替換成新的程序映象。所以任何shell命令都會建立程序,都會用到exec系統呼叫。
例如:在shell命令列執行ps命令,實際上是shell程序呼叫fork複製一個新的子程序,在利用exec系統呼叫將新產生的子程序完全替換成ps程序。

用exec函數可以把當前程序替換為一個新程序,且新程序與原程序有相同的PID。exec名下是由多個關聯函陣列成的一個完整系列[4]

呼叫fork建立新程序後,父程序與子程序幾乎一模一樣[1,p398]。

fork是一個UNIX術語,當fork一個程序(一個執行中的程式)時,基本上是複製了它,並且fork後的兩個程序都從當前執行點繼續執行,並且每個程序都有自己的記憶體副本。

原程序是父程序,新程序是子程序。可以通過fork()返回值區分。

父程序中fork呼叫返回的是新的子程序的pid(process id),而子程序中fork呼叫返回的是0

舉個例子:

#include<unistd.h>
#include<stdio.h>
#define LEN 10
int main()
{
    pid_t id=getpid();
    printf("Main pid: %d \n",id);
	int i;
	pid_t res=fork();
	if(res==0)
	{
	  for(i =0;i<LEN;i++) 
	  {
		pid_t id1=getpid();
		printf("%d ",id1);
		printf("Child process:%d\n",i);
	  }
	}
	else
	{
	  printf("res %d\n",res);
	  for(i=0;i<LEN;i++) 
	  {
		pid_t  id2=getpid();
		printf("%d ",id2);
		printf("parent process:%d\n",i);
	  }
	}

	printf("THE END\n");
	 return 0;
}

/*output
Main pid: 10965 
res 10966
10965 parent process:0
10965 parent process:1
10965 parent process:2
10965 parent process:3
10965 parent process:4
10965 parent process:5
10965 parent process:6
10965 parent process:7
10965 parent process:8
10965 parent process:9
10966 Child process:0
10966 Child process:1
THE END
10966 Child process:2
10966 Child process:3
10966 Child process:4
10966 Child process:5
10966 Child process:6
10966 Child process:7
10966 Child process:8
10966 Child process:9
THE END
*/

如果想要程式啟動另一程式的執行但自己仍想繼續執行的話,怎麼辦呢?那就是結合fork與exec的使用[6][1, p397]

舉個例子(修改自[6]):

#include<string.h>
 #include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
#include<unistd.h>
 
 char command[256];
 void main()
 {
    int rtn; /*子程序的返回數值*/
    while(1) {
       /* 從終端讀取要執行的命令 */
       printf( ">" );
       fgets( command, 256, stdin );
       command[strlen(command)-1] = 0;
       if ( fork() == 0 ) {/* 子程序執行此命令 */
          execlp( command, NULL );
          /* 如果exec函數返回,表明沒有正常執行命令,列印錯誤資訊*/
          perror( command );
          exit( errno );
       }
       else {/* 父程序, 等待子程序結束,並列印子程序的返回值 */
          pid_t sonid=wait ( &rtn );
          printf(" child pid: %d\n",sonid);
          printf( " child process return %d\n", rtn );
       }
   }
}

/*output:錯誤命令、需要引數命令、正確命令
>aa
aa: No such file or directory
 child pid: 11230
 child process return 512
>echo
A NULL argv[0] was passed through an exec system call.
 child pid: 11231
 child process return 134
>ps
 child pid: 11247
 child process return 139
*/

先fork,然後子程序藉助exec呼叫程式command。對錯誤命令、需要引數的命令、以及不需要引數的命令給出對應的輸出。

2 檔案描述符(file descripter,fd)

一切裝置都可以看作檔案。

對核心而言,所有開啟的檔案都通過檔案描述符參照[7]。檔案描述符是非負整數,範圍是[0,OPEN_MAX -1]。現在OPEN_MAX 一般為64

但是[7]又說對於FreeBSD 8.0,Linux 3.2.0 ,Mac OS X 10.6.8等, fd變化範圍幾乎無限,只受到記憶體數量、int字長以及系統管理員所設定的軟限制和硬限制的約束。。。why?

當open或者create一個新檔案時,核心向程序返回一個檔案描述符。

當讀、寫一個檔案時,使用open或create返回的檔案描述符標識該檔案,將其作為引數傳送給read / write

按照慣例,fd為0 / 1 / 2分別關聯STDIN_FILENO / STDOUT_FILENO / STDERR_FILENO。這些常數也定義在unistd.h.

3 系統呼叫包含在哪些標頭檔案中呢?

包括exec、fork、read、write在內,許多系統呼叫包含在unistd.h標頭檔案中

POSIX,Portable Operating System Interface。是UNIX系統的一個設計標準,很多類UNIX系統也在支援相容這個標準,如Linux。
unistd.h是POSIX標準定義的unix類系統定義符號常數的標頭檔案,包含了許多UNIX系統服務的函數原型[5]。在該標頭檔案,用於存取裝置驅動程式的底層函數(系統呼叫)有這五個:open/close/read/write/ioctl[1]。

4 檔案I/O

[7]中提到大多數檔案I/O用到的5個函數為:open/read/write/lseek/close

4.1 函數read

呼叫read函數從開啟檔案中讀資料。

#include<unistd.h>
ssize_t read(int filedes, void *buf, size_t nbytes);

返回值:

成功,讀出的位元組數;

失敗,-1;

遇到檔案尾,0

有多種情況可使實際讀到的位元組數少於要求讀的位元組數:

  • 讀普通檔案時,在讀到要求位元組數之前已經到達了檔案尾端。

例如,若在到達檔案尾端之前還有30個位元組,而要求讀100個位元組,則read返回30,下一次再呼叫read時,它將回0。

  • 當從終端裝置讀時,通常一次最多讀一行

  • 當從網路讀時,網路中的緩衝機構可能造成返回值小於所要求讀的位元組數。

  • 當從管道或FIFO讀時,如若管道包含的位元組少於所需的數量,那麼read將只返回實際可用的位元組數。

  • 當從某些面向記錄的裝置(例如磁碟)讀時,一次最多返回一個記錄。

  • 當某一訊號造成中斷,而已經讀了部分資料量時。讀操作從檔案的當前偏移量出開始,在成功返回之前,該偏移量將增加實際獨到的位元組數

read的經典原型定義則是:

int read(int fd, char*buf, unsigned nbytes);

相關推薦:《Linux視訊教學

以上就是linux執行ls會引起哪些系統呼叫的詳細內容,更多請關注TW511.COM其它相關文章!