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

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

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

reakwon.tistory.com

 

리눅스 커널(Kernel)

리눅스 커널은 하드웨어와 가장 가까이 있는 일종의 프로그램입니다. 가장 핵심적인 프로그램으로 사용자는 커널을 통해서 하드웨어를 제어합니다. 함부로 하드웨어를 직접 만졌다가는 돌아올 수 없는 강을 건너게 될 수도 있습니다. H/W에는 CPU, 메모리(RAM), 하드디스크(HDD), 기타 입출력 장치 등 많은 것들이 있는데 실제 직접 조작할 수도 없고 해서도 안됩니다. 그래서 커널이 존재하게 되는것이지요. 이 처럼 커널은 시스템의 자원을 관리하는 운영체제 그 자체로 보시면 됩니다. 

윈도우즈도 역시 커널을 가지고 있는데, 사용자가 커널의 코드를 직접 들여다 볼 수 없고 거기에 맞는 모듈(Module)을 만들어거 끼워넣을 수 없죠. 윈도우즈는 커널 내부를 공개하고 있지 않기 때문인데요. 그래서 윈도우즈 커널을 수정하려면 오직 MS사 밖에 할 수 없습니다. 리눅스는 윈도우즈와는 다르게 커널 내부 소스를 전부 공개합니다. 그래서 커널을 잘 아는 사람은 거기에 맞게 모듈이라는 커널의 부분을 끼워넣어서 변경할 수 있습니다. 

이전에 리눅스는 커널에 조금이라도 변경이 생기면 수정 후 전체 커널 컴파일을 해야했는데, 이 모듈이라는 개념이 도입된 후 필요한 커널의 부분만 수정하고 다시 끼울 수 있으면서 커널 컴파일하는 모든 과정을 수행하지 않아 시간이 획기적으로 줄어 들 수 있습니다. 현재 적재된 커널 모듈을 보려면 lsmod를 이용해서 볼 수 있고 모듈을 설치할때는 insmod를 이용하면 되는데, 물론 아무나는 안됩니다. 커널 프로그래밍을 이용하기 때문에 아무나 건드리면 시스템이 멈출 수 있습니다.

쉘(shell)

쉘은 사용자의 응용프로그램과 커널 사이에 위치해있으며 응용프로그램의 명령어와 커널이 대화를 하도록 만들어줍니다. 그래서 명령어 해석기라는 부릅니다.

자, 우리가 명령어를 입력하게 되면 컴퓨터에서는 쉘(shell)이 이 명령어를 받아 해석하여 커널(kernel)에게 보내면 커널은 우리가 내려주었던 동작을 하게 됩니다. 그래서 그에 대한 결과를 사용자에게 전달하려고 다시 쉘에 응답을 보내고 쉘을 거쳐 사용자에게 도달합니다. 여기서 쉘도 프로그램입니다.

쉘 종류는 여러가지고 존재합니다. 

Bourne Shell (/bin/sh)

본쉘이라고 합니다. 유닉스 최초의 쉘이기도 하지요. 최조의 쉘이라서 미흡한 기능이 많은데요. 여기서 일반 유저의 쉘 프롬프트는 $, root사용자의 프롬프트는 #으로 나타냅니다.

Bash, Bourne Again Shell (/bin/bash)

현재 리눅스에서 표준으로 채택된 쉘입니다. 여러분에게 가장 친숙한 쉘이라고 생각하시면 됩니다. 여러분이 명령을 칠때 명령어 다 쓰기 싫어서 tab쓰시죠? 그게 원래는 리눅스 자체의 기능이 아니고 bash의 기능입니다. 그 밖에도 아래와 같은 특징이 있습니다. Mac 운영체제에서도 bash가 쓰입니다.


 - History(방향키 ↑↓)
 - Alias
 - 자동 완성 (tab)
 - 연산
 - Job Control
 - 그 외

 

C shell (/bin/csh)

C언어를 기반으로 만들어진 쉘입니다. 쉘 스크립트가 C언어로 작성하는 것처럼 작성할 수 있습니다.

 

 

쉘을 이해해보자

이 외에도 쉘의 종류는 여러가지가 있는데요. 실제 시스템에서 지원하는 쉘을 보기 위해서 /etc/shells를 열어보시면 됩니다. 아래는 저의 시스템에 있는 shell들입니다.


/home/ubuntu# cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/bin/dash
/usr/bin/dash

그리고 사용자가 어떤 쉘을 쓰고 있는지 보고 싶으시다면 SHELL 환경변수를 확인하면 됩니다. echo $SHELL 으로 확인하세요. 저는 bash쉘을 사용하고 있군요.


/home/ubuntu# echo $SHELL
/bin/bash

 

여기서 bash쉘에서 지원하는 기능이 sh(본쉘)에서도 먹힐까요? 사용하는 bash쉘을 본쉘로 바꿔보도록 하겠습니다. 단순히 sh명령으로 쉘을 실행해봅시다. 그리고 명령어를 아무거나 처보세요. 그리고 우리가 이전의 명령어를 치려고 위 화살표(↑)를 누르면 이상한 문자(^[[A)가 쓰여지게 되어 이전의 명령어를 수행할 수는 없습니다. 결국 sh는 히스토리 기능을 지원하지 못하며, 정확히 얘기해서 저의 명령을 해석하지 못한다는 것입니다. bash에서는 ~/.bash_history에 그 동안의 명령어들 내역을 보관하고 있는데, 본쉘은 ~/.bash_history가 무엇인지도 모르죠.


/home/ubuntu# sh
# ls
공개  다운로드  문서  바탕화면  비디오  사진  음악  템플릿
# pwd
/home/ubuntu
# ^[[A

 

보통 bash에서 ll은 alias로 ls -alF의 명령어 별명으로 지정합니다. 지금 bash에서 ll을 치면 파일들의 목록이 자세히 나오게 되는데요. ll을 sh상에서 실행하면 실행이 되지 않습니다. bash에서 ll이 수행되지 않는다면 alias지정이 되지 않은 것인데요. vi로 ~/.bashrc를 열고 하단에 alias ll='ls -alF'를 써주시고 source ~/.bashrc를 수행하면 이제 ll이 수행될 것입니다. bashrc는 bash쉘의 설정파일입니다. 

 
     80 # some more ls aliases
     81 alias ll='ls -alF'
     82 alias la='ls -A'
     83 alias l='ls -CF'
     84

그렇다면 사용자가 접속할때 bash말고 다른 쉘을 사용하고 싶다면 어떻게 할까요? 시스템에서 root로 접속시 기본적으로 사용할 쉘이 어디에 기록되어있냐면 /etc/passwd에 기록되어있습니다. cat /etc/passwd | grep root의 결과가 아래와 같습니다. 리눅스를 조금 공부하셨다면 /etc/passwd에서 각 필드가 무엇을 뜻하는지는 아실겁니다. 

root:x:0:0:root:/root:/bin/bash

맨 오른쪽에 보이는 것이 접속 시 적용할 쉘인데요. 바꾸고 싶다면 chsh -s [shell path]를 입력하면 됩니다. 예를 들면 chsh -s /bin/sh 이렇게 쓰고 재접속을 하면 적용된 쉘로 바뀝니다. 

쉘에서 쓰는 명령어들을 하나의 대본으로 만들어 놓을 수 있는데요. 예를 들어 어느 경로에 들어가서 디렉토리를 만들고 거기에 파일을 만들고 파일의 내용을 어떤 내용으로 기록하고, 그런 작업들 말이에요. 그러려면 사용자가 수동으로 cd를 입력하고 mkdir하고 touch해서 파일을 만들고 그 내용을 echo '블라블라' > file 해서 입력하고 해야겠지요. 하지만 이런 대본(script)를 만들어서 쉘에게 알려주면 일련의 명령을 쉘이 알아서 수행해줍니다. 이것을 우리는 쉘 스크립트라고 하며 확장자가 .sh로 끝나는 파일입니다.

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

파이프와 더불어 더 많은 정보를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

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

reakwon.tistory.com

 

파이프(Pipe)

파이프(Pipe)란 프로세스간 통신을 할때 사용하는 커뮤니케이션의 한 방법입니다. 가장 오래된 UNIX 시스템의 IPC로 모든 유닉스 시스템이 제공합니다. 하지만 두가지 정도의 한계점이 있습니다.

 

첫번째 한계점으로 파이프는 기본적으로 반이중 방식입니다. 물론 전이중 방식을 지원하는 시스템이 있긴 하나, 최대의 이식성을 위해서는 파이프는 반이중 방식이라는 생각을 해야합니다. 이것은 FIFO라는 명명된 파이프로 극복할 수 있습니다.

두번째 한계점으로는 부모, 자식 관계에서의 프로세스들만 사용할 수 있습니다. 부모프로세스가 파이프를 생성하고, 이후 자식 프로세스와 부모프로세스가 파이프를 이용하여 통신합니다.

 

이러한 한계점이 잇긴 하지만 여전히 쓸모있는 IPC기법입니다.

 

파이프는 unistd.h 헤더파일이 존재합니다.

 

#include <unistd.h>
int pipe(int fd[2]);

pipe함수가 성공적으로 호출되었다면 0, 실패했을 경우 -1을 반환합니다.

인자 fd는 2개의 원소가 있는 배열이라는 점을 주목합시다. 2개의 원소를 쓰는 이유가 있습니다. 아래의 그림을 보면서 이해합시다.

 

 

파이프는 커널영역에 생성되어 파이프를 생성한 프로세스는 파일 디스크립터만 갖고 있게 됩니다. 여기서 파일디스크립터 fd[1]은 쓰기용 파이프, fd[0]은 읽기용 파이프입니다. 그러니 우리가 만약 데이터를 fd[1]에 쓰게 되면 fd[0]으로 그 데이터를 읽을 수 있는 것입니다.

 

그렇다면 자식 프로세스를 하나 더 두어서 자식과 부모가 통신할 수 있게 하려면 어떻게 해야할까요? 우선 자식 프로세스를 fork하면 파일 디스크립터는 부모의 파일디스크립터를 자식이 그대로 사용할 수 있는 것을 활용합니다. (파일디스크립터가 그대로 자식프로세스에 복제됩니다.)

부모프로세스는 파이프에 데이터를 쓰는 프로세스, 자식 프로세스는 그 파이프에서 데이터를 읽는 프로세스로 설계합시다.

 

 

우선 부모 프로세스에서 파이프를 생성하면 파이프에 데이터를 쓸것이기 때문에 읽기 파이프는 닫습니다. fd[0]이죠? 그런 후 fd[1]에 데이터를 씁니다.

자식 프로세스는 쓰기 파이프는 쓰지 않으므로 fd[1]을 닫고, 읽기 파이프로 데이터를 읽습니다.

 

다음은 그런 기능을 하는 코드입니다.

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MAX_BUF 1024
#define READ 0
#define WRITE 1
int main(){
        int fd[2];
        pid_t pid;
        char buf[MAX_BUF];

        if(pipe(fd) < 0){
                printf("pipe error\n");
                exit(1);
        }
        if((pid=fork())<0){
                printf("fork error\n");
                exit(1);
        }

        printf("\n");
        if(pid>0){ //parent process
                close(fd[READ]);
                strcpy(buf,"message from parent\n");
                write(fd[WRITE],buf,strlen(buf));
        }else{  //child process
                close(fd[WRITE]);
                read(fd[READ],buf,MAX_BUF);
                printf("child got message : %s\n",buf);
        }
        exit(0);
}

 

결과는 아래와 같습니다.

 

child got message : message from parent

 

자식 프로세스에서 부모 프로세스가 pipe에 쓴 데이터를 읽었습니다. 

또는 자식프로세스가 데이터를 쓰고, 부모프로세스가 데이터를 읽는 설계도 가능하겠죠.

 

 

그렇다면 부모 프로세스와 자식 프로세스가 읽기, 쓰기가 가능하게 구현하려면 어떻게 해야할까요? 파이프를 한개만 사용한다고 해봅시다.

 

그리고 이런 상황을 가정해보지요.

1. 먼저 부모프로세스가 파이프에 fd[1]로 데이터를 보냅니다. 

2. 그 이후 자식 프로세스가 부모 프로세스가 쓴 데이터를 fd[0]으로 읽습니다.

3. 자식 프로세스는 바로 fd[1]로 파이프에 응답값을 보냅니다.

4. 부모 프로세스는 fd[0]으로 자식 프로세스가 보낸 응답값을 읽습니다.

 

결론을 말씀드리면 항상 위의 상황은 발생하지 않습니다. 그 이유는 누가 먼저 파이프를 읽느냐에 따라서 결과가 달라지는데, 만일 부모프로세스가 파이프에 쓰고, 자식 프로세스가 그 데이터를 읽기도 전에 부모프로세스가 먼저 데이터를 읽는다면 파이프에 데이터는 없겠죠. 허나 자식 프로세스는 없는 데이터를 계속 읽기만 기다리고 있기 때문에 프로그램이 망하게 되는 겁니다.

 

이때는 파이프를 2개 사용해야합니다.

fdA와 fdB 2개 사용합니다. 부모프로세스는 자식에게 쓰기용으로 fdA[1], 자식프로세스로부터 읽기용으로 fdB[0]만 있으면 됩니다. 필요없는 fdA[0], fdB[1]은 닫아줍니다.

그리고 자식프로세스는 부모프로세스로부터 읽기용으로 fdA[0], 쓰기용으로 fdB[1]만 있으면 되지요. 역시 필요없는 fdA[1], fdB[0]은 닫아줍니다.

이제 이런 개념으로 코드를 구현합시다.

 

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MAX_BUF 1024
#define READ 0
#define WRITE 1
int main(){
        int fdA[2],fdB[2];
        pid_t pid;
        char buf[MAX_BUF];
        int count=0;

        if(pipe(fdA) < 0){
                printf("pipe error\n");
                exit(1);
        }

        if(pipe(fdB) < 0){
                printf("pipe error\n");
                exit(1);
        }

        if((pid=fork())<0){
                printf("fork error\n");
                exit(1);
        }

        printf("\n");
        if(pid>0){ //parent process
                close(fdA[READ]);
                close(fdB[WRITE]);
                while(1){
                        sprintf(buf,"parent %d",count++);
                        write(fdA[WRITE],buf,MAX_BUF);
                        memset(buf,0,sizeof(buf));
                        read(fdB[READ],buf,MAX_BUF);
                        printf("parent got message : %s\n",buf);
                        sleep(1);
                }
        }else{  //child process
                close(fdA[WRITE]);
                close(fdB[READ]);
                count=100000;
                while(1){
                        sprintf(buf,"child %d",count++);
                        write(fdB[WRITE],buf,MAX_BUF);
                        memset(buf,0,sizeof(buf));
                        read(fdA[READ],buf,MAX_BUF);
                        printf("\tchild got message : %s\n",buf);
                        sleep(1);
                }
        }
        exit(0);
}

 

 

부모 프로세스는 0부터 1초마다 증가한 값을 파이프에 쓰고, 자식 프로세스로부터 파이프로 읽습니다. 자식 프로세스는 100000부터 증가한 값을 1초마다 쓰고, 읽습니다. 그 결과는 아래와 같습니다.

        child got message : parent 0
parent got message : child 100000
        child got message : parent 1
parent got message : child 100001
        child got message : parent 2
parent got message : child 100002
        child got message : parent 3
parent got message : child 100003
        child got message : parent 4
parent got message : child 100004
        child got message : parent 5
parent got message : child 100005

       

부모 자식 관계의 프로세스가 아닌 별개의 프로세스가 통신할때는 아까 위에서 말씀드린 것 처럼 FIFO를 사용해야합니다. 

 

파이프를 이용한 IPC구현, 이제 어렵지 않겠죠?

 

반응형
블로그 이미지

REAKWON

와나진짜

,

pwd (Print Working Directory)

현재 디렉토리를 알려주는 명령어입니다. 현재 유저가 어느 위치에 있는지 알아볼 수 있는 명령어입니다. 

 

아래의 예에서는 현재 작업디렉토리가 /home/reakwon이라는 디렉토리라는 것을 알 수 있습니다.

[reakwon@localhost ~]$ pwd

/home/reakwon

cd (Change Directory)

디렉토리를 이동하는 명령어입니다. 절대 경로나 상대 경로를 주어서 디렉토리를 이동할 수 있습니다. 

 

cd /root  = /root 디렉토리로 이동합니다.

cd ..  = 현재 디렉토리의 상위 디렉토리로 이동합니다.

cd ~ = 현재 사용자의 홈 디렉토리로 이동합니다.

 

이런 조합으로 이동이 가능합니다.

 

 

ls (List)

디렉토리 안의 파일과 디렉토리를 알 수 있는 명령어입니다. 여러가지 옵션이 존재하는데, 옵션은 여러가지를 혼합해서 사용할 수 있습니다. 옵션을 살펴보도록 하지요.

 

a : 모든 파일을 보여줍니다. 즉, 숨김파일까지 전부 보여주는 것이죠. 숨김파일이란 파일명 앞에 "."이 달린 파일명이고 ls명령어만으로 보여지지 않는 파일입니다.

 

l : (소문자 '엘') 파일의 자세한 내용을 보여줍니다. 

다음은 제 리눅스 pc의 tmp 디렉토리의 내용의 일부를 보여줍니다.

-rw-r--r--. 1 root    root    1148  5월 16 14:33 anaconda.log
drwx------. 2 reakwon reakwon   25  5월 16 14:50 firefox_reakwon
drwxr-xr-x. 2 root    root      18  5월 16 13:51 hsperfdata_root
-rw-r--r--. 1 root    root     420  5월 16 14:33 ifcfg.log
-rwx------. 1 root    root     836  5월 16 14:29 ks-script-o30BkB
-rw-r--r--. 1 root    root       0  5월 16 14:32 packaging.log
-rw-r--r--. 1 root    root       0  5월 16 14:32 program.log
-rw-r--r--. 1 root    root       0  5월 16 14:32 sensitive-info.log
drwx------. 2 reakwon reakwon   24  5월 16 14:34 ssh-TF6BwHbO3Rdo
drwx------. 2 reakwon reakwon   24  5월 16 14:41 ssh-UDg27UAVaZcs
drwx------. 2 reakwon reakwon   24  5월 16 23:13 ssh-bWUCqvi956kP
drwx------. 2 reakwon reakwon   24  5월 16 22:38 ssh-g8sqmExSvLtD
        1.    2.     3.         4.         5.          6.                  7.

 

1. 파일의 종류와 권한: 이 파일이 어떤 파일인지, 소유주, 소유주 그룹, 기타 다른 유저들이 이 파일을 사용할때의 권한을 보여줍니다. 

파일의 종류는 다음과 같습니다.

    a) - : 일반 파일

    b) d : 디렉토리

    c) b : block 장치 파일

    d) c : character 장치 파일

    e) l : 심볼릭 링크 파일

    f) p : 명명된 파이프

    g) s : 소켓 파일

 

권한은 다음과 같이 지정됩니다. 각 3개씩 권한이 나누어 지는데요.

rwx(파일 소유자의 권한)r-x(파일 소유 그룹의 권한)r-x(그외 사용자에 대한 권한)

r : read , w : write , x : execute

 

위의 파일 권한을 해석해본다면 파일 소유자는 읽기,쓰기, 실행 권한을 갖고 있네요. 파일 소유자가 있는 그룹은 이파일을 읽고, 실행만 할 수 있겠네요. 다른 사용자는 읽고, 실행만 할 수 있습니다.

 

2. 하드링크수를 보여줍니다. 

3. 파일의 소유주를 보여줍니다. 디렉토리를 만들거나 파일을 만든 계정입니다. 파일의 소유주는 chown명령어를 이용해 바꿀 수 있습니다.

4. 소유그룹을 보여줍니다. 파일 소유주의 그룹을 나타냅니다. 

5. 파일의 크기를 보여줍니다.

6. 최종 수정일, 시간을 보여줍니다.

7 마지막으로 파일명을 보여줍니다.

 

 

 

i : inode번호를 보여줍니다. 디렉토리에는 파일명과 해당 파일의 inode번호가 매핑되어 있는데, ls -l과 같이 파일의 자세한 정보를 볼 수 있는 것은 inode에 파일에 대한 메타데이터가 기록이 되기 때문입니다. 어떤 것들이 있는지는 아래와 같습니다.

  • 파일 모드 : 파일의 형식과 실행 권한
  • 링크 수 : 이 아이노드에 대한 디렉터리 참조 수
  • 소유자 계정 : 파일의 소유자
  • GID : 이 파일과 관계된 그룹 소유자
  • 파일 크기 : 파일의 바이트
  • 파일 주소 : 주소 정보
  • 마지막 접근(Access) : 마지막으로 파일에 접근한 시각
  • 마지막 수정(Modified) : 마지막으로 파일을 수정한 시각
  • 아이노드 수정(Changed) : 마지막으로 아이노드를 수정한 시각

: 하위 디렉토리의 내용까지 보여줍니다. 예를 들어 현재 디렉토리가 aaa이며 파일이 bbb,ccc 그리고 그 하위 디렉토리가 ccc이며 ccc의 디렉토리에 eee,fff 파일이 있다면 디렉토리 aaa의 파일을 전부 출력해주며 하위 디렉토리인 ccc의 내용까지 전부 출력해주는 옵션입니다.

: 디렉토리인지, 어떤 타입의 파일인지를 보여줍니다.

 

반응형
블로그 이미지

REAKWON

와나진짜

,