링크에 대한 성질, 그리고 이에 대한 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

링크

파일은 서로 참조하고 참조 받을 수 있습니다. 그러니까 어떤 파일이 어떤 파일을 삿대질해서 가리킬 수도 있고(무척 평화적이죠? 현실세계에서는 참교육 시전받을 수 있습니다.) 같은 내용을 바라볼 수도 있다는 뜻입니다. 이러한 성질을 링크라고 합니다. 링크를 본격적으로 설명하기 전에 I-node라는 개념을 먼저 알아야 할 팔요가 있습니다.

1. Inode

Inode는 아래에 보는바와 같이 i-node라는 구조체를 가리키게 됩니다. 이 i-node에는 파일에 대한 거의 모든 정보(파일 모드, 링크개수, UID, GID, 파일 크기, 파일 관련 시간 등)가 담겨 있는데, 내용은 담겨있지 않습니다. 파일의 메타정보라고 생각하시면 됩니다. i-node는 파일의 내용을 가리키는 일종의 포인터를 갖고 있을 뿐이죠. Single indirect, Double indirect,  Triple indirect 등이 보이죠? 이게 다 파일에 대한 내용을 가리키는 블록들이며, 마치 C언어에서 포인터, 더블 포인터 등과 같이 내용을 가리키게 됩니다.

출처 : https://www.systranbox.com/exploring-the-essential-role-of-inodes-in-linux-and-unix-operating-systems/

inode가 갖고 있는 정보는 아래와 같으니 참고하시기 바래요.

inode가 가지고 있는 정보 설명
inode 번호 inode의 고유 식별 번호입니다.
파일 모드 16비트의 플래그로 파일의 실행 권한입니다. 소유자의 권한, 소유자 그룹의 권한, 기타 사용자의 권한, 파일 형식, 실행 플래그 등을 나타냅니다.
링크 수 이 아이노드에 대한 참조 수를 나타냅니다.
소유자 아이디 파일의 소유자 아이디를 나타냅니다.
그룹 아이디 파일 소유자의 그룹 아아디를 나타냅니다.
파일 크기 파일의 크기(bytes)를 나타냅니다.
파일 주소 실 데이터가 나오는 파일 주소를 나타냅니다.
마지막 접근 마지막으로 파일에 접근한 시간을 나타냅니다.
마지막 수정 마지막으로 파일을 수정한 시간을 나타냅니다.
아이노드 수정 마지막으로 아이노드를 수정한 시간을 의미합니다.

I-node는 각자 자신들의 고유한 번호를 가지고 있는데, 이 고유한 번호를 i-node 번호라고 합니다. ls 명령어에서 -i 옵션은 i-node번호를 같이 보여줍니다. 그래서 i-node번호와 같이 자세한 출력은 원하면 ls -li 을 입력하면 됩니다. 왼쪽에 나오는 숫자가 바로 i-node 번호입니다.

# ls -il /usr/lib
total 2952
6291630 drwxr-xr-x 105 root root   86016 Jun  8 06:17 aarch64-linux-gnu
6441489 drwxr-xr-x   2 root root    4096 Apr 18 00:48 apg
6291631 drwxr-xr-x   2 root root    4096 Feb 17 17:30 apparmor
6291632 drwxr-xr-x   5 root root    4096 Feb 17 17:30 apt
6442507 drwxr-xr-x   3 root root   12288 Apr 18 00:48 aspell

실제 inode의 내용을 확인해보고 싶다면 stat 명령을 사용해서 확인해보시기 바랍니다. stat명령도 내부적으로 inode에서 정보를 얻는 구현이 있기 때문입니다. 아래는 단순 빈 파일의 정보입니다.

# stat /etc/passwd
  File: /etc/passwd
  Size: 3212            Blocks: 8          IO Block: 4096   regular file
Device: fd00h/64768d    Inode: 5245840     Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2023-08-14 09:04:09.872000001 +0000
Modify: 2023-07-27 17:40:33.552013428 +0000
Change: 2023-07-27 17:40:33.560013428 +0000
 Birth: 2023-07-27 17:40:33.552013428 +0000

같은 I-node를 가리키느냐, 아니면 데이터 블록이 원본 파일을 가리키느냐에 따라서 하드링크(Hard link)와 심볼릭(Symbolic link)로 나뉘어집니다. 파일에 링크를 걸어주는 명령이 ln 명령어입니다. ln 명령어를 통해서 하드링크와 심볼릭 링크가 무엇인지 확인해봅시다.

       ln [OPTION]... [-T] TARGET LINK_NAME
       ln [OPTION]... TARGET
       ln [OPTION]... TARGET... DIRECTORY
       ln [OPTION]... -t DIRECTORY TARGET...

 

2. 하드링크

하드링크는 파일이 같은 i-node를 가리켜 파일의 내용을 공유하는 링크 방식을 의미합니다. 하드 링크는 같은 i-node를 가리키기 때문에 i-node 번호가 같습니다. ln 명령어로 하드링크를 걸어봅시다. 하드 링크를 거는 방법은 아래와 같은 명령어 형식을 사용하면 됩니다. 

ln 원본파일 링크파일

우선 아래와 같이 문자열을 담고 있는 origin이라는 파일을 하나 생성하고, i-node 번호(1열)를 확인하면 464235라는 것을 확인할 수 있네요. 파일을 최초로 만들었을 땐 링크수(3열)가 1이라는 점! 

# echo "hard link test" > origin
# ls -il
total 4
464235 -----w--w- 1 root ubuntu 15 Jun 14 01:35 origin

이후 ln 명령어를 통해서 하드링크를 걸어줍니다. I-node와 링크수를 확인해보니, i-node는 464235로 같고 링크수가 2로 하나 증가함을 알 수 있습니다. 그 외의 파일의 권한, 소유자, 시간 모두 정확히 같습니다. 그렇죠?

# ln origin  hardlink
# ls -il
total 8
464235 -----w--w- 2 root ubuntu 15 Jun 14 01:35 hardlink
464235 -----w--w- 2 root ubuntu 15 Jun 14 01:35 origin

그렇다면 우리는 이런 그림을 그려볼 수 있습니다. 링크수는 i-node를 가리키는 화살표의 갯수를 의미하며, 두 파일은 i-node를 같이 공유하고 있음을 알 수 있습니다.

2.1 하드링크의 원본 파일 삭제 시

그렇다면 만약 어느 하나의 파일을 삭제하게 된다면 어떻게 될까요? 여기서는 원본의 파일을 삭제해보도록 하겠습니다. 

# rm origin 
# ls -il
total 4
464235 -----w--w- 1 root ubuntu 15 Jun 14 01:35 hardlink
# cat hardlink 
hard link test

결과에서 알 수 있듯이 i-node의 번호는 변하지 않고, 링크의 갯수가 하나 줄었습니다. 내용을 보면 심지어 내용도 보존이 됩니다. 원본 파일을 삭제했는데도 말이죠. 이런 상황입니다. 

그래서 파일을 삭제한다는 뜻은 링크를 제거한다는 것과 유사합니다. 

 

3. 심볼릭 링크(Symbolic Link)

심볼릭 링크는 ln 명령어에 -s 옵션을 추가하여 링크를 걸 수 있습니다. 위와 같은 과정으로 테스트를 해보면 하드링크와 다른점을 확인할 수 있습니다.

# echo "symbolic link test" > origin
# ls -il
total 4
464235 -----w--w- 1 root ubuntu 19 Jun 14 01:54 origin
# ln -s origin symlink
# ls -il
total 4
464235 -----w--w- 1 root ubuntu 19 Jun 14 01:54 origin
464240 lrwxrwxrwx 1 root ubuntu  6 Jun 14 01:55 symlink -> origin

하드링크와의 차이점을 정리해보면 이렇습니다.

  • origin의 i-node 번호(1열)와 symlink 파일의 i-node번호가 다르다.
  • 링크의 갯수(3열)가 변하지 않는다.
  • 다른 기타 정보(권한, 크기, 시간 정보 등)이 다르다.
  • 파일의 유형을 보면 정규 파일('-')이 아니라 링크('l')이다.

이 상태에서 원본 파일과 심볼릭 링크 파일의 관계를 그리면요. 아래와 같습니다. 

이 그림이 성립이 된다는 것을 확인시켜드리고자 이 상태에서 원본 파일을 과감하게 지우겠습니다. 

# rm origin 
# ls -il
total 0
464240 lrwxrwxrwx 1 root ubuntu 6 Jun 14 01:55 symlink -> origin
# cat symlink 
cat: symlink: No such file or directory

원본 파일인 origin을 삭제하고 심볼릭 링크를 읽으려해보면 그러한 파일을 찾을 수 없다는 에러 메시지를 볼 수 있습니다.  만약 다시 origin을 생성해보면 어떨까요?

# echo "new origin" > origin
# ls -il
total 4
464235 -----w--w- 1 root ubuntu 11 Jun 14 06:59 origin
464240 lrwxrwxrwx 1 root ubuntu  6 Jun 14 01:55 symlink -> origin
# cat symlink 
new origin

이러한 경우에는 다시 symlink로 파일을 읽을 수 있습니다. 다만 그 내용은 원래 이전의 origin의 내용이 아니라 다시 생성한 origin 파일의 내용인 것을 알 수 있습니다. 결국은 심볼릭 링크의 경우에 파일을 가리킨다는 것을 확인할 수 있습니다. 

4. ln 명령어 구현 

간단한 ln 명령어를 구현해보도록 하겠습니다. 그전에 우리가 알아야할 관현 함수들은 symlink와 linkat 함수입니다. 

  • symlink
#include <unistd.h>
int symlink(const char *target, const char *linkpath);

심볼릭 링크를 거는 시스템 콜입니다. target은 현재 프로세스 위치의 파일 이름입니다. 상대 경로일 수 있습니다. linkpath는 링크 파일의 이름입니다. 이때 이 파일이 존재하면 에러 -1이 발생합니다. 

  • linkat
#include <unistd.h>
int linkat(int olddirfd, const char *oldpath,
			int newdirfd, const char *newpath, int flags);

하드 링크, 심볼릭 링크 둘 다 걸 수 있는 시스템 콜입니다. linkat 함수는 조금 까다로울 수 있는데, newpath가 oldpath를 가리키도록 링크를 걸게 됩니다.

olddirfd와 oldpath, newdirfd와 newpath가 한 쌍으로 파일을 나타냅니다. oldpath가 절대 경로일 경우 olddirfd는 무시가 되고, oldpath가 상대 경로일 경우 olddirfd 기준의 상대경로가 됩니다. newdirfd와 newpath와의 관계도 마찬가지입니다. 함수를 호출한 프로세스를 기준으로 상대위치로 만들고 싶다면 fd인자에 AT_FDCWD 상수를 사용해야합니다.

flag는 AT_SYMLINK_FOLLOW를 사용할 수 있습니다. 만약 이 flag를 쓰게 되면 원본 파일이 심볼릭 링크라면 링크를 따라가서 하드 링크를 겁니다. 원본 파일이라니까 헷갈리실거 같은데 ln 명령어에서 첫번째 인수를 말해요. 이 flag를 쓰지 않으면 원본 파일인 심볼릭 링크를 따라가 심볼릭 링크를 겁니다. 

ln 원본파일 링크파일

즉, 정리하면

  • AT_SYMLINK_FOLLOW 를 사용할 경우 : symlink를 계속 따라가서 harlink
  • AT_SYMLINK_FOLLOW를 사용하지 않은 경우 : symlink를 계속 따라가서 symlink

 

4.1 소스코드 

이제 간단히 구현한 myln.c 코드를 보겠습니다.

//myln.c

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

// -s 옵션 
bool symbolic = false;
// -L 옵션
bool logical = false;

int main(int argc, char *argv[]){

        int c;
        char **files_name;
        char *src, *dst;

        //옵션 파싱
        while((c = getopt(argc, argv, "sL")) != -1 ){
                switch(c){
                        case 's':
                                symbolic = true;
                                break;
                        case 'L':
                                logical = true;
                                break;
                }
        }

        files_name = argv + optind;

        //원본 파일
        src = files_name[0];
        //링크 파일
        dst = files_name[1];

        //심볼릭 링크시에 symlink 사용
        if(symbolic){
                return symlink(src, dst);
        }

        //심볼릭 링크가 아니면 -L 옵션 여부에 따라 AT_SYMLINK_FOLLOW 플래그 설정
        return linkat(AT_FDCWD, src, AT_FDCWD, dst, logical ? AT_SYMLINK_FOLLOW : 0);

}

linkat에 AT_FDCWD를 사용한 것을 보시기 바랍니다. AT_FDCWD는 현재 싱행되는 프로그램의 위치를 기준삼아 src를 찾고, dst링크 파일을 만듭니다. 위 코드는 실제 ln의 코드를 풀어 쓴 코드입니다. 아래는 실제 ln의 소스 코드 일부분입니다.

static bool
do_link (const char *source, const char *dest)
{
 ...
   ok = ((symbolic_link ? symlink (source, dest)
         : linkat (AT_FDCWD, source, AT_FDCWD, dest,
                   logical ? AT_SYMLINK_FOLLOW : 0))
        == 0);
 
 ...
 }

 

이제 코드 결과를 확인해봅시다. 

# gcc myln.c 
# echo "this is origin" > origin
# ./a.out -s origin symlink1
# ./a.out origin hardlink1
# ls -il
total 24
464557 -rwxr-xr-x 1 root root 9088 Aug 15 05:59 a.out
464558 -rw-r--r-- 2 root root   15 Aug 15 05:59 hardlink1
464648 -rw-r--r-- 1 root root 1121 Aug 15 05:52 myln.c
464558 -rw-r--r-- 2 root root   15 Aug 15 05:59 origin
464646 lrwxrwxrwx 1 root root    6 Aug 15 05:59 symlink1 -> origin

우선 원본 파일인 origin을 생성하고 심볼릭 링크와 하드링크를 하나씩 걸었습니다. symlink1은 심볼릭 링크로 잘 링크되어있고, hardlink1도 inode를 보니 origin과 같아서 하드링크가 잘 걸려있네요!

-L옵션도 확인해볼까요? 

# ./a.out -L symlink1 hardlink2
# ./a.out symlink1 symlink2
# ls -il
total 28
464557 -rwxr-xr-x 1 root root 9088 Aug 15 05:59 a.out
464558 -rw-r--r-- 3 root root   15 Aug 15 05:59 hardlink1
464558 -rw-r--r-- 3 root root   15 Aug 15 05:59 hardlink2
464648 -rw-r--r-- 1 root root 1121 Aug 15 05:52 myln.c
464558 -rw-r--r-- 3 root root   15 Aug 15 05:59 origin
464646 lrwxrwxrwx 2 root root    6 Aug 15 05:59 symlink1 -> origin
464646 lrwxrwxrwx 2 root root    6 Aug 15 05:59 symlink2 -> origin

-L옵션은 linkat 콜 인자 중 flag에 AT_SYMLINK_FOLLOW를 전달한 것과 같습니다. 그러니까 -L 옵션을 줄 경우 원본 파일은 symlink1을 따라가서 최종 원본 파일을 찾고 거기에 하드링크를 걸게 됩니다. -L 옵션을 뺀 경우, 그러니까 AT_SYMLINK_FOLLOW옵션을 주지 않았을 경우 링크를 계속 따라가서 최종 원본 파일에 심볼릭 링크를 걸게 되지요. 

여기까지 리눅스의 i-node가 대한 간단한 소개와 link의 방식인 hard link, symbolic link의 개념과 차이점을 확인해보았으며 직접 ln 명령어를 맛보기로 구현해보았습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,