返回文章列表

Linux 檔案操作與系統呼叫深度解析

本文探討 Linux 系統中檔案操作與系統呼叫的核心技術,包含 `lseek`、`link`、`unlink`、`dup`、`stat`、`fstat` 等,並講解檔案許可權、umask 與特殊許可權的設定與應用。文章提供程式碼範例與流程圖示,幫助讀者理解如何在程式中操作檔案與目錄,並有效管理檔案許可權。

系統程式設計 作業系統

在 Linux 系統開發中,理解檔案操作和系統呼叫至關重要。這些底層機制是程式與作業系統互動的橋樑,讓程式得以讀寫檔案、管理目錄、控制檔案許可權等。本文將深入解析 lseeklinkunlinkdupstatfstat 等關鍵系統呼叫,並探討檔案許可權、umask 和特殊許可權的設定與應用,幫助讀者更有效率地操作檔案和目錄。這些系統呼叫提供了對檔案指標位置調整、硬連結建立與刪除、檔案描述符複製以及檔案狀態查詢等功能。此外,瞭解檔案許可權的設定方式,包括 umask 的作用和特殊許可權(Set-UID、Set-GID 和 Sticky Bit)的應用,對於保障系統安全和資料完整性至關重要。文章中提供的程式碼範例和流程圖示,可以幫助讀者更直觀地理解這些概念,並在實際程式設計中靈活運用。

檔案操作與系統呼叫探討

在 Unix-like 系統中,檔案操作與系統呼叫是實作程式與作業系統之間互動的核心技術。以下將探討一些常見的檔案作業系統呼叫,包括 lseeklinkunlinkdup、以及 statfstat,並提供具體的程式碼範例和詳細解說。

檔案指標位置調整

lseek 是一個用於調整檔案指標位置的系統呼叫。這個功能對於需要在檔案中進行隨機存取的應用程式非常重要。以下是 lseek 的使用範例:

#include <stdio.h>
#include <fcntl.h>

int main()
{
    int fd;
    long position;
    fd = open("datafile.dat", O_RDONLY);
    if (fd != -1)
    {
        position = lseek(fd, 0L, 2); /* seek 0 bytes from end-of-file */
        if (position != -1)
            printf("The length of datafile.dat is %ld bytes.\n", position);
        else
            perror("lseek error");
    }
    else
        printf("can’t open datafile\n");
    close(fd);
}

內容解密:

這段程式碼展示瞭如何使用 lseek 來查詢檔案的長度。首先,使用 open 函式以只讀模式開啟名為 datafile.dat 的檔案。如果成功開啟檔案,則使用 lseek 將檔案指標移動到檔案結尾。這裡的 2 表示從檔案結尾開始計算偏移量。如果成功,則輸出檔案的長度;否則,輸出錯誤資訊。

建立硬連線

link 系統呼叫可以為現有的檔案建立一個新的硬連線。硬連線是指多個檔名指向同一個 i-node,因此對其中一個檔案進行修改會反映到所有連線上。

#include <stdio.h>

int main()
{
    if ((link("file.old", "file.new")) == -1)
    {
        perror("ERROR");
        exit(1);
    }
    exit(0);
}

內容解密:

這段程式碼展示瞭如何使用 link 系統呼叫為現有的 file.old 建立一個新的硬連線 file.new。如果操作成功,傳回 0;否則,傳回 -1 並輸出錯誤資訊。

刪除硬連線

unlink 系統呼叫可以刪除一個硬連線及其對應的目錄專案,並減少該檔案的連線計數。如果連線計數達到零,則會刪除檔案內容。

#include <stdio.h>

int main()
{
    if ((unlink("file.old")) == -1)
    {
        perror("ERROR");
        exit(1);
    }
    exit(0);
}

內容解密:

這段程式碼展示瞭如何使用 unlink 系統呼叫刪除名為 file.old 的硬連線。如果成功刪除,傳回 0;否則,傳回 -1 並輸出錯誤資訊。

副本檔案描述符

dup 系統呼叫可以複製一個現有的檔案描述符,使其指向同一個檔案表專案。這對於需要在不同描述符上操作同一個檔案時非常有用。

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int main()
{
    int fd;
    fd = open("file.dat", O_WRONLY | O_CREAT, S_IREAD | S_IWRITE);
    if (fd == -1)
    {
        perror("FILE already opened");
        exit(1);
    }
    close(1); /* close standard output */
    dup(fd); /* fd will be duplicated */
    close(fd); /* close the extra slot */
    printf("Hello\n");
    exit(0);
}

內容解密:

這段程式碼展示瞭如何使用 dup 系統呼叫複製一個檔案描述符。首先,以寫入模式建立或開啟名為 file.dat 的檔案。然後關閉標準輸出(描述符 1),並將剛剛建立的檔案描述符複製到標準輸出。最後,關閉原始檔案描述符並輸出 “Hello” 命令列。

查詢檔案狀態

statfstat 系統呼叫可以取得關於檔案狀態的資訊,如擁有者、i-node 編號、檔案型別、大小、許可權等。

#include <stdio.h>
#include <sys/stat.h>

int main()
{
    struct stat fileStat;
    if (stat("example.txt", &fileStat) == -1)
    {
        perror("ERROR");
        return -1;
    }

    printf("File size: \t%ld bytes\n", fileStat.st_size);
    printf("Number of links: \t%d\n", fileStat.st_nlink);
    printf("File inode: \t%ld\n", fileStat.st_ino);

    return 0;
}

內容解密:

這段程式碼展示瞭如何使用 stat 系統呼叫取得名為 example.txt 的檔案狀態資訊。首先,定義了一個 struct stat 型別的變數來儲存檔案狀態資訊。然後使用 stat 函式取得檔案資訊並進行錯誤處理。最後,輸出檔案大小、連線數和 i-node 編號。

基本許可權管理

在 Unix-like 系統中,每個檔案都有擁有者、群組和其他人的許可權設定。這些許可權決定了誰可以讀取、寫入或執行該檔案。以下是如何檢視和管理這些許可權的一些基本方法。

ls -l /usr/bin/new1

內容解密:

這條命令使用 ls -l 檢視 /usr/bin/new1 檔案的詳細資訊,包括許可權設定。第一列顯示的是檔案型別和許可權(例如 -rwxr-xr-x),表示擁有者具有讀寫執行許可權,群組具有讀執行許可權,其他人也具有讀執行許可權。

流程圖示

此圖示展示了不同系統呼叫之間的關係及其操作流程。

內容解密:

此圖示展示了各種檔案作業系統呼叫之間的關係及其操作流程。從開啟檔案開始,可以進行多種操作如查詢檔案長度、建立硬連線、刪除硬連線、複製檔案描述符以及取得檔案狀態資訊等。

Linux 檔案許可權及特殊許可權解密

在 Linux 系統中,檔案許可權的設定和管理是保護系統安全及資料完整性的重要部分。本文將探討 Linux 檔案許可權的基本概念、許可權表示法以及特殊許可權(如 Set-UID、Set-GID 和 Sticky Bit)的作用與應用。

檔案許可權的基本概念

Linux 系統中的每個檔案和目錄都有三種基本許可權:讀取(read)、寫入(write)和執行(execute)。這些許可權分別對應於檔案的擁有者(owner)、群組成員(group)和其他使用者(others)。

範例解析

以下是一個檔案許可權的範例:

-rwxr-xr-x 1 user1 user1 8632 Sep 19 2021 /usr/bin/new1
  • 第一個字元 -:表示這是一個普通檔案。
  • 接下來的九個字元 rwxr-xr-x:分別代表擁有者、群組成員和其他使用者的許可權。
    • rwx:擁有者可以讀取、寫入和執行。
    • r-x:群組成員和其他使用者只能讀取和執行。

許可權表示法

Linux 使用一個九位元向量來表示檔案許可權,每三位元對應於一種使用者型別(擁有者、群組、其他)。

  • r:讀取許可權,對應二進位制的 100,即十進位制的 4
  • w:寫入許可權,對應二進位制的 010,即十進位制的 2
  • x:執行許可權,對應二進位制的 001,即十進位制的 1

例如,-rwxr-xr-x 對應於十進位制的 755

  • 擁有者:rwx (4+2+1=7)
  • 群組成員:r-x (4+0+1=5)
  • 其他使用者:r-x (4+0+1=5)

特殊許可權

除了基本的讀取、寫入和執行許可權外,Linux 還提供了一些特殊許可權來滿足特定需求。這些特殊許可權包括 Set-UID、Set-GID 和 Sticky Bit。

Set-UID

Set-UID(Set User ID upon execution)是一種特殊許可權,當設定在可執行檔案上時,該檔案會以檔案擁有者的身份執行,而不是以執行該檔案的使用者身份執行。

例如,密碼變更程式通常會設定 Set-UID,因為它需要修改 /etc/passwd 檔案,而這個檔案只有 root 才有寫入許可權。

-rwsr-xr-x 1 root root 8632 Sep 9 2008 /usr/bin/passwd

在這個例子中,s 號代替了 x,表示該檔案具有 Set-UID 屬性。

Set-GID

Set-GID(Set Group ID upon execution)與 Set-UID 類別似,但它適用於群組。當設定在可執行檔案或目錄上時,該檔案或目錄會以擁有者所屬群組的身份執行或建立。

例如,某些分享目錄可能會設定 Set-GID 屬性,使得在該目錄中建立的所有新檔案都屬於同一個群組。

drwxr-s--- 2 user1 group1 4096 Oct 10 2023 /shareddir

在這個例子中,s 號代替了群組成員的 x,表示該目錄具有 Set-GID 屬性。

Sticky Bit

Sticky Bit 主要用於分享目錄,如 /tmp/var/tmp。當設定在目錄上時,即使其他使用者具有寫入許可權,他們也無法刪除或重新命名由其他使用者建立的檔案。

drwxrwxrwt 5 root root 4096 Oct 10 2023 /tmp

在這個例子中,最後一位元為 t ,表示該目錄具有 Sticky Bit 屬性。

檔案建立時的 User ID 和 Group ID

當新檔案建立時,其 User ID 和 Group ID 的設定如下:

  • User ID:新檔案的 User ID 與建立該檔案的程式的 Effective User ID 一致。
  • Group ID:新檔案的 Group ID 則可能是:
    • 建立該檔案程式的 Effective Group ID。
    • 新檔案所在目錄的 Group ID(如果該目錄具有 Set-GID 屬性)。

效果 User ID 和 Group ID

大多數情況下,Effective User ID (EUID) 與 User ID (UID) 一致,Effective Group ID (EGID) 與 Group ID (GID) 一致。然而,當可執行檔具備 Set-UID 或 Set-GID 屬性時,EUID 或 EGID 則會變成該可執行檔擁有者或群組所屬之身份。

例如:

-rws--x--x 1 root root /usr/bin/passwd

當普通使用者執行 /usr/bin/passwd時 ,EUID 被設為root 的UID ,使得程式可以更新 /etc/passwd

檔案存取許可權與系統呼叫

在 Unix 及類別 Unix 系統中,檔案的存取許可權是透過檔案模式(mode)來控制的。這些許可權可以透過各種系統呼叫來修改或查詢,包括 chmodchownaccess 等。這些系統呼叫提供了對檔案及目錄的精細控制,確保資料的安全性和完整性。

檔案許可權與 umask

在 Unix 系統中,檔案的許可權是透過一個稱為 umask 的掩碼來設定的。umask 是一個八進位制數字,用來決定新建檔案或目錄的預設許可權。當建立新檔案或目錄時,系統會根據 umask 值來調整預設的許可權。

檔案存取許可權掩碼

st_mode 成員是 stat 結構的一部分,包含了檔案的許可權資訊:

  • S_IRWXU: 擁有者可讀、寫、執行
  • S_IRUSR: 擁有者可讀
  • S_IWUSR: 擁有者可寫
  • S_IXUSR: 擁有者可執行
  • S_IRWXG: 群組可讀、寫、執行
  • S_IRGRP: 群組可讀
  • S_IWGRP: 群組可寫
  • S_IXGRP: 群組可執行
  • S_IRWXO: 其他人可讀、寫、執行
  • S_IROTH: 其他人可讀
  • S_IWOTH: 其他人可寫
  • S_IXOTH: 其他人可執行

umask() 呼叫

每個程式都有一個自己的 umask 值,這個值會從父程式繼承。umask() 函式用來設定程式的檔案建立掩碼並傳回之前的掩碼值。低階九位的掩碼會在每次建立檔案時使用。

Unix 核心在建立普通檔案時會設定預設許可權為 666,而對於目錄則是 777。當使用 open()mkdir() 或其他系統呼叫建立檔案或目錄時,核心會將當前的 umask 值從預設許可權中減去,以設定最終的許可權。

範例

假設當前的 umask 值為 044,若要為一個普通檔案設定預設許可權為 666,核心會從 666 中減去 044,結果是 622

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {
    int fd;
    int old_mask;

    old_mask = umask(0);
    fd = open("f1", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    close(fd);
    printf("created f1: 0666\n");

    fd = open("f2", O_WRONLY | O_CREAT | O_TRUNC, 0200);
    close(fd);
    printf("created f2: 0200\n");

    umask(022);
    fd = open("f4", O_WRONLY | O_CREAT | O_TRUNC, 0777);
    close(fd);
    printf("created f4: %o\n", 0777 & ~022);

    fd = open("f5", O_WRONLY | O_CREAT | O_TRUNC, 0200);
    close(fd);
    printf("created f5: %o\n", 0200 & ~022);

    return 0;
}

在這個範例中,首先將 umask 設定為 0,使得所有建立的檔案都會使用在開啟時指定的許可權。然後將 umask 改為 022,這樣在建立檔案時會從指定的許可權中減去這個值。

access() 函式

access() 函式用來檢查指定路徑名稱的檔案是否具有特定的存取許可權。它接受兩個引數:路徑名稱和模式。

int access(const char *pathname, int mode);

如果成功完成,則傳回值為 0;否則傳回 -1,並設定全域變數 errno 指示錯誤。

改變所有者與模式

改變檔案所有者和模式是對 inode 的操作。進行這些操作的程式必須是超級使用者或該檔案的擁有者。

int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
int chown(const char *pathname, uid_t owner, gid_t group);

chmod() 函式

int chmod(const char *pathname, mode_t mode);

改變指定路徑名稱的檔案模式。

chown() 函式

int chown(const char *pathname, uid_t owner, gid_t group);

改變指定路徑名稱的檔案所有者和群組。

掛載與解除安裝檔案系統

掛載檔案系統使得檔案系統中的資料能夠像檔案一樣進行讀寫操作,而解除安裝則反之。掛載操作透過 mount() 函式完成,解除安裝透過 umount() 函式完成。

mount() 函式

int mount(const char *source, const char *target,
          const char *filesystemtype,
          unsigned long mountflags,
          const void *data);

將 source 指定的檔案系統掛載到 target 指定的目錄下。

umount() 函式

int umount(const char *target);

解除安裝指定目錄下掛載的檔案系統。

錄錄相關系統呼叫

Unix 中一切皆為檔案(everything is a file),因此也有一系列與目錄相關的系統呼叫。這些呼叫用於建立、刪除和操作目錄內容。

mkdir() 函式

int mkdir(const char *path, mode_t mode);

建立一個新目錄並設定其模式。

#include <sys/stat.h>

int main() {
    if (mkdir("new_directory", S_IRWXU | S_IRGRP | S_IXGRP) != 0) {
        perror("mkdir");
        return -1;
    }
    return 0;
}

此圖示展示了 mkdir 函式在建立新目錄時如何應用 umask 和預設模式:

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Linux 檔案操作與系統呼叫深度解析

package "圖論網路分析" {
    package "節點層" {
        component [節點 A] as nodeA
        component [節點 B] as nodeB
        component [節點 C] as nodeC
        component [節點 D] as nodeD
    }

    package "中心性指標" {
        component [度中心性
Degree Centrality] as degree
        component [特徵向量中心性
Eigenvector Centrality] as eigen
        component [介數中心性
Betweenness Centrality] as between
        component [接近中心性
Closeness Centrality] as close
    }
}

nodeA -- nodeB
nodeA -- nodeC
nodeB -- nodeD
nodeC -- nodeD

nodeA --> degree : 計算連接數
nodeA --> eigen : 計算影響力
nodeB --> between : 計算橋接度
nodeC --> close : 計算距離

note right of degree
  直接連接數量
  衡量局部影響力
end note

note right of eigen
  考慮鄰居重要性
  衡量全局影響力
end note

@enduml

說明:

  1. Create Directory: 使用者嘗試建立一個新目錄。
  2. Apply Default Mode: 應用預設模式(例如:正規檔案為 666, 目錄為 777)。
  3. Check umask: 檢視當前程式的 umask 值。
  4. Adjust Mode: 若有 umask 值,則從預設模式中減去 umask 值。
  5. Create Directory with Adjusted Mode: 建立帶有調整後模式的新目錄。

玄貓提醒大家,「一切皆為檔案」不僅僅是一句口號,它實實在在地影響著你如何進行檔案存取與管理。瞭解這些細節將能幫助你更高效地處理各種檔案與目錄操作。