리눅스데브코스 [6주차 - 4]<시스템 프로그래밍 - 시스템 콜>

심우열·2023년 5월 11일
0

1. 리눅스의 목적

1. 리눅스 운영체제가 제공하는 기능

  • 하드웨어 추상화
  • 다중화(multiplex)
  • 격리(isolation)
  • 공유(sharing)
  • 보안(security)
  • 성능(performance)

2. OS 커널이 제공하는 서비스

  • 프로세스(a running program)
  • 메모리 할당
  • 파일 내용
  • 파일 이름, 디렉토리
  • 엑세스 컨트롤(보안)
  • 기타(user, IPC, network, time, terminal)

2. 리눅스 시스템 콜

  • 응용프로그램은 시스템 콜을 통해 OS의 기능을 사용

1. 시스템 콜의 가장 중요한 점

  • 유저의 코드를 절대로 supervisor mode에서 실행시켜 주면 안됨
  • 유저 모드에서 커널 모드로 전환

2. 시스템 콜 실습

실습코드 다운, wget

  • 라이브러리 설치(arm)
sudo apt install libcap-dev libacl1-dev libselinux1-dev libseccomp-dev libc6-dev-armhf-cross

-x86

sudo apt install libcap-dev libacl1-dev libselinux1-dev libseccomp-dev gcc-multilib

1. copy()

  • tlpi-dist/fileio/copy.c
  • 입력파일에서 바이트를 읽어서, 출력 파일에 쓰기

1. open()

  • open()은 파일을 생성하고 파일 디스크립터를 반환(오류시 -1)
  • fd는 작은 정수
  • 리눅스 커널은 프로세스마다 fd 인덱스를 테이블로 관리
  • 다른 프로세스는 다른 fd 네임스페이스를 가짐

2. 내부에서 벌어지는 일

  • 마치 함수 호출처럼 보임, 하지만 실제적으로는 특별한 명령어
  1. 유저 레지스터 저장
  2. supervisor mode 로 전환
  3. 커널 entry point 로 진입
  4. sys open()
  5. 파일 시스템에서 이름 찾고 커널 자료 구조 수정
  6. 유저 레지스터 복구
  7. 유저 모드로 전환

3. 코드 분석

  • tlpi-dist/fileio/copy.c
/* Listing 4-1 */

/* copy.c

   Copy the file named argv[1] to a new file named in argv[2].
*/
#include <sys/stat.h>
#include <fcntl.h>
#include "tlpi_hdr.h"

#ifndef BUF_SIZE        /* Allow "cc -D" to override definition */
#define BUF_SIZE 1024
#endif

int
main(int argc, char *argv[])
{
    int inputFd, outputFd, openFlags;
    mode_t filePerms;
    ssize_t numRead;
    char buf[BUF_SIZE];

    if (argc != 3 || strcmp(argv[1], "--help") == 0)
        usageErr("%s old-file new-file\n", argv[0]);

    /* Open input and output files */

    inputFd = open(argv[1], O_RDONLY);
    if (inputFd == -1)
        errExit("opening file %s", argv[1]);

    openFlags = O_CREAT | O_WRONLY | O_TRUNC;
    filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
                S_IROTH | S_IWOTH;      /* rw-rw-rw- */
    outputFd = open(argv[2], openFlags, filePerms);
    if (outputFd == -1)
        errExit("opening file %s", argv[2]);

    /* Transfer data until we encounter end of input or an error */

    while ((numRead = read(inputFd, buf, BUF_SIZE)) > 0)
        if (write(outputFd, buf, numRead) != numRead)
            fatal("write() returned error or partial write occurred");
    if (numRead == -1)
        errExit("read");

    if (close(inputFd) == -1)
        errExit("close input");
    if (close(outputFd) == -1)
        errExit("close output");

    exit(EXIT_SUCCESS);
}

4. 코드 실행

2. fork()

  • 새로운 프로세스를 생성하는 시스템 콜
  • Shell: 커맨드로 새로운 프로세스를 생성함.
    • 예: $echo hello
  • fork(): 시스템 콜은 새로운 프로세스를 생성
  • 리눅스 커널은 호출 한 프로세스의 자원을 복사하여 생성함
    • Code, Data, Registers, File descriptors, current directory
    • 차이점? 반환하는 PID
    • 부모: pid, 자식: 0

1. 코드 분석

  • tlpi-dist/procexec/fork_whos_on_first.c
/* Listing 24-5 */

/* fork_whos_on_first.c

   Parent repeatedly creates a child, and then processes both race to be the
   first to print a message. (Each child terminates after printing its message.)
   The results of running this program give us an idea of which of the two
   processes--parent or child--is usually scheduled first after a fork().

   Whether the child or the parent is scheduled first after fork() has
   changed a number of times across different kernel versions.
*/
#include <sys/wait.h>
#include "tlpi_hdr.h"

int
main(int argc, char *argv[])
{
    int numChildren, j;
    pid_t childPid;

    if (argc > 1 && strcmp(argv[1], "--help") == 0)
        usageErr("%s [num-children]\n", argv[0]);

    numChildren = (argc > 1) ? getInt(argv[1], GN_GT_0, "num-children") : 1;

    setbuf(stdout, NULL);               /* Make stdout unbuffered */

    for (j = 0; j < numChildren; j++) {
        switch (childPid = fork()) {
        case -1:
            errExit("fork");

        case 0:
            printf("%d child\n", j);
            _exit(EXIT_SUCCESS);

        default:
            printf("%d parent\n", j);
            wait(NULL);                 /* Wait for child to terminate */
            break;
        }
    }

    exit(EXIT_SUCCESS);
}

2. 코드 실행

3. exec()

  • 실행 파일로 호출한 프로세스를 변경
    • 실행 파일(프로그램) -> 호출한 프로세스
  • $echo a b c
  • 프로그램은 컴파일러에 의해 명령어와 메모리 초기값을 저장함.
  • exec()은 호출한 현재 프로세스를 교체
    • 호출한 프로세스의 명령어와 메모리 값은 버림
    • 파일로 부터 새로운 명령어와 메모리 값을 읽음
    • 열린 파일 디스크립터는 계속 유지
  • exec(파일 이름, 인자)

1. 코드 분석

  • tlpi-dist/procexec/t_execl.c
/* Listing 27-5 */

/* t_execl.c

   Demonstrate the use of execl() to execute printenv(1).
*/
#include <stdlib.h>
#include "tlpi_hdr.h"

int
main(int argc, char *argv[])
{
    printf("Initial value of USER: %s\n", getenv("USER"));
    if (putenv("USER=britta") != 0)
        errExit("putenv");

    /* exec printenv to display the USER and SHELL environment vars */

    execl("/usr/bin/printenv", "printenv", "USER", "SHELL", (char *) NULL);
    errExit("execl");           /* If we get here, something went wrong */
}

2. 코드 실행

4. pipe()

  • 프로세스간 통신을 위한 기법
    • ls | grep –nr simple*
  • pipe() 시스템 콜은 두 개의 FD를 생성함
    • 첫번째는 read를 위한 FD
    • 두 번째는 write를 위한 FD

1. 코드 분석

  • tlpi-dist/pipes/simple.pipe.c
/* Listing 44-2 */

/* simple_pipe.c

   Simple demonstration of the use of a pipe to communicate
   between a parent and a child process.

   Usage: simple_pipe "string"

   The program creates a pipe, and then calls fork() to create a child process.
   After the fork(), the parent writes the string given on the command line
   to the pipe, and the child uses a loop to read data from the pipe and
   print it on standard output.
*/
#include <sys/wait.h>
#include "tlpi_hdr.h"

#define BUF_SIZE 10

int
main(int argc, char *argv[])
{
    int pfd[2];                             /* Pipe file descriptors */
    char buf[BUF_SIZE];
    ssize_t numRead;

    if (argc != 2 || strcmp(argv[1], "--help") == 0)
        usageErr("%s string\n", argv[0]);

    if (pipe(pfd) == -1)                    /* Create the pipe */
        errExit("pipe");

    switch (fork()) {
    case -1:
        errExit("fork");

    case 0:             /* Child  - reads from pipe */
        if (close(pfd[1]) == -1)            /* Write end is unused */
            errExit("close - child");

        for (;;) {              /* Read data from pipe, echo on stdout */
            numRead = read(pfd[0], buf, BUF_SIZE);
            if (numRead == -1)
                errExit("read");
            if (numRead == 0)
                break;                      /* End-of-file */
            if (write(STDOUT_FILENO, buf, numRead) != numRead)
                fatal("child - partial/failed write");
        }

        write(STDOUT_FILENO, "\n", 1);
        if (close(pfd[0]) == -1)
            errExit("close");
        exit(EXIT_SUCCESS);

    default:            /* Parent - writes to pipe */
        if (close(pfd[0]) == -1)            /* Read end is unused */
            errExit("close - parent");

        if (write(pfd[1], argv[1], strlen(argv[1])) != strlen(argv[1]))
            fatal("parent - partial/failed write");

        if (close(pfd[1]) == -1)            /* Child will see EOF */
            errExit("close");
        wait(NULL);                         /* Wait for child to finish */
        exit(EXIT_SUCCESS);
    }
}

2. 코드 실행

profile
Dev Ops, "Git, Linux, Docker, Kubernetes, ansible, " .

0개의 댓글