728x90
반응형

Operating System은 disks의 의미있는 정보들을 file system으로 관리한다. (file 단위로 관리한다.)

File System

: file과 attributes의 집합체. 수많은 file을 관리하기 위해 file의 이름, 위치를 별도로 관리하고 있다.

새로 파일 생성하면 disk에 생성된다. physical한 위치를 실제 위치가 아닌 filename과 offset을 이용해서 개념적으로 사용자가 보기 편하게 지정한다. -> 경로명을 의미한다.

Directory

: file의 한 종류이다. 파일 중 directory entries를 포함하고 있는 것이다. directory entries는 filename을 disk의 file physical location과 연관시킨다. directory는 그 안에 파일들을 포함할 수 있다. directory entries를 포함하고 있는 파일이다. directory entries는 filename과 offset 페어의 집합을 가지고 있는것이 directory이다.

ex> dir directory안에는 directory entry 정보를 가지고 있다. 파일에 대한 정보를 나타내는 id => offset. file을 새로 만들게 되면 inode 객체가 새로 생성되고 OS는 unique한 id번호(inode)를 부여해준다. ls에 명령을 주면 확인할 수 있다.

ls -i

  inode번호, 파일명 이러한 pair를 directory entries라고 함. 이런 정보들을 가지고 있는 것이 directory다. 대부분의 파일 시스템은 tree structure로 구성되어있다.  -> 똑같은 파일이 존재할 수 있다는 장점(다른 디렉토리에 있다면)

 

Root directory : 다른 모든 sub directory의 부모.

Parent directory : 현재 작업 디렉토리의 parent directory ".." 현재 작업중인 디렉토리 기준으로 부모. 

Current working directory: "." 파일의 경로명을 표현할 때 사용한다. 

home directory : 사용자 id를 가지고 login . 사용자가 만든 아이디가 보통 home directory가 된다. "~" home directory 의미. cd ~ : 홈디렉토리로 감. cd만 입력해도 됨.

sub-directory : 다른 디렉토리 안에 속해있는 디렉토리. 

pathname : 절대경로(absolute path)(fully qualified pathname), 상대경로(relative path)

상대경로는 현재 작업 directory 기준으로 나타낸다. 

상대경로의 가지수는 무한대의 가지수가 있을 수 있다.

 

Change Directory

cd 명령어 사용. 똑같은 작업을 프로그램 상에서도 할 수 있다. 

#include<unistd.h>
int chdir(const char* path);

chdir함수

현재 작업 디렉토리를 다른 디렉토리로 변경할 수 있다. 경로명을 parameter로 주면 이동하게 된다. 성공의 의미로 0을 리턴, 실패하면 -1을 리턴한다. 현재 작업중인 directory만 영향을 미친다.

// getcwd 함수를 이용해서 현재 위치를 확인하자. 

 

Get Current Directory : pwd (print working directory) : 현재 작업 디렉토리

#include<unistd.h>
char* getcwd(char* buf, size_t size);

char* getcwd(char* buf, size_t size) : 현재 작업디렉토리가 반환이 된다. 

getcwd(cwd,sizeof(cwd)); 이런식으로 쓰면 된다. (물론 cwd는 위에 선언해주어야한다)

첫번째 파라미터 : String을 담을 수 있는 파라미터. 현재 작업디렉토리를 buf에 써준다. (output parameter)

두번째 파라미터 : buf의 크기

buf의 크기를 char array의 크기 를 얼마짜리로 만들어야하나?

return 값은 buf의 포인터 값이 리턴. 실패하면 NULL 리턴. 

Buffer size : 최대경로의 길이를 알아야 할텐데 적당히 주기도 위험. 경로의 길이가 얼마가 될지 모르기 때문에. 

-> 시스템 별로 PATH_MAX constant값이 설정되어 있다. 이 값을 활용하자. array의 크기를 PATH_MAX로 설정.

물론 정의되어있지 않은 시스템도 있기때문에 임의로 사용하면 된다.

아래는 PATH_MAX로 buffer의 크기를 지정하엿다.

#include <limits.h>
#include <stdio.h>
#include <unistd.h>
#ifndef PATH_MAX
#define PATH_MAX 255
#endif

int main(void) {
    char mycwd[PATH_MAX];

    if (getcwd(mycwd, PATH_MAX) == NULL) {
        perror("Failed to get current working directory");
        return 1;
    }
    printf("Current working directory: %s\n", mycwd);
    return 0;
}

#ifndef PATH_MAX : PATH_MAX가 정의되어있지않으면 밑에 구문을 실행하라는 내용이다. 이미 정의가 되어있으면 ifndef건너뛰게 되는것이다.

shell에서 실행파일명을 안써도 그냥 ls만 해도 되는 이유?

file name만 주면 shell은 환경변수에서 PATH에 등록된 환경변수를 순서대로 뒤진다. linux에서는 환경변수를 .profile에 설정을 하기도 한다. PATH라는 환경변수 값이 뭐냐 -> env

중간에 PATH가 보임 -> 여기에 directory가 보임. 디렉토리 발견하면 실행.

which 명령어를 사용하여 위치 알 수 있음

which ls

현재 작업디렉토리도 path에 등록되어 있으므로 파일 경로 입력안해도 실행되게 된다. path의 마지막에 등록이 된다.

마지막에 추가해야된다 -> 젤 처음에 추가되면 보안상에 문제가 생긴다. 해커가 ls를 젤 앞에 심어두면 그 프로그램이 실행된다. --> 위험 방지.

 

Directory Access : shell에서는 cd 명령어를 사용해서 했던 것이다. 

프로그램에서 사용하려면? -> opendir, closedir, readdir 함수를 사용한다.

디렉토리 목록을 사용하기 위해 opendir먼저 하고 closedir함수, 하나씩 읽겠다면 readdir 함수 사용한다.

opendir system call

open된것을 하나하나씩 반환을 해준다. 성공하면 DIR pointer를 리턴하고 실패하면 null pointer를 반환한다. 

#include<sys/types.h>
#include<dirent.h>
DIR* opendir(const char* dirname);

opendir 사용하기 위해서는 2가지 헤더 include 하고. 오픈된 디렉토리 목록이 반환이 된다. 포인터값이 반환이 된다. 

DIR type은 <dirent.h>에 정의되어 있고, directory stream을 나타난다.

Directory stream : 나열된 정보. 타겟 디렉토리 안에 있는 모든 directory entries의 sequence.

 

readdir system call

Directory read : directory를 readdir함수를 통해서 읽을 수 있다.

#include<sys/types.h>
#include<dirent.h>
struct dirent *readdir(DIR* dirptr);

dirptr값은 opendir함수의 리턴값. readdir함수를 처음 호출하게되면 순차적으로 반환이 된다. 

readdir의 리턴값이 dirent라는 구조체로 리턴이 된다. 

하나의 directory entry를 가리키는 것이 dirent라는 구조체로 담긴다. 

반복적으로 오픈해서 탐색한다. 

while문 안에서 readdir을 반복적으로 호출하는 것이다.)언제까지 호출할 수 있냐 -> 더이상 읽을 수 없을때까지.

 

dirent 구조체에는 d_ino (inode번호). d_name(파일명) 의 pair정보가 저장되어있다.

다 하면 closedir 사용한다.

closedir system call

#include<dirent.h>
int closedir(DIR* dirptr);

성공하면 0을 반환, 실패하면 -1을 반환한다.

 

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>

// DIR* opendir(const char*dirname);
// struct dirent* readdir(DIR* dirptr);
// int clodedir(DIR* dirptr);
int main()
{
    DIR *temp = NULL;
    struct dirent *file = NULL;
    char home[1024];
    strncpy(home, getenv("HOME"), sizeof(home));

    if ((temp = opendir(home)) == NULL)
    {
        fprintf("stderr,%s directory 정보를 읽을 수 없습니다.\n", home);
        return -1;
    }

    while ((file = readdir(temp)) != NULL)
    {
        printf("%s\n", file->d_name);
    }
    closedir(temp);

    return 0;
}

rewinddir system call

rewinddir : directory entry sequence를 access할 수 있는 DIR* 

내부 포인터를 다시 처음으로 되돌린다. 다시 첫번째 directory entry가 반환이된다. 첫번째 디렉토리를 다시 호출해야겠다 할 때 사용. 

#include<sys/types.h>
#include<dirent.h>
void* rewinddir(DIR* dirptr);

showname.c

#include <dirent.h>
#include <errno.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
   struct dirent *direntp;
   DIR *dirp;

   if (argc != 2) {
      fprintf(stderr, "Usage: %s directory_name\n", argv[0]);
      return 1;
   }

   if ((dirp = opendir(argv[1])) == NULL) {
      perror ("Failed to open directory");
      return 1;
   }

   while ((direntp = readdir(dirp)) != NULL)
      printf("%s\n", direntp->d_name);
   while ((closedir(dirp) == -1) && (errno == EINTR)) ;
   return 0;
}

target directory는 argv[1]에 target directory가 들어있다. dirp 포인터값을 가지고 access할 수있다. 

d_name을 출력한다. 모든 디렉토리 탐색하면서. null이면 모두 읽었으니까 빠져나와서 close(dirp)한다. 

 

File status information 

lstat, stat 함수 이용하면 특정 파일의 속성 정보들 알 수 있다. file을 이름으로 접근한다.

(stat 명령어는 Linux 파일에도 있다) <sys/stat> 헤더파일 include하고 사용해야 한다. 여러가지 정보들을 두번째 파라미터로 반환해준다.

#include<sys/stat.h>
int lstat(const char* restrict path, struct stat* restrict buf);
int stat(const char* restrict path, struct stat* restrict buf);

stat, lstat, fstat 함수들은 모두 파일 정보를 읽어오는 함수이다. 모두 int형 정수를 반환한다.

restrict는 그냥 없다고 생각하고 봐도된다.

그럼 lstat이랑 stat은 뭔 차이일까?

l은 link라고하는 파일. link라고 하는 것은 윈도우에서 바탕화면 아이콘이 링크를 의미한다고 생각하면 된다. 바탕화면 아이콘 더블클릭하면 원본파일이 실행되는 것처럼 symbolic link가 바탕화면 아이콘을 의미한다고 생각하면된다. 

그럼 stat과 lstat에서 첫번째 파라미터로 link가 오게 되면 link의 stat정보를 표현하냐 아니면 원본파일의 stat을 표현하는가??

여기서 stat과 lstat에서 차이가 생긴다.

stat함수에서 link를 주면 원본파일의 status information 반환된다.

lstat함수에서 link를 주면 link파일 자체의 속성파일이 반환된다.

성공하면 stat구조체에 파일 정보들로 채워진다.

 

구조체의 필드 정보(stat 구조체의 필드 정보들)

 

printaccess.c

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

void printaccess(char *path) {
   struct stat statbuf;

   if (stat(path, &statbuf) == -1)
      perror("Failed to get file status");
   else
      printf("%s last accessed at %s", path, ctime(&statbuf.st_atime));
}

last access time을 ctime이라는 함수를 써서 string으로 변환함.

 

Determining the type of a file 

st_mode를 써서 targetfile이 디렉토리인지 아닌지 판단하는 함수.

st_mode type을 조사하기 위한 매크로가 있다. S_ISDIR쓰면 st_mode가 디렉토리인지 확인.

#include <stdio.h>

int isdirectory(char *path);

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

   if (argc != 2) {
      fprintf(stderr,"Usage: %s filename\n",argv[0]);
      return 1;
   }

   if (isdirectory(argv[1]))
      fprintf(stderr,"%s is a directory\n",argv[1]);
   else
      fprintf(stderr,"%s is not a directory\n",argv[1]);
   return 0;
}

man inode확인하면 field의 의미들 나옴.

 

UNIX File Implementation

: 파일의 종류는 주로 파일의 확장자를 통해서 파일의 타입이 결정된다. 파일의 타입을 결정하는 것이 OS level 혹은 application 레벨에서 파일의 타입을 구분하는 경우가 있다. 확장자는 OS가 구분하기 위함이 아니라 사용자가 구분하기 위함이다. Directory가 계층을 이뤄서 구성이 되고, directory안에 directory entry 정보들이 들어간다. 하나의 entry에는 filename과 inode번호를 가지고 있다.

 

inode

: inode는 파일의 정보를 담는 객체이다. 각각의 파일은 inode를 가지고 있고, inode number로 구별된다. Inode는 파일에 대한 정보를 저장하고 있다. stat이나 lstat함수를 호출하면 OS에서 파일에 대한 정보를 inode에서 찾아서 들고온다. 들고와서 stat구조체에 담아준다.

 

시스템에서 생성할 수 있는 inode는 한계가 있다. inode의 크기는 파일의 크기와 관계없이 고정이 되어 있다. inode안에 파일의 컨텐츠가 들어있는건 아니므로 contents를 찾아갈 수 있는 포인터 정보만 있다. 

inode의 구조

inode에서는 pointer 종류가 구분된다. 

direct pointer는 파일의 콘텐츠 블럭을 가리키는데 파일 블럭을 직접 나타낸다. 파일 블럭안에는 파일 내용이 바로 있다.

indirect pointer는 간접적으로 파일블럭을 가리키는 포인터이고 종류는 여기서 3가지가 있다.

single indirect pointer는 한단계 거쳐서 파일 블록을 가리킨다. double indirect pointer는 두 단계, triple indirect pointer는 3단계를 거쳐서 파일을 가리킨다. 

 

direct pointer의 개수만큼 file의 블럭을 direct pointer가 직접 가리키게 된다. 

indirect pointer가 가리키는 block에는 파일 콘텐츠가 없고 direct pointer의 값들이 들어가 있다.

그렇다면 왜 indirect pointer를 가질까?

 

indirect pointer의 존재 이유는 파일이 크기 때문에(컨텐츠 내용이 많음) 많은 개수의 file block을 가리켜야 해서 direct pointer만으로는 부족하다. single direct pointer로 direct pointer들을 가리키는 block을 가리킬 수 있으므로 유용하다. 

 

Directory implementation

: directory라는 것을 어떻게 구현할까? directory안에 directory entry정보가 있다. directory도 하나의 파일이기 때문에 파일 type중 directory 타입이라는 파일이다. directory는 file name과 file location을 담고 있다. 

directory = inode number(file location 표현) + file name

inode에 접근하려면 

 

Advantages of inode + filename 

 File system은 디렉토리들로 구성이되어 있고 디렉토리안에 디렉토리 entry 정보가 들어있다. Directory entry정보란 것은 inode + filename을 유지한다. Directory entry 안에 파일 자체에 대한 정보는 들어있지 않다. 전체적인 파일 시스템 구조는 디렉토리 엔트리들의 집합으로만 표시되어있다. 디렉토리 구조의 크기는 크지 않다. 대부분의 OS에서 분리하는 식으로 파일구조가 이루어져 있다.

 

 실제파일과 directory를 구분하는 장점들은 일단 파일명을 변경할때 간단하다. 파일이름이 디렉토리 entry에 있기 때문에 실제 inode에 찾아갈 필요없이 directory entry만 찾아가서 변경하면 된다. 실제 그 파일의 inode객체를 이동시킬 필요가 없이 directory entry만 옮기면 된다. File move, mv 명령을 가지고 파일의 디렉토리를 옮길 수 있고 이름을 변경할 수 있다. 파일을 다른 디렉토리로 이동하겠다 하면 파일의 inode와 컨텐츠 블록 전체가 이동하는게 아니고 디렉토리 entry만 변경하게 된다.

 

 예를 들어 크기가 기가바이트 크기의 파일을 다운로드 받는다고 하자. 이 파일을 다른 디렉토리로 이동시킬 때 많은 시간이 필요하지 않다. 왜냐하면 파일이 다 이동되는게 아니라 디렉토리 entry만 이동하기 때문이다. 그러나, 파일을 다른 디렉토리로 복사하게 되면 현재 파일 내용과 똑같은 파일을 copy해야하기 때문에 inode를 새로 만들고 contetns도 새로 블록이 생성되어야하기 때문에 시간이 오래걸린다.

 

 또, 디스크 상에 실제 파일 블럭은 하나만 존재하더라도 그 파일을 나타내는 이름은 여러개가 있을수도 있다. 파일은 하난데 파일을 나타내는 link는 여러개를 둘수있다. 같은 inode를 가리키는 디렉토리 entry만 추가하면 되기 때문에 쉽게 추가할 수 있다. 그게 link개념이 되는거다. . 디렉토리 entry의 집합 디렉토리 구조자체는 크지않다. 왜냐하면 실제 파일정보는 inode라는 별도의 객체에 저장이 되어있기때문에 directory entry자체는 크기가 작다. 

 inode구조를 통해서 inode안에 direct, indirect포인터를 통해서 파일블럭을 찾아갈 수있다. 포인터가 몇개의 파일블럭을 가리킬 수 있느냐에 따라 하나의 파일의 최대 크기가 결정이 되는것이고 하나의 포인터를 통해서 이 파일이 표현할 수 있는 최대 크기가 몇바이트 되냐가 예제에 나와 있다.

 

 (Exercise)

 : 어떤 inode객체가 있는 inode가 128바이트라고 가정하자. 포인터는 4바이트짜리다. 가정에서 status information이 차지하는 공간이 68바이트였다. inode공간이 크게 나누면 status information 차지하는 공간, pointer가 차지하는 공간으로 나뉜다.

 

 하나의 블럭의 크기가 8kbyte이다. inode에는 파일 콘텐츠 블럭도 있고 포인터 담는 블럭도 있다. 블럭을 가리키는 포인터의 크기도 32비트 4바이트. 이렇게 가정이 되어있을때 이 inode 객체 안에 direct 포인터 공간이 얼마나 있을까. 이 inode안에 direct pointer는 몇개가 있을 수 있을까. 그럼 포인터 정보를 봐야된다 .포인터는 2가지 종류가 있었다. direct, indirect포인터 또 indirect포인터는 종류가 3가지이다. indirect포인터는 이렇게 하나씩 있다. indirect포인터 3개가 차지하는 공간은 4바이트 공간이 3개가있는거니까 12바이트를 차지한다. 전체 inode크기에서 status information과 indirect 포인터 차지하는 공간 빼면 128 - 12 - 68 = 48 -> 이게 다이렉트 포인터가 차지하는 공간. 다이렉트 포인터의 개수는 48/4 = 12. 즉 12개의 다이렉트 포인터가 존재한다.

 

 그 다음 질문을 보면 다이렉트 포인터를 가지고 얼마나 큰 파일 컨텐츠를 담을 수 있겠느냐. 다이렉트 포인터를 가지고 얼마나 큰 파일을 가질 수 있겠느냐. 12개의 포인터가 있는거니까 다이렉트 포인터 하나가 파일 블럭 하나를 가리키게된다. 파일 블럭 하나가 블럭하나가 8Kbyte이다. 12*8kbyte의 블럭을 가리킬 수 있게 되는것이다.

 

 이렇게 되면 96킬로바이트 크기의 파일콘텐츠를 가질 수있다. 8 kilobyte는 8곱하기 2의10제곱 바이트다. 1킬로바이트라는 것은 2의 10제곱 바이트로 분리하기 때문. 1024바이트*8. 2의 10승 킬로바이트는 1메가바이트.

 

 저장공간의 단위는 이런식으로 표현한다. 8192바이트 12개가 있는것이다. 98304바이트가 된다. 다이렉트 포인터 12개 가지고는 최대 96kb밖에 못하는 것이다. 그래서 indirect pointer가 필요한 것이다.

 

 single indirect pointer를 가지고는 얼마나 큰 크기의 컨텐츠를 가질 수 있겠는지 계산해보자. single indirect pointer는 block을 하나 가리키고 있는데 block안에 direct pointer들을 가지고 있다. direct pointer하나가 파일 블럭을 가리키고 있다. single indirect pointer를 가지고 몇 개의 파일블럭을 가리킬 수 있냐면 direct pointer개수만큼을 가리킬 수 있다. pointer block안에 direct pointer가 몇 개가 있냐의 문제이다. block하나가 8kbyte짜리니까 이 블럭안에 directpointer가 몇개가 들어갈 수 있겠느냐. 포인터하나가 4바이트이기 때문에 8kbyte를 4byte로 나누면 몇 개의 direct pointer를 가지는지 확인할 수 있다. 2048pointer. 2의 11제곱 개가 된다. 그래서 하나의 block안에 2048개의 다이렉트 포인터가 있다는 얘기다.

 

 각각의 다이렉트 포인터로 파일 블럭하나를 가리킬 수 있으니까 2048개의 파일블럭을 가리킬 수 있다. 파일블럭하나도 8kb니까 2048개 * 8kb-> single indirect pointer가지고는 16777216바이트 --> 16mb 정도의 크기를 가지는 것이다.  single indirect pointer를 가지고 16mb 콘텐츠를 가질 수 있다. 같은 방식으로 확장해서 생각해보면 double indirect pointer를 가지고는 얼마나 큰 콘텐츠를 가질 수 있을지 계산할 수 있다.

 

Hard Links and Symbolic Links

: 링크라고 하는 것은 파일 이름과 inode사이의 연관관계를 표현한 것을 link라고 한다. filename은 디렉토리 entry안에 포함되어있었고 inode객체와의 연관관계를 표현한것을 링크라고 한다.

 

 UNIX 시스템에서는 두가지 시스템의 link가 있다고 얘기한다. link라고 하는것이 하나는 hardlink symbolic link 또는 softlink라고 표현한다. 링크도 hard와 soft로 구분된다. 강한association을 가지고 있는게 hardlink이고 느슨한 연관관계가 symbolic link이다. 실제 구조도 보면 이래서 hard구나 symbolic이구나를 확인할 수 있다.

 

 Directory entry 자체가 사실 hard link를 표현한 것이다고 얘기한다. directory entry는 어떤 파일의 inode를 직접 가리키고 있는 객체다. inode 번호를 통해서는 inode를 찾아갈 수 있다. 보통 파일 하나를 생성하면 그 파일을 나타내는 inode객체가 하나 생성된다. inode객체가 생성되고 inode번호가 부여된다. inode번호는 directory entry에 저장된다.

 예를 들어 test라는 파일의 속성과 상태정보 콘텐츠를 찾아갈 수 있는 inode가 생성되고, 파일을 하나 만들게 되면 내부 시스템적으로 inode객체와 directory entry가 생성이 된다. directory entry 자체가 hardlink를 가리킨다. 파일명과 inode사이의 연관 관계를 표현한 것이다. inode번호가 test file inode를 직접 가리키고 있다. 디렉토리 entry자체가 디렉토리 디렉토리 entry가 바로 link, test라는 파일명과 그리고 inode번호가 inode를 결국 나타내는 것이다.

 

 파일명과 inode사이의 연관관계 다이렉트한 연관관계 표현한게 hardlink다. 조금 더 표현하면 내가 지금 절대 경로로 표현해서 루트 밑에 temp라는 디렉토리 밑에 test.txt라고 하는 파일을 하나 만들겠다고 했을 때 디렉토리 구조가 있으니까 디렉토리 entry중에 test.txt라는 entry가 추가 되고 test.txt의 inode가 만들어지고 inode번호가 디렉토리 entry안에 할당이 된다. root밑에 temp 디렉토리 밑에 test.txt파일이 보인다. 디렉토리 entry에 하나가 추가되는 것이다. 파일 하나 만든다고 하는게 파일명과 inode가 직접 direct 연결된걸로 표현한게 하드링크다. 하드링크는 별도로 만드는게 아니라 파일을 만들면 만들어지는 것이다. 재밌는건 파일을 가리키는 하드링크를 또 만들 수 있다. 그냥 파일을 만드는 것도 하드링크 만드는건데 또 추가할 수있다. 디렉토리 엔트리를 하나 추가하는것이다. hardlink개념은 파일명과 inode를 직접적으로 연결시키는 directory가 hardlink이다.

 

 symbolic은 직접연결이 아니라 간접적으로 연결시키는게 symbolic link다. 별개의 파일이고 그 파일안에 원본 파일로 가는 경로명이 들어있다. 이게 symbolic link라는 걸 OS가 알게 되면 원본 파일의 경로를 보고 원본파일에 inode로 찾아간다. symbolic link를 만들면 symbolic link에 해당하는 디렉토리 엔트리가 추가된다. 근데 symbolic link에 해당하는 inode가 있는데 이 inode안에 보면 원본파일의 경로가 들어가있다. inode안에 원본파일 경로가 있다. 그럼 OS가 확인하고 얘는 심볼릭 링크네? 하면 탐색을 멈추는게 아니라 원본파일의 경로를 계속 쫓아간다. /temp/sym이라는 파일을 오픈하겠다하면은 /temp/sym파일의 inode를 봤더니 inode가 symbolic이네? 하고 다시본다. 아 원본은 /temp/test.txt구나 해서 얘를 open한다. symbolic link를 통해 간접 연결, indirect하게 연결되어있다.

 

 이전에 stat함수를 얘기하면서 lstat함수를 얘기하면서 첫번째 변수로 symbolic link가 오게 되면 이 파일의 status information이라고 하는게 심볼릭 링크자체의 정보가 있고 아니면 원본 파일의 어떤 status를 반환하느냐 lstat을 쓰고 첫번째 symbolic link를 주면 바로 symblolic link의 status가 반환된다. 그냥 stat함수를 써서 symbolic link주면 원본의 status가 반환된다. symbolic이 바탕화면 아이콘 이라고 생각하면 된다. 

 

 그래서 inode의 status information중에 어떤 정보가 있었냐면 stat 함수의 결과인 stat 구조체 중에 number of hard link에 해당하는 필드가 있다. hard link의 개수 hardlink가 꼭 하나가 아니라 여러개가 있을 수 있다. hardlink를 추가할때마다 inode 링크값이 하나씩 늘어난다. default로는 카운트값이 1인데 파일에 대해서 hardlink를 하나 더 추가할 수 있다. hardlink의 디렉토리 엔트리가 추가되는것이다. inode 안에 이 inode를 가리키는 카운트값이 유지가 된다. 카운트값이 왜 유지되냐 하면 이 inode 값을 언제까지 유지할꺼냐 os가 판단할 수 있는 기준으로 카운트값을 사용한다.

 

 파일이 생성이 되면 새로운 디렉토리가 생성이 되고 새로운 inode가 생성이 된다. hardlink라는 것은 디렉토리 reference라는 것을 가리키는 것이고 직접적인 포인터정보를 가지고 있는 것이다. 파일과 연관된 파일명이라는 것은 간단하게 디렉토리 엔트리에 포함되어있는 label이라고 표현을 하기도 하는데 그냥 파일이름이라고 생각하면 된다.

 그리고 같은 파일인데 같은 파일을 가리키는 게 꼭하나가 아니라 여러 개가 있을수도 있다. 그 하드링크의 이름은 같은 디렉토리안에 다른 이름으로 존재할수도 잇다. 다른 디렉토리에 원본으로 존재하고 다른 디렉토리에 위치할수도있다. 파일을 생성하면 inode가 생성되고 디렉토리 entry도 추가되면 inode를 access할수있다.

 

 같은 inode를 가리키는 디렉토리엔트리를 또 만들 수 있다. 같은 inode가리키는 hardlink이름이 여러개가 있을수있다. 같은 이름일수도 있고 다른이름일수도 있다. 이것은 symbolic link도 마찬가지다. 경우에 따라 필요하다면 다른이름을 통해서 hardlink를 access할때 어떤 특정 디렉토리를 통해서 파일을 변경하게 되면, (파일 컨텐츠나 뭐 속성) 어떻게 되냐면 예를 들어 test prime이라는걸로 변경했다면 test가 가리키는 inode에 뭔가 추가를 한다. 그 변경된 사항이 test라는 이름이 test라는 걸로 해도 영향을 끼친다. 같은 파일을 가리키고 있기 때문에 다른 하드링크의 이름으로 변경된 사안이 그대로 반영된다.

 이건 하드링크가 아니라 symbolic link도 마찬가지다. 하드링크는 같은 파일시스템 내의 파일에 대해서만 참조를 할 수있다라는 제약 사항이 있다. 새로운 하드링크를 또 생성을 할수있고 새로 생성하면 새로운 디렉토리 entry만 추가되는 것이다. 새로 추가되는것은 같은 inode를 가리키기 때문에 inode번호가 동일하다. 하드링크를 추가하기 위한 shell 명령어는 Ln(link의 약어)명령어를 통해 원본 파일의 hardlink를 추가할 수 있다.

 

 이렇게 추가하면 link count값이 증가하게 된다. 반대로 하드링크가 삭제될때 마다 link count가 삭제된다. 삭제는 rm으로 하면 된다. 하드링크를 생성하기 위해서는 ln shell 명령어 시스템 콜 함수는 link이다. 삭제하기 위해서는 shell명령어는 rm., system call 함수는 unlink. 하드링크를 삭제하면 link count값이 감소한다. 그래서 삭제하다가 삭제해서 count값이 0이 되면 inode 자체가 삭제된다. count값이 0이 아니면 inode유지, 해당 디렉토리 entry만 삭제. inode의 카운트값이 언제까지 유지되는지 보고 결정한다. 최초에는 그냥 파일을 만들게 되면 link count값이 1이니까 rm을 통해서 hardlink를 삭제하면 inode자체도 삭제를 하게 된다. 

 

Link Counter : 대부분의 파일 시스템은 하드링크를 서포팅하고 같은 파일을 가리키는 하드링크가 링크카운터값을 inode에 유지하고 있다. link counter는 정수형 숫자이다. 그리고 link counter의 integer값은 link의 총개수를 유지하는 것이고 새로운 hardlink가 추가될 때마다, link가 삭제될때마다 link count값이 감소하게 된다. 0이되면 os는 inode자체도 파일이 이제 삭제가 되는것이다. 삭제된 inode공간은 system에 반환된다.

 

#inlcude <unistd.h>
int link(const char* path1, const char* path2);
int unlink(const char* path);

link는 파라미터 2개오고 첫번째가 원본 경로명, 두번째가 생성될 경로명이 된다.

unlink는 삭제될 파일의 경로명이 파라미터로 온다.

 

Example

$ ln / dirA/name1   /dirB/name2

The following code performs the same action as the above

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

if(link("/dirA/name1","/dirB/name2")==-1)
	perror("Falied to make a new link in /dirB);

ln 명령어로 만들고자 할때도 파라미터 사용은 동일하다. 원본 하드링크 경로 순으로 입력한다.

하드링크를 새로 만들게 되면 inode관계가 어떻게 되는거냐하면 root밑에 dirA밑에 entry하나가 name1이라는게 있고 12345 라는 inode 숫자이름은 링크의 카운터다. 이 inode를 통해서 name1이라는 것에 컨텐츠 블럭이 따로 잇다. name1이라는 거에 하드링크를 하나 만들었다. name2라고 추가하면 dirB라는 디렉토리 엔트리가 추가되고 inode번호는 원본 inode번호와 같은 번호, 같은 inode를 가리키게된다. 블럭에서는 변화가 없고 linkcount만 하나 추가된다. 이런 상태가 되었으니 name2로 변경하면 파일블럭도 변경된다. 반영된다. hardlink라 하는것도 원본이다.

(inode로 같은지 확인해주면 된다.)

 

 Symbolic link는 파일명과 inode사이에 간접적인 연관관계를 갖는것이다. symbolic link를 만들면 symbolic link라는 파일이 만들어지게 된다. 원본 파일이 포함된 특별한 파일이 만들어지게 된다. symbolic link 이 경로를 내가 access할려는 것을 symbolic link로 하게 되면 symbolic link의 inode를 보고 탐색하는데 그냥 일반 파일이 아니라 symbolic link라는 것을 알게 되면 원본 파일을 보고 탐색을 계속한다. OS가 계속 탐색을 하게 되는것이다. Symbolic link를 만들기 위해서는 ln -s옵션을 주게 되면 hard link가 아닌 symbolic link를 만들게 된다. link count값을 따로 유지하지 않는다. 하드링크는 조금더 강한 링크이기 때문에 inode에서 링크 카운트를 관리하는데 symbolic link는 따로 관리하지 않는다.

 

Symbolic link API

#include <unistd.h>
int symlink(const char* path1, const char* path2);

int symlink라는 별도의 함수를 사용한다. 파라미터의 순서는 1. 원본 2. 새롭게 만들어지는 target 경로명이 된다.

ln -s를 써서 symbolic link를 만든다. symlink써서 만든다. 만약에 에러가 나면 -1이 리턴이 된다. 

name2를 바꾸면 원본파일이 변경이 된다. symbolic link나 hardlink나 결국 같은 것을 가리킨다. 차이는 직접적 연결이나 간접적 연결이냐의 차이가 난다.

ln -s temp.c temp-sym.c

ls -i

우분투에서 보면 색깔도 다르다 --> 별도의 파일이 만들어지고 symbolic link는 표시를 화살표로 해준다. 

temp.c라는 원본파일과 같은 이름 만들고 저장. 그리고 ls하면 아까랑 다른 temp.c가 만들어진다. inode보면 새로운걸 가리키게 된다. 근데 temp-sym.c와 temp.c는 또 간접적으로 연결되어있다.

728x90
반응형

'CS > 시스템 프로그래밍' 카테고리의 다른 글

Times and Timers  (0) 2021.11.16
UNIX Special Files  (0) 2021.11.03
UNIX I/O  (0) 2021.10.15
System Call 함수 [Fork() 함수, wait() 함수]  (0) 2021.10.04
Process  (0) 2021.10.03
728x90
반응형

UNIX는 입출력 devie의 종류와 상관없이 I/O 사용할 수 있게 System Call 함수 4개를 제공해준다. 

이때, 파일이 입출력의 대상이 된다. (Linux에서는 socket도 I/O device 중 하나로 생각하면 된다.)

입출력 장치의 종류와 상관없이 일관된 방법으로 I/O 수행할 수 있도록 UNIX에서는 system call 함수를 제공하고 있다.

 

먼저 알아두어야 할 용어들을 보자. peripheral device란 컴퓨터의 주변장치(입출력 장치)들을 의미한다. 보통은 모니터, 키보드 등을 의미한다. device driver란 device 제어를 위해 필요한 소프트웨어, hardware access 하기 위한 device driver를 의미한다.

UNIX에서는 Open, Close, Read, Write, (ioctl) 4개의 함수만 있으면 I/O device를 쓸 수 있다. 이런 함수를 사용하기 위해서 모든 device를 file로 다룬다. 모든 device는 file로 표현이 된다. (일반적인 file은 아니므로 special file로 표현한다.)

모든 device들은 /dev 디렉토리 안에 저장되고, 주변장치 hardware resource를 나타내는 special file이다. 여기에는 커널 이미지도 존재한다.

그렇다면 UNIX files은 어떻게 다루어 질까?

파일들은 일련의 byte들의 집합으로 다루어진다. file들도 확장자가 있는것을 볼 수 있는데, 이는 사용자가 구분하기 위한 것이다. 모든 I/O device들은 file들로 존재한다. 그리고 OS의 kernel도 file로 존재한다.

 

그래서 UNIX시스템에서 파일을 통해 I/O 수행하는 법만 알면 device들도 다룰 수가 있게 되는 것이다.

먼저 file과 open한 file을 구분해야 된다. open 된 파일에 대한 정보가 별도로 관리가 된다.

file이 열리면 open한 파일에 뭘 기대하냐면 file을 읽어 들이던지 쓰던지 해야 한다. 이를 위해 file offset이라는 정보가 유지가 된다.

file offset은 open된 파일에서만 의미가 있다. file offset은 read나 write를 시작할 position지점을 의미한다. file이 open 되면 초기화되어 file offset은 시작 지점인 0이 된다.

임의의 지점에 쓰고 싶으면 lseek 함수를 사용해서 offset 옮겨야 한다. 

 

read() 함수

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

read함수는 3개의 parameter가 필요하다.

read함수의 return type은 ssize_t. 즉, 숫자 값이 return이 된다. 

-1은 error가 발생하면 return 하게 된다.

1. int fildes -> 첫 번째 파라미터

첫번째 파라미터 file descriptor는 file read를 하려면 먼저 open() 함수 써서 open 해야 된다. 그러면 지정된 경로명을 준다. OS가 해당 경로의 파일을 찾아서 open 해주고 open 된 파일에 access 할 수 있는 return값을 준다. 이 return값은 integer타입이다. 이 return값은 filedescriptor라 하는 값에 해당한다. filedescriptor를 통해 kernel이 특정 파일에 접근할 수 있게 된다.

2. void* buf -> 두 번째 파라미터 

user가 제공하는 buf는 void*인데 실제로는 파일에다 I/O 하는 단위가 byte단위이다. char type의 배열을 만들어 놓는다.

char buff [array크기]는 file에서 읽어 들인 것을 저장하는 공간이고 file로부터 읽어 들일 데이터 저장할 곳이 된다.

3. size_t nbyte -> 세 번째 파라미터

형식은 unsigned integer이다. 요청하는 byte 수를 의미한다. 이 파일로부터 몇 byte를 fildes로부터 읽어 들일건지 결정한다. 지정한 바이트 수 만큼 첫번째 파라미터로 부터 읽어들여서 2번째 파라미터에 쓴다. 

2번째와 3번째 parameter는 연관관계가 있다. 예를 들어, buffer의 크기를 10짜리를 하나 마련했다. 그러면 buf가 가질 수 있는 데이터 크기는 10byte가 최대이다. 그런데 nbyte를 10byte보다 크게 설정하면 읽어들일 수 없다. 그래서 보통 buf의 크기와 nbyte는 같다.

주의해야 할 점은 2번째 parameter는 포인터를 넘기니까 실제 메모리를 할당하고 넘겨야 한다. char* buf 선언만 해 놓고 넘기면 메모리가 없어서 에러가 생긴다. (읽은 값을 저장할 메모리가 없다.)

 

성공적으로 읽었으면 return 값 ssize_t 값이 return 된다. 실제로 읽어 들인 byte 수가 return 이 된다. 

이때, 1byte라도 읽었으면 성공이다. 에러 값은 errno에 설정을 해준다.

read operation을 regular file에서 실행하면 적은 byte가 return 될 수도 있다. 10byte 요청했는데 마지막이 4byte만 남은 경우 4byte만 읽어도 성공한 것이다. (error가 아니다) 10을 요청했는데 4만 반환이 될 수도 있다. read함수는 endoffile 캐릭터 만날 때까지 읽을 수 있는 것이다. 

그다음에 또 read함수 호출하면? -> 한byte도 못 읽게 된 경우는 0을 리턴한다. 

따라서 0을 리턴한다. -> 읽을거리가 없다는 것을 의미한다.

그래서 반복되는 것을 읽는 종료 조건은 return이 0이 아닐 때까지로 정한다.

 

File descriptor  : open 되어 있는 file을 나타내는 지시자이다.

시스템에서 정의하고 있는 constant 값 : process가 시작이 되면 따로 open하지 않아도 file descriptor가 3가지가 open이 되어 정의가 되어있다. 이 3가지는 open을 하지 않아도 read, write 할 수 있다. 커널이 3개의 파일 device를 표준 입출력 장치에 추가한다.

1. STDIN_FILENO : 키보드에 해당한다. 표준 입력장치에 해당하는 File descriptor constant 값(#define 0)

2. STDOUT_FILENO : 표준 출력장치에 해당하는 모니터이다. (#define 1)

3. STDERR_FILENO : 화면과 똑같은 장치인데 우선순위가 높은 에러 메시지를 출력할 때 사용한다. 표준 에러 장치에 해당한다. (#define 2)(close 해서는 안된다.)

 

Readline.c

한 줄을 입력받기 위한 함수이다. 임의의 파일로부터 한줄씩 입력받을 수 있고 read함수와 비슷한 방식으로 parameter 양식을 비슷하게 가져간다.

#include <errno.h>
#include <unistd.h>

int readline(int fd, char *buf, int nbytes) {
   int numread = 0;
   int returnval;

   while (numread < nbytes - 1) {
      returnval = read(fd, buf + numread, 1);
      if ((returnval == -1) && (errno == EINTR))
         continue;
      if ( (returnval == 0) && (numread == 0) )
         return 0;
      if (returnval == 0)
         break;
      if (returnval == -1)
         return -1;
      numread++;
      if (buf[numread-1] == '\n') {
         buf[numread] = '\0';
         return numread;
      }
   }
   errno = EINVAL;
   return -1;

while문을 계속 돌면서 한줄 입력받는다. 한줄을 어떻게 구분할 거냐? -> \n 만날 때까지 하나의 char씩 읽어 들인다.

한 줄을 읽어 들이는 과정의 오류 handling 하는 것이 if문을 통해 구현되어있다.

int numread -> 지금까지 읽어 들인 char 수를 의미 -> 마지막에 return 해줌. -> 실제 read함수와 동일함

int returnval; -> 리턴 값을 저장하기 위한 변수 read함수의 return값을 검사하기 위함

 

반복의 종료 조건 : while문에서 numread값이 nbyte-1보다 작을 때까지 읽어 들이겠다. 

여기서 nbyte와 numread가 같을 때 종료하는 것이 아니라 -1을 하는 이유는 맨 마지막에 null이 들어가기 때문에 null character자리가 필요하기 때문이다.

 

returnval = read(fd, buf+numread,1); -> 하나씩 읽어들이겠다.

buf+numread의 의미 : array의 index는 0부터 시작하는데 numread에 mapping이 된다. numread++로 하나씩 증가시켜서 읽겠다는 의미이다.

if문들은 에러 핸들링을 위한 조건들이다.

1. returnval ==-1 && errno ==EINTR :

 EINTR은 interrupt case를 의미한다. read함수가 -1을 리턴했는데 read함수는 정상적인데 중간에 외부요인을 받아서 return을 못했다는 의미이다(실제 에러 상황이 아님) 그래서 continue로 다시 수행한다. systemcall 함수에서 interrupt를 고려한 코드가 되는 것이다. 

2. returnval ==0 && numread==0 :

한 바이트도 읽지 못했다는 것을 의미한다. fileposition이 EOF이기 떄문에. 더 읽을것이 없다. numread=0 인것은 아직 한바이트도 읽지 않았는데 파일 끝을 만난 것이다. -> input file이 빈 파일이 온경우에 해당한다.

3. returnval==0 : 

계속해서 읽었는데 읽다가 다음에 읽은 것이 EOF를 만난 것이다. input file이 한 줄에 new line character가 없는 경우이다. 하나씩 잘 읽다가 그다음이 EOF을 만난 경우이다. 한 줄을 다 입력받지 못한 상태. break를 해서 while문을 빠져나가서 errno를 설정하고 -1을 리턴한다.

4. returnval  == -1 :

그 외의 다른 에러들 read함수가 내부에서 error가 나서 읽지 못한 경우에 해당한다.

 

정상적으로 읽었으면 한 바이트 읽고 \n 만나면 numread 자리에 \0(null character)를 집어넣는다. \0을 넣어서 한 줄을 string으로 만들어서 numread를 리턴하겠다. numread값에는 null은 빼고 리턴이 된다. String으로 구성해야 되기 때문에 null character가 필요하기 때문에 while문에서 nbytes - 1을 해준 것이다.

 

readline함수를 실제로 사용

char mybuf [100]; // 선언하고 시작해야 함.

bytesread = readline(STDIN_FILENO, mybuf, sizeof(mybuf)); // 98개까지 입력을 받겠다.

 

Read함수라는 것은 3개의 parameter를 필요로 한다. 

#include <stdio.h>
#include <unistd.h>
#define BUFSIZE 80

int readline(int fd, char *buf, int nbytes);

int main(void) {
   char buf[BUFSIZE];
   int num;

   while ( (num = readline(STDIN_FILENO, buf, BUFSIZE)) > 0) {
      fprintf(stderr,"Number of bytes read: %d\n",num);
      fprintf(stderr,"Line:!%.*s!\n",num,buf);
   }
   if (num < 0) {
      fprintf(stderr,"readline returned %d\n",num);
      return 1;
   }
   return 0;
}

fprintf는 젤 앞에 parameter를 추가해서 file에 출력하겠다 라는 의미이다.

c라이브러리에서는 filepointer라는 것을 이용한다.

%.*s는 % s랑 똑같음. 입력받은 내용 계속 출력해주는 것이다.

 

Write() 함수

#include <unistd.h>
ssize_t write(int fildes, const void* buf, size_t nbyte)

buf 안의 내용을 nbyte만큼 fildes에 쓰겠다는 의미이다.(Read 함수와 반대)

file에 쓰인 byte 수가 return이 된다.

더 적은 byte 수만큼도 쓸 수 있다. 즉, 실제로 쓰이는 것은 n보다 작을 수 있다.

그래서 다 쓰였는지 확인할 필요가 있다. 요청한 byte 수보다 작으면 반복해서 쓰는 작업을 수행해야 할 것이다.

 

File Offset

: 오픈된 파일에 대해서 수행할 수 있는 것. 파일을 오픈하게 되면 Offset이 초기화 되게 된다. 다음에 수행할 위치를 File Offset이라고 한다. 오픈된 파일은 0byte로 초기화시킨다. 실제 읽은 byte 수만큼 이동하게 된다. 현재 file offset 값은 이동하게 된다. I/O 작업을 수행한 만큼 fileoffset이 이동된다. close 하면 file offset 정보 없어지게 된다.

read, write 사용할 때 read함수에서 읽어들인 byte수를 return 받아서 이 크기만큼 wrtie를 해야지 error가 나지 않는다. 또 interrupt도 고려해서 write함수를 사용한다.

 

copyfile()

#include <errno.h>
#include <unistd.h>
#define BLKSIZE 1024

int copyfile(int fromfd, int tofd) {
   char *bp;
   char buf[BLKSIZE];
   int bytesread;
   int byteswritten = 0;
   int totalbytes = 0;

   for (  ;  ;  ) {
      while (((bytesread = read(fromfd, buf, BLKSIZE)) == -1) &&
             (errno == EINTR)) ;         /* handle interruption by signal */
      if (bytesread <= 0)          /* real error or end-of-file on fromfd */
         break;
      bp = buf;
      while (bytesread > 0) {
         while(((byteswritten = write(tofd, bp, bytesread)) == -1 ) &&
              (errno == EINTR)) ;        /* handle interruption by signal */
         if (byteswritten < 0)                      /* real error on tofd */
            break;
         totalbytes += byteswritten;
         bytesread -= byteswritten;
         bp += byteswritten;
      }
      if (byteswritten == -1)                       /* real error on tofd */
          break;
   }
      return totalbytes;
}

cp 명령어를 read,write 함수로 구현한 것이다. 

fromfd를 읽어서 tofd로 copyfile을 한다. 둘 다 open을 먼저 하고 넘겨주면 된다. 소스파일 read, 타깃 파일에 write.

char buf [BLKSIZE] : 여기 읽어서 저장

bytesread : read함수의 return값

byteswritten : write함수의 return 값 저장

totlabytes : 소스파일의 크기가 같아진다. // copyfile의 return값

write 할 때 요청하는 블록 크기만큼 안 쓰이고 일부만 되는 경우 계속 write함수 호출해서 buffer다 될 때까지 write함수 반복한다. 소스파일의 내용을 다 타깃 파일로 쓰면 for문 빠져나온다. 

interrupt에 대한 것도 고려해 주었다. 

for문 진입, read 해서 블록을 읽는다. read에서 읽은 값이 <=0 : 에러 상황 or endoffile 만났을 때

 

bp는 buffer내부를 가리키는 포인터이다. 처음 bp값은 buffer와 동일하다. bp를 buff안에서 계속 이동시킨다. bp 포인터값을 이동시킴. 더 써야될 내용 있는지도 확인. 쓴 바이트 수만큼은 뺀다. 1024에서 1000만큼 썼으면 빼서 bytesread에 저장. bp는 다음에 시작할 데이터 값 위치를 저장해줌. 

bp로 실제 써야될 부분으로 옮겨준다. 

 

r_read(), r_write()

#include <errno.h>
#include <unistd.h>

ssize_t r_read(int fd, void *buf, size_t size) {
   ssize_t retval;

   while (retval = read(fd, buf, size), retval == -1 && errno == EINTR) ;
   return retval;
}

interrupt로 인해 중단되었을 경우 다시 read하는 것으로 짜여짐.

#include <errno.h>
#include <unistd.h>

ssize_t r_write(int fd, void *buf, size_t size) {
   char *bufp;
   size_t bytestowrite;
   ssize_t byteswritten;
   size_t totalbytes;

   for (bufp = buf, bytestowrite = size, totalbytes = 0;
        bytestowrite > 0;
        bufp += byteswritten, bytestowrite -= byteswritten) {
      byteswritten = write(fd, bufp, bytestowrite);
      if ((byteswritten == -1) && (errno != EINTR))
         return -1;
      if (byteswritten == -1)
         byteswritten = 0;
      totalbytes += byteswritten;
   }
   return totalbytes;
}

요청한 바이트 수만큼 write한다. 쓰여질때까지 계속해서 반복한다.

 

readwrite()

#include <limits.h>
#include "restart.h"
#define BLKSIZE PIPE_BUF

int readwrite(int fromfd, int tofd) {
   char buf[BLKSIZE];
   int bytesread;

   if ((bytesread = r_read(fromfd, buf, BLKSIZE)) == -1)
      return -1;
   if (bytesread == 0)
      return 0;
   if (r_write(tofd, buf, bytesread) == -1)
      return -1;
   return bytesread;
}

한 블럭을 읽어서 타겟타일로 써주는 함수. copfile은 전체를 쓴다는것에 차이가 있음.

buf의 크기를 PIPE_BUF로 정의해서 사용했음. write함수도 targetfile로 쓰게되었으면 중간에 방해받는 경우 없도록 도와준다. 

 

copyfile()

#include <unistd.h>
#include "restart.h"
#define BLKSIZE 1024

int copyfile(int fromfd, int tofd) {
   char buf[BLKSIZE];
   int bytesread, byteswritten;
   int totalbytes = 0;

   for (  ;  ;  ) {
      if ((bytesread = r_read(fromfd, buf, BLKSIZE)) <= 0)
         break;
      if ((byteswritten = r_write(tofd, buf, bytesread)) == -1)
         break;
      totalbytes += byteswritten;
   }
   return totalbytes;
}

file copy하는것을 더 간단하게 만듬. 

 

readblock()

#include <errno.h>
#include <unistd.h>

ssize_t readblock(int fd, void *buf, size_t size) {
   char *bufp;
   size_t bytestoread;
   ssize_t bytesread;
   size_t totalbytes;

   for (bufp = buf, bytestoread = size, totalbytes = 0;
        bytestoread > 0;
        bufp += bytesread, bytestoread -= bytesread) {
      bytesread = read(fd, bufp, bytestoread);
      if ((bytesread == 0) && (totalbytes == 0))
         return 0;
      if (bytesread == 0) {
         errno = EINVAL;
         return -1;
      }
      if ((bytesread) == -1 && (errno != EINTR))
         return -1;
      if (bytesread == -1)
         bytesread = 0;
      totalbytes += bytesread;
   }
   return totalbytes;
}

r_read()함수를 개선했음. r_read()는 interrupt만 고려, 요청한 바이트수만큼 읽을때까지 반복해서 수행한다. 요청한 바이트수만큼 읽지못하면 error가 발생함. end-of-file 처음에 만나면 0반환. 

for문을 돌면서 size만큼 for문을 돌면서 읽도록 한다. 다 읽으면 totlabytes를 리턴한다. 

 

Open() 함수

open 함수는 2가지 버젼이 있다.

#include <fcntl.h>
#include <sys/stat.h>
int open(const char* path, int flag);
int open(const char* path, int flag, mode_t mode);

 기존에 만들어 놓은 파일 오픈하는 방법과 새로 파일을 생성해서 오픈하는 방법이 있다.

 오픈하려고 하는 파일 char 배열으로 주고, flag변수는 오픈할 때 어떤 모드로 오픈할지 알려준다. flag argument는 OR(|)을 사용하여 access모드와 부가적인 option을 지정할 수 있다. 

2번째 버젼은 mode라는 파라미터가 하나 더 붙는다. mode_t는 permission 정보. 처음에 파일 만들때 access permission 이 무조건 필요하다.

 어떤 사용자가 access할 권한은 3가지 (읽기,쓰기,실행권한)이다. 3가지 권한을 3가지 종류의 사용자(owner, 동일 그룹 사용자, 그 외 다른 사용자)에게 부여한다. 세 종류의 사용자.

 open함수가 성공적으로 open하면 filedescriptor가 return된다. filedescriptor는 open된 파일을 가리키는 지시자. 만약 에러가 발생하면 -1을 리턴하고 errocode가 설정이 된다.

 

Flag

access mode는 셋중하나로 설정해야 한다.

O_RDONLY : read only

O_WRONLY : write only

O_RDWR : read and write

 

Additional flags : OR operation을 통해서 조합해서 사용할 수 있다.

O_APPEND : file offset값의 시작점을 가리킨다. file을 open되었을때 file안에 내용이 쓰여져 있으면, write함수 호출할 때 이 flag로 open하면 endoffile로 이동해서 write를 시작하도록 한다. (원래는 offset은 그냥 0으로 설정되어있음) write랑 같이 사용한다.

ex> O_WRONLY | O_APPEND 이렇게 사용

O_TRUNC : write관련 모드인데, 내용이 있는 file을 오픈할때 내용을 다 제거하고 open한다. 파일을 삭제한게 아니고 파일안에 새로운 데이터를 채우고 싶을때 기존의 내용은 지우고 사용할 수 있게 해준다.

O_CREAT : 새로 파일을 생성해서 open하고자 할때. 해당 경로로 주어진 파일이 존재하지 않는 경우 생성해서 오픈해 준다. 새로 파일을 만드는 것이기 때문에 open모드의 2번째 함수를 적용해서 파일이 없는 경우 새로 만들고 access permission권한이 적용된 파일이 생성되서 오픈된다. 

이 모드 사용할 때 생각해야 될 것이 temp.txt 파일이 이미 존재한다면 어떻게 될까??
이때는 creat 모드가 무시가 된다. 새로 생성이 안되고 그냥 오픈이 된다. temp.txt 라는 파일을 새로 만들어서 사용하려고했는데 이미 존재한다면 이 위에다 새로운 내용을 write해서 덮어쓰면 기존 내용 지워질 수 있어서 위험성이 있다. 기존 내용이 없어질 수가 있으니까 O_CREAT모드와 O_EXCL모드를 같이 사용해준다.

O_EXCL : create모드를 썼는데 temp.txt가 존재하면 O_EXCL이 error을 낸다. (같은 파일이 이미 존재합니다!) -> O_CREAT와 같이 조합해서 쓰도록 하자. 

Question> 파일 오픈하는 요구사항이 주어지고 어떻게 오픈해야 할까 생각해보기.
CASE>존재하는 파일에 덮어쓰는것을 피하고 싶다. --> O_CREAT | O_EXCL 를 사용하여 오픈함수가 에러를 내게끔한다.

O_APPEND의 활용
char buffer[BSIZE];

write(fd,buffer,BSIZE);

1>O_APPEND 없을경우 : 기존 FileOffset지점부터 쓰인다.

2>O_APPEND 있을경우 : offset이 먼저 이동하고 buffer의 내용을 쓰게 된다. 

 

Permission mask : 새로 파일을 만들경우 무조건 줘야한다.(Open함수 2번째 방법의 3번째 파라미터)

permission은 ls -l 했을때 젤 왼쪽에 permission정보랑 같다.

O_CREAT 쓸때 세번째 파라미터로 mode_t 모드 타입으로 지정하고 mode_t 모드는 숫자로 할 수 있다. permission 어떻게 표현되는지 살펴봐야 한다. 권한 하나가 bit하나를 나타낸다. user group others 이렇게 3개 사용자이면 9개의 bit가 필요하다. 사용자별로 묶어서 3bit씩 access 권한을 표시한다(읽기, 쓰기, 실행) 각 사용자에 대해서 3bit로 표현되는 권한의 가지수는 8개가 나온다. 한 사용자를 숫자로 표현한다. ex> 644, 755 숫자말고도 POSIX symbolic name으로 주어진다.

 

예를 들어, info.dat를 만들어서 오픈하고 싶다. 오픈할 때 이미 존재하면 덮어쓰고 싶다. 그리고 새로운 파일은 user에게는 read,write되고 나머지 사용자는 read만 하게 하고 싶다.
--> O_CREAT 모드만 적용하고 O_EXCL모드는 사용 안하고 644 권한을 주면 된다.

open("info.dat",O_RDWR|O_CREAT,fdmode));
mode_t fdmode = 644;

644권한이라는 것은 owner는 110, group은 100, others는 100 권한이므로 owner는 read,write permissions을 가지고, group과 others는 only read만 할 수 있다.

int fd;
mode_t fdmode = (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
if((fd=open("info.dat",O_RDWR|O_CREAT,fdmode))==-1)
   perror("faild to open info.dat");

숫자말고 POSIX symbolic names를 사용하려면 sys/stat.h 헤더파일을 include를 하여 사용하자.

 

copyfilemain.c

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include "restart.h"

#define READ_FLAGS O_RDONLY
#define WRITE_FLAGS (O_WRONLY | O_CREAT | O_EXCL)
#define WRITE_PERMS (S_IRUSR | S_IWUSR)

int main(int argc, char *argv[]) {
   int bytes;
   int fromfd, tofd;

   if (argc != 3) {
      fprintf(stderr, "Usage: %s from_file to_file\n", argv[0]);
      return 1;
   }

   if ((fromfd = open(argv[1], READ_FLAGS)) == -1) {
      perror("Failed to open input file");
      return 1;
   }

   if ((tofd = open(argv[2], WRITE_FLAGS, WRITE_PERMS)) == -1) {
      perror("Failed to create output file");
      return 1;
   }

   bytes = copyfile(fromfd, tofd);
   printf("%d bytes copied from %s to %s\n", bytes, argv[1], argv[2]);
   return 0;                                   /* the return closes the files */
 }

입력 : copfilemain temp.c target.c

앞에서 봤던 copyfile함수도 이용 -> open을 어떻게 했느냐

copyfilemain temp.c target.c --> argument 3개 필요 --> 잘들어왔는지 check 

argv[1] ==> 읽기 전용으로 열겠다

argv[2] ==> 쓰기 전용 새로 만들고 덮어쓰기 x

 

Closing함수 : close() -> 오픈된 파일에 대한 정보를 반환한다. 

#include<unistd.h>
int close(int filedes);

copyfilemain.c는 copy를 하기 위해 2개의 파일을 open해서 사용한다. 다하고 난 다음에 close를 해야한다. 다른 task를 진행하는데 close를 하지않고 계속 실행하면 open하는데 사용된 메모리가 계속 남아있는채로 진행되기 때문에 메모리를 불필요하게 나둔채로 진행하면 낭비가 된다. 불필요하게 사용되는 메모리가 많아진다. 

 

r_close() 

#include <errno.h>
#include <unistd.h>

int r_close(int fd) {
   int retval;

   while (retval = close(fd), retval == -1 && errno == EINTR) ;
   return retval;
}

retval에 return 값 받아서 close한다. EINTR이고 -1이면 외부 interrupt에 의한 종료이기 때문에 다시 close하겠다.

 

lseek 함수

: file을 오픈하고 난 다음에 file offset을 임의의 위치로 옮기기 위해 사용한다.

순차적으로 I/O 가 이동하는 것인데 몇 byte떨어진 지점을 읽고 싶다고 하여 이동시켜야할 필요가 생길 때 사용한다.

#include<sys/types.h>
#include<unistd.h>
off_t lseek(int filedes, off_t offset, int start_flag);

첫번째 파라미터 : int filedes : 해당 open된 파일

두번째 파라미터 : fileoffset 몇 byte를 옮길 것인지. -> 부호가 나타내는 것은 fileoffset의 이동방향

세번째 파라미터 : start_flag -> 2번째 파라미터의 값만큼 이동시키는데 기준점을 나타낸다. constant로 3개 지정되어있다. 현재 fileoffset위치에서 옮기는데 SEEK_CUR이면 file offset로부터 2번째 파라미터 값만큼 옮기겠다.

SEEK_SET : file beginning

SEEK_CUR : File Offset

SEEK_END : File end

ex> lseek(fd, 10, SEEK_CUR) : fd를 현재 fileoffset위치로부터 오른쪽으로 10만큼 이동하겠다. 

SEEK_END에서 부터 10만큼 이동하게 할 수도 있다 -> 파일 확장 expanding file is possible

return 값은 최종적으로 이동된 file offset값이 된다.

 

File representation

file open 하게 되면 open 된 파일에 대한 정보가 어떻게 관리하는지 (File descriptors , File pointers)

open함수를 호출했을때 리턴값 : File descriptors

open함수를 사용하지 않고 File pointers 사용 -> c라이브러리에서 제공하는 파일 포인터가 리턴됨

File pointer안에 File descriptor가 있다고 생각하면 된다.

ex> FILE* fp

사용이 유사하나 차이점이 있다. 

file descriptor 중에서도 표준 입출력 장치 file descriptor는 constant값으로 open된 파일의 I/O 작업을 수행하는것이다.

 

File descriptors : 3종류의 table의 역할을 이해해야한다. 

File이 open 되었을때 내부적으로 사용하는 table

process마다 가지고 있는 table : File descriptor table

1. File descriptor table : process가 오픈한 파일에 대한 정보 가지고 있음. 처음 3개의 entry는 kernel에 의해 자동으로 채워진다. STDIN, STDOUT, STDERR. 커널이 알아서 오픈해준다. filedescriptor값은 결국에 정수값이다. 결국 file descriptor의 index값을 의미한다. STDIN은 0번, STDOUT은 1번, STDERR은 2번으로 인덱스 값을 의미하게 된다. entry의 값은 다음 system file table entry를 가리키는 포인터가 되는 것이다.  그 다음에 오픈 함수를 호출한 것이다(앞에 3개, 2번까지의 것은 process 생기면 추가되어있음) 이 프로세스의 file descriptor의 index가 filedescriptor다. 

 

2. system file table도 오픈할 때마다 추가된다. 이 entry를 file descriptor table이 가리킨다. 대표적으로 fileoffset값이 저장되는데 처음에 0으로 초기화 된다. 그리고 open된 파일에 대한 정보, access 정보가 저장된다. file descriptor table 여러개가 system file table을 가리킬 수 있다(fork()의 경우 가능) ---> count 정보도 들어가있다. count의 의미는 몇개의 file descriptor file이 나를 가리키냐에 해당한다.

 

3. In-memory inode table : 오픈된 실제의 파일이 있을텐데 , 그 파일에 대한 정보는 별도의 공간에 유지가 되고 있다. 그 파일에 대한 위치. open된 파일이 아니라 원래 파일에 대한 정보를 담고 있는 inode로 갈 수 있는 정보가 여기에 유지된다. inode안에 파일 관련 meta info를 저장하고 count값도 저장된다.

 파일을 open하게 되면 3개의 table에 하나씩 추가되게 된다. inode로부터 실제 파일의 내용도 찾아갈 수있다. open된 파일에 대한 정보와 원래 파일에 대한 정보는 다르다. inode안에 실제 파일의 컨텐츠들을 가질수있는 pointer도 저장되어있다. 별다른 블럭에 존재한다. 실제 컨텐츠는 하드디스크에 있다. 예를 들어, write함수로 "abc"를 쓰겠다고 할때 descriptor table entry -> system file table -> in-memory inode table에 실제 파일로 가서 쓰게 된다. 쓴만큼 fileoffset은 자동으로 업데이트된다. in-momery inode에도 count값이 있다. 여러 개의 systme file table이 가리킬 수 있기 때문이다. 예를 들어 또다른 process가 같은 파일 open하면 system file table 여러 개가 in-memory inode table 동일한 entry를 가리킬수있다. in-memory inode에는 실제 active file로 찾아갈 수 있는 정보 포함되어 있다.

 

Qusetion>

1> 프로세스가 close(myfd) 호출해서 닫으면 어떤일이 생길까? --> file descriptor table에서 해당 index 삭제한다. 그리고 systemfile table에서 count값을 하나 감소시킨다. (바로 entry가 삭제되는 것이 아니다) count값이 0이되면 system file table의 해당 entry를 삭제한다. in-memory inode의 count값도 감소시킨다. inmomery도 count값이 0이되면 삭제되는 것이다. close를 호출하면 filedescriptor는 바로 삭제되지만 나머지는 count가 감소하게된다. 

2> 두개의 process가 같은 파일을 오픈해서 write함수로 쓰게 되면? 

- p에서는 "abc"를 my.dat에 쓰고 p`은 "123"을 my.dat에 쓴다. 각각 별도의 file offset을 가지게 된다. 기존 abc의 내용이 사라지고 123을 덮어쓴다(하나의 시나리오이고 꼭 이렇게 되는건 아니다. 확실한건 3byte만 쓰여진다.) 앞서 쓴 내용을 뒤에 쓴 process가 덮어쓰게 된다. 어쨌든 3byte만 쓰여진다. 어떤 프로세스가 나중에 쓰여지냐에 따라 달라짐. 왜냐하면 separate file offset을 가지기 때문이다.

3> 만약에 file offset값이 inode table에 있다면?

in memory inode table에 file offset에 있다면 두 개의 process가 하나의 file offset을 공유하게 되어서 덮어쓰는 것이 되지 않는다. 2개의 process가 서로 이어져서 쓰여진다. 

 

언제 다른 process의 file descriptor table이 system file table 같은entry를 가리킬까? 

- 1번째 파일이 open하고, p가 fork해서 자식프로세스를 만들면 동일한 system file table entry를 가리킨다. 

 

File pointers : FILE structure를 가리키는 것

FILE* myfp;
if((myfp = fopen("/home/ann/my.dat","w"))==NULL)
       perror("failed to open /home/ann/my.dat");
else
       fprintf(myfp,"This is a test");

 FILE structure는 buffer를 가지고 있다. FILE structure는 buffer공간이 있고 buffer이외에 file descriptor를 가지고 있다. 이 FILE structure에 대한 포인터가 File pointers 가 된다. 버퍼 공간을 가지고 있다는 것이 핵심이다.

fopen 의 리턴값으로 FILE*를 리턴받는다. fprintf는 파일에 출력하고 싶을때 사용한다. file 포인터로 쓰게되면 버퍼를 가지고 있으므로 fp가 가리키는 파일에다가 쓰게되면 직접 쓰여지는것이 아니고 buffer에 계속 쓰여지게 된다. buffer가 가득차게되면 실제 쓰여지게 된다.

 그렇다면 buffer를 중간까지 채우고 쓰고 싶으면 어떤 방법을 써야될까?

 -> 조금만 채워졌더라도 쓰여지게 할 수 있는 함수는 fflush함수를 사용하여 강제로 buffer를 비워주면 된다. 

buffer가 가득차게 되면 file descriptor를 통해서 쓰이게 된다.

terminal 파일에다 내용을 쓰면 line buffered된다. 즉, 줄바꿈되면 buffer가 한줄 내용이 출력이된다. 라인단위로 buffer된다. standard output은 라인단위, standard error장치는 버퍼링 과정없이 바로 출력이 된다. standard erro가 standard output보다 우선순위가 높다 -> 빨리 출력이 된다. 

 C라이브러리 함수를 통해서 오픈하면. fopen으로 open하고 file pointer리턴값이 저장된다. 2개의 parameter가 필요하다. 파일의 경로명과 access 모드. 성공적으로 open이 되면 entry가 하나 추가 되고 system file table에도 entry가 추가. my.dat가 최초로 open된것이면 inode table에도 추가. my.dat로 찾아갈 수 있다.

 entry의 index가 file descriptor다. file structure가 생성된다. myfp를 통해서 my.dat에 access할 수 있다. file descriptor도 file pointer안에 포함이 된다. 

 fopen에 에러가 발생하면 null값이 반환된다. file pointer에 쓰겠다. fully buffered가 된다. buffer에 일단 쓰여진다. 다른 작업 수행하고 정상적으로 종료되면, fp가 가리키는 buffer가 비워진다. 

 

Inhertance of file descriptors

부모와 자식 process 간에 file descriptor 관계. my.dat라는 파일안에 "ab"가 들어있다고 가정. 

부모와 자식이 같은 파일로부터 1byte읽었을때

1. open하고 fork : 부모의 filedescriptor값을 그대로 받는다. 동일한 system file , 동일한것 가리키게됨.

-> 부모가 read한것과 자식이 읽은 것과 같음. 부모가 "a"을 읽고 fileoffset은 1로 update. 자식은 그럼 여기서 읽으니까 b를 출력.

2. fork하고 open : 부모 프로세스부터 실행한다고 가정하면, open my.dat오픈하고 entry하나 추가 되고, sft에도 추가되고, inode에도 추가되고 my.dat에 a캐릭터를 읽어서 출력한다. 자식프로세스도 fork함수 다음에 별도로 오픈하겠다 sft에 별도의 entry가 추가된다.

-> 부모와 공유하지 않게 된다. in-memory inode table은 같은 것을 가리키게 된다. 자식도 처음값인 a를 읽게 된다. 

 

1번 케이스. openfork.c

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

int main(void) {
   char c = '!';
   int myfd;

   if ((myfd = open("my.dat", O_RDONLY)) == -1) {
      perror("Failed to open file");
      return 1;
   }
   if (fork() == -1) {
      perror("Failed to fork");
      return 1;
   }
   read(myfd, &c, 1);
   printf("Process %ld got %c\n", (long)getpid(), c);
   return 0;
}

2번 케이스 forkopen.c

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

int main(void) {
   char c = '!';
   int myfd;

   if (fork() == -1) {
      perror("Failed to fork");
      return 1;
   }
   if ((myfd = open("my.dat", O_RDONLY)) == -1) {
      perror("Failed to open file");
      return 1;
   }
   read(myfd, &c, 1);
   printf("Process %ld got %c\n", (long)getpid(), c);
   return 0;
}

 

Line buffering example : terminal file 쓸경우 -> line buffering된다는 예제

terminal buffer인 경우 fully buffered 되지 않고 line buffered된다.

fp가 stdout인 경우, 줄바꿈 캐릭터 나올때까지 출력이 된다.

 

fileiofork.c

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

int main(void) {
   printf("This is my output.");
   fork();
   return 0;
}

This is my output 2번 출력 -> 부모 프로세스로부터 한번, 자식프로세스로부터 한번. (부모 buffer fflush, 자식 buffer fflush된다.)

 

fileioforkline.c

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

int main(void) {
   printf("This is my output.\n");
   fork();
   return 0;
}

This is my output 한번 출력

new line character만나서 출력되고 buffer비워지고, fork하면 자식프로세스는 buffer가 비어있는채로 받아서 return되어도 buffer안에 비울게 없어서 그냥 종료된다. fork되기전에 fflush가 되어버렸기 때문.

 

STDIN과 STDERR의 차이

bufferout.c

#include <stdio.h>

int main(void) {
   fprintf(stdout, "a");
   fprintf(stderr, "a has been written\n");
   fprintf(stdout, "b");
   fprintf(stderr, "b has been written\n");
   fprintf(stdout, "\n");
   return 0;
}

--> a (buffer), 화면에는 출력된 것이 아님.(buffer에 존재)

--> stderr로 되면 a has been written이 화면에 먼저 출력된다. (우선 순위 높다)

--> b(buffer)에 출력이 된다.  (buffer에 존재)

--> b has been written이 화면에 먼저 출력된다.

--> stdout으로 \n -> 한 라인이 쓰여졌으므로 ab가 마지막으로 출력이 된다.

 

bufferinout.c

#include <stdio.h>

int main(void) {
   int i;
   fprintf(stdout, "a");
   scanf("%d", &i);
   fprintf(stderr, "a has been written\n");
   fprintf(stdout, "b");
   fprintf(stderr, "b has been written\n");
   fprintf(stdout, "\n");
   return 0;
}

a라는 것이 먼저 출력되고 scanf로 입력 받고, a has been written, b has been written, b 차례로 출력된다.

버퍼안에 먼저 a가 들어간다. scanf의 동작자체가 input받기전에 버퍼를 비우는 작업부터 한다. 그래서 a가 먼저 출력된다. 입력을 하고 난 다음에는 위와 동일하다. 

 

Filters and redirection

Filtering은 input이 있을때 정제가 되어서 특정조건에 맞는것만 output으로 내보내는 작업을 한다. 보통은 standard output으로 출력하는데 중간에 transformation과정을 거친다.

filter 명령어들 : head, tail, more, sort, grep, awk....

head와 tail은 파일의 내용을 출력해주는 명령. head는 처음, tail은 파일의 내용을 끝에. more명령어는 처음에 한페이지씩 출력. sort명령은 sorting해주는 명령어 string들을 알파벳 순서대로 sort. grep은 키워드를 가지고 있는 파일을 검색해줌. cat은 가장 간단한 명령의 filter. input으로 파일의 내용이 들어오고 파일 내용 그대로 출력. 사실상 0 transformation. 하나도 filtering 안하는 filter. 

Redirection : 방향을 바꾼다. terminal 상에서 redirection사용해서 바꿀 수 있다. 

dup2()는 fildes2의 file descripotr table가 open되어 있으면 닫고, fildes의 entry를 fildes2로 복사한다.

#include<unistd.h>
int dup2(int fildes,int fildes2);

output redirection > : > 왼쪽의 명령어의 결과의 출력결과를 바꾼다. > 오른쪽에 지정한 파일로 왼쪽 명령어의 결과가 출력되는 곳이 바뀐다.

예를 들어 ls는 그냥 출력. ls > temp.txt 하면 화면에 안나오고 temp.txt로 출력방향이 바뀌게 된 것이다. 

input redirection < : 키보드로부터 입력 받아서 하는데, < 오른쪽에 파일 지정해서 왼쪽의 명령어로 넘겨준다. standardinput말고 다른 파일로 input 받을 수 있다.

예를 들어 sort <temp.txt 하면 키보드로 받은것 말고 temp.txt.를 sort해준다. 

내부를 들여다 보자. filedescriptor table을 사용한다. 

output redirection. ls를 수행한 결과를 temp.txt로 보내는 예시. fdt는 화면에 출력하기 위해 standard output인 1번을 통해 내용을 출력하는데 output redirection을 사용하면 temp.txt를 오픈한다. 그래서 fdt에 entry하나 추가된다. fdt에서 entry값이 temp.txt로 가는 entry가 복사가 된다. 그다음에 이 process STDOUT_FILENO(1) write함수를 써서 쓰게 되면 1번 index가 수정이 되어서 temp.txt로 간다. fdt 안의 entry안의 내용이 redirection하면 수정이 된다.

input redirection도 동일.

 

filedescriptor를 복사해주는 systmecall함수도 있다. -> 자주 사용은 x.

int dup2(int fildes, int fildes2); --> 이 함수의 역할은 fildes안에 entry값을 복사해주는 역할을 하는 역할을 한다. 

소스파일 descriptor와 target파일 descriptor 지정가능. 가장 낮은 파일 값에 close를 먼저 하고 entry fildes를 fildes2로 복사해준다. 화면으로 출력되는 것을 my.file로 redirection되서 옮겨준다. dup2함수로 바꿔줌.

redirect.c

#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include "restart.h"
#define CREATE_FLAGS (O_WRONLY | O_CREAT | O_APPEND)
#define CREATE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

int main(void) {
   int fd;

   fd = open("my.file", CREATE_FLAGS, CREATE_MODE);
   if (fd == -1) {
       perror("Failed to open my.file");
       return 1;
   }
   if (dup2(fd, STDOUT_FILENO) == -1) {
      perror("Failed to redirect standard output");
      return 1;
   }
   if (r_close(fd) == -1) {
      perror("Failed to close the file");
      return 1;
   }
   if (write(STDOUT_FILENO, "OK", 2) == -1) {
      perror("Failed in writing to file");
      return 1;
   }
   return 0;
}

my.file을 open하고 accessmode를 주었다, 권한정보는 define되어 있다.

dup2를 호출해서 fd를 STDOUT_FILENO를 1의 entry로 복사를 하겠다. 1이 가리키는 파일은 stdout인데 1은 이제 3과 똑같은 my.file을 가리키게 된 것이다. 

fd를 close하면 fdt의 entry는 삭제된다. 마지막 3번째 entry가 삭제된다. fd를 close해도 sft는 count만 줄어듬. (2->1)

my.file은 계속 open된 상태. STDOUT에 "ok"를 wrtie 한다. my.file에 append되어서 출력이 된다. (STDOUT_FILENO에는 아까 복사된 fd의 entry값이 있기 때문이다)

728x90
반응형

'CS > 시스템 프로그래밍' 카테고리의 다른 글

Times and Timers  (0) 2021.11.16
UNIX Special Files  (0) 2021.11.03
Files and Directories  (0) 2021.10.15
System Call 함수 [Fork() 함수, wait() 함수]  (0) 2021.10.04
Process  (0) 2021.10.03
728x90
반응형

1. fork 함수

Process Creation : 부모 함수를 복사해서 새로운 자식 프로세스를 생성한다

Parent & Child : Calling 함수를 parent, 새로운 process를 child라고 한다

Return 값 : fork 함수의 리턴값이 부모와 자식이 다르다

- 자식은 0을 리턴받는다

- 부모는 자식 프로세스의 id를 리턴받는다.

- 에러가 발생했을시 부모에게 음수를 전달한다.

부모와 자식은 concurrent하게 실행된다.

자식 프로세스는 부모 프로세스 코드를 그대로 실행하고 부모 코드를 자식도 가진다.

자식 프로세스는 fork 함수의 리턴값을 받으면서 fork 함수 아랫줄 라인을 실행하게 된다.

부모 프로세스는 그대로 실행하고 이것과 별개로 자식 프로세스는 fork()에서 0을 리턴받고 fork함수의 아래줄을 실행한다.

- fork를 위해서는 #include <unistd.h> 포함해주자.

 

2. fork() 함수의 특징

fork 함수는 부모의 이미지를 복사해서 새로운 process를 만든다.

child 함수는 부모의 attributes, 예를 들어 environment(환경 변수)와 privileges(권한 정보)를 물려받는다.

open files와 devices같은 부모의 resources도 물려받는다.

fork함수로 만들어진 자식프로세스와 부모 프로세스 관계

- PID와 PPID는 다르다 (상식적으로 일단 다른 프로세스니까)

- 모든 data는 copy된다

- 같은 코드를 사용한다

- 같은 지점부터 실행한다.

- 서로 다른 return 값을 가진다(fork()이후)

 

3. Parent 속성과 resources : 모든 속성과 resources들이 자식에게 물려지는것은 아니다.

Process ID : 자식은 새로운 process ID를 가진다.

CPU Usage (프로세스가 CPU를 얼마나 사용하는지에 대한 정보)

- 자식의 CPU usage 시간은 0으로 reset된다.

Locks and alarm (lock 정보를 물려주면 2개의 process가 둘 다 동시에 access 하기 때문에 불가능하다)

- Lock : 프로세스간 동기화 관련된 부분, resource를 동시에 access하게 되면 conflict 문제가 발생하므로 이를 해결하기 위해 OS에게 lock을 요청한다.

- 자식은 부모가 가지고 있는 lock은 가지지 않는다.

- 만약 부모가 alarm을 가진다면 child는 부모 알람이 끝나도 자식에게 알려지지 않는다

Pending signals : 어떤 event에 대한 통지수단

- 자식은 pending signal없이 시작한다. 부모가 signal의 pending을 fork할때 가지고 있다고 할지라도.

 

4. Parent Attributes and  Resources

자식 프로세스는 분리된 entity로서 processor time을 위해 경쟁한다.

유저가 CPU타임을 더 얻기 위해 많은 process를 만들면 된다.

 

5. fork의 장단점

장점 : child는 parent의 모든 데이타를 상속받는다. process간의 통신은 IPC라는 것을 통해 하는데 child와 parent는 IPC 사용이 필요없어 통신에 장점이 있다.

단점 : 전혀 다른 코드 실행을 위해서는 제한이 있다.

-> 그래서 return값이 다르다는 점을 이용해서 concurrent하게 일부 다른 부분 실행하게 만들 수 있다.

자식이 fork되면 부모가 먼저 실행될까 자식이 먼저 실행될까?

- 대부분 부모이지만 OS의 스케쥴링 시스템에 의해서 무슨 일이 벌어질지는 모른다. 

 

Example 1>

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

int main(void)
{
    int x;
    x = 0;

    fork();

    x = 1;
    printf("I am process %ld and my x is %d\n", (long)getpid(), x);
}

다음과 같은 코드는 fork()함수를 통해 자식을 생성하고 부모 코드에서 선언해둔 x의 값과 자식 프로세스에서의 x의 값이 어떻게 다른지 확인할 수 있다. 자식 프로세스에서도 부모와 동일한 코드를 가지게 되므로 x=0이고 fork()이후에 x=1로 바껴서 출력된다.

I am process 5731 and my x is 1
I am process 5711 and my x is 1

 

자식 프로세스와 부모 프로세스가 다르게 동작하게 하고 싶으면 어떤 방법을 통해서 할 수 있을까? 

fork()함수에서 return 값이 다르다는 것을 이용하면 된다. 자식 프로세스는 fork()함수에서 0을 리턴받지만 부모 프로세스는 새소 생성된 자식 Process ID를 return 값으로 전달받는다.

Ex 2>

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(void)
{
    pid_t childpid;

    childpid = fork();
    if (childpid == -1)
    {
        perror("Faild to work");
    }
    if (childpid == 0)
    {
        printf("I am child %ld\n", (long)getpid());
    }
    else
    {
        printf("I am parent %ld\n", (long)getpid());
    }
    return 0;
}

if 함수를 통해서 3가지로 분기하였다. childpid가 -1을 받을때는 perror를 출력, 0일때는 자식 프로세스 실행, else일 때 부모 프로세스를 실행시켰다. 

I am child 6495
I am parent 6472

 

Ex 3> 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
//argc개의 process를 chain형태로 실행시키기 위함.
int main(int argc, char *argv[])
{
    pid_t childpid = 0;
    int i, n;

    if (argc != 2)
    { //check for valid number of command-line arguments
        fprintf(stderr, "Usage: %s processes\n", argv[0]);
        return 1;
    }
    n = atoi(argv[1]); //atoi함수는 String을 Int로 변환해준다.
    for (i = 1; i < n; i++)
    {
        if (childpid = fork()) //부모 프로세스는 fork()한 다음 break로 빠져나감. 부모 프로세스는 return 값이 0보다 클것이기 때문에 0보다크면 true
        {
            break;
        }
        // 자식 프로세스는 zero value를 return받으므로 다음 loop iteration에서 부모가 된다.
        // 에러가 생기면 fork는 -1을 리턴하고 loop빠져나간다.
    }
    fprintf(stderr, "i:%d process ID:%ld parent ID:%ld childID:%ld\n", i, (long)getpid(), (long)getppid(), (long)childpid);
    return 0;
}

위의 함수는 chain형태로 하나의 부모프로세스에서 그 부모 프로세스는 break되고 자식프로세스가 다시 부모프로세스가 되어 자식 프로세스를 생성하는 프로그램이다.

 

Ex 4>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    pid_t childpid = 0;
    int i, n;

    if (argc != 2)
    {
        fprintf(stderr, "Usage:%s processes\n", argv[0]);
        return 1;
    }
    n = atoi(argv[1]);
    for (i = 1; i < n; i++)
    {
        if ((childpid = fork()) <= 0) //부모가 아닌 경우 break되므로 자식은 모두 break되고 부모 하나만 남는다.
        {
            break;
        }
    }
    fprintf(stderr, "i:%d processID:%ld parentID:%ld childID:%ld\n", i, (long)getpid(), (long)getppid(), (long)childpid);
    return 0;
}

위 프로그램은 하나의 부모 프로세스에서 계속 분기하여 자식프로세스를 만든다. 물론 자식프로세스도 진행되며 부모프로세스가 되어 자식 프로세스를 만들게 된다.

 

6. wait() function

부모 프로세스는 자식 프로세스가 종료되었는지 여부를 체크할 필요가 있다. wait함수를 사용하면 부모 자식의 종료시점이 서로 동기화되는 방식으로 작동한다.

- wait function은 자식의 상태가 변경되었을 경우(일반적인 경우는 종료) 부모 프로세스(caller)가 대기(suspend execution)하게 한다. 

wait function은 부모 프로세스에게 다음을 허용한다.

1) 자식프로세스가 종료될때까지 기다림

2) 자식프로세스의 상태 전달받음

3) 자식 프로세스가 종료하면서 return이 있다면 넘겨받을 수 있다.

 

7. wait and waitpid function

1) #include <sys/wait.h> 사용

2) pid_t wait(int *stat_loc);

wait는 종료한 자식 processID를 반환한다.

- stat_loc으로 종료 상태 정보도 return 가능

- 비정상종료일때 몇번 signal을 받아 종료되었는지도 확인가능

3) pid_t waitpid(pid_t pid, int *stat_loc, int options);

- 첫번째 파라미터를 0으로 주게되면 자식 process 중 부모 process와 같은 process 그룹 중 하나를 선택함

- 특정 자식 프로세스나 특정 프로세스 그룹의 자식을 wait 할 수 있음

- option은 non-blocking모드로 작동할 수도 있다 (WHOHANG으로 마지막 파라미터를 뒀을 경우)

- error가 발생하면 -1이나 errno전달받음

wait나 waitpid는 wait가 완료될때까지 실행이 suspend된다. ==>blocking 버전 함수

non-blocking모드는 한번 함수가 task완료할 수 있는지 check해보는 의미이다. 

자식 프로세스가 끝났나? 한번 체크해보고 종료된 자식 process있으면 종료된 자식process 리턴한다.

 

ex> waiting for all children

 

 

 

728x90
반응형

'CS > 시스템 프로그래밍' 카테고리의 다른 글

Times and Timers  (0) 2021.11.16
UNIX Special Files  (0) 2021.11.03
Files and Directories  (0) 2021.10.15
UNIX I/O  (0) 2021.10.15
Process  (0) 2021.10.03
728x90
반응형

1. Process

process는 operating-system에서 실행중인 프로그램을 의미한다.

process는 서로서로 CPU를 공유한다.

- 한번에 하나의 프로세스가 실행될 수 있다. (Single Core라고 가정한 경우)

- CPU는 빠르게 process 사이를 왔다갔다 한다. (Time Sharing 알고리즘에 따라서 Context Switch를 수행한다)

Process가 OS와 소통하는 것을 system call 이라고 한다.

Process는 interrupt가 일어날 수 있다 (I/O device interrupt, system call)

- OS의 리소스를 사용할 때, SystemCall 함수 호출, OS가 대신 리소스를 사용하고 결과를 전달해 준다.

Process는 Process control block(PCB) 또는 process descriptor이라는 data structure에 정보가 담긴다.

 

2. Context Switch

현재 상태 정보를 저장하고 다음 실행할 process를 load한다.

현재 진행 중인 프로세스의 충분한 정보를 저장해서 아무일 없었던것처럼 재개되어야한다.

context switch는 interrupt에 의해 일어난다. interrupt의 종류는 다음과 같다

(Interrupt가 생기면 context switch가 발생한다)

- software interrupt (system call)

- device interrupt

- timer interrupt (quantum expired) : Process가 실행이 될 때 scheduling algorithm 종류 중에 OS가 process를 선택하고 나서 정해진 시간이 끝났을 때 다 됐으면 process를 ready queue로 쫓아내고 timer interrupt가 일어난다. 

 

3. Process identification

Process는 process ID와 parent process ID를 가지고 있다. (여기서 pid_t는 process ID type 변수이다)

- pid_t getpid(void) : process ID를 반환한다. 

- pid_t getppid(void) : 부모 process ID를 반환한다.

- pid_t 는 unsigned Integer type이다.

- parent process가 종료되면, 자식 프로세스는 system process에게 선택받는다.

User and group ID

- 시스템 관리자는 user account가 생성될 때, userID 와 groupID를 준다.

- UNIX process는 real/effective user 와 group ID를 가지고 각 프로세스에게 권한을 준다. 

(userID는 Linux에서 2가지 개념이 있다. real userID와 effective userID)

 

4. Process State : COMMAND는 PS이다.

 

5. Process Hierachy

Parent와 Child Processes : root를 제외하고는 모두 부모 process를 가진다. 

Root process : 모든 process의 조상, 시스템 boot되면 첫번째로 생성되는 process

Shell process는 리눅스에서 터미널을 실행시키면 생성된다. Shell process가 사용자가 입력한 것을 실행하기 위해 새로운 자식 process를 만들어서 자식 process가 실행시킨다.

Shell Example : Shell creates a new proces when it receives a command

 

6. System Function for process

fork : Clone the calling process

exec : make calling process run a different program

exit : terminate calling process

wait : wait for child process to terminate

 

 

728x90
반응형

'CS > 시스템 프로그래밍' 카테고리의 다른 글

Times and Timers  (0) 2021.11.16
UNIX Special Files  (0) 2021.11.03
Files and Directories  (0) 2021.10.15
UNIX I/O  (0) 2021.10.15
System Call 함수 [Fork() 함수, wait() 함수]  (0) 2021.10.04
728x90
반응형

https://programmers.co.kr/learn/courses/30/lessons/60058

 

코딩테스트 연습 - 괄호 변환

카카오에 신입 개발자로 입사한 "콘"은 선배 개발자로부터 개발역량 강화를 위해 다른 개발자가 작성한 소스 코드를 분석하여 문제점을 발견하고 수정하라는 업무 과제를 받았습니다. 소스를

programmers.co.kr

[문제 풀이]

문제에서 주어진 알고리즘대로 진행하면 된다. 

1. 입력이 빈 문자열인 경우, 빈 문자열을 반환합니다.
2. 문자열 w를 두 "균형잡힌 괄호 문자열" u, v로 분리합니다. 단, u는 "균형잡힌 괄호 문자열"로 더 이상 분리할 수 없어야 하며, v는 빈 문자열이 될 수 있습니다.
3. 문자열 u가 "올바른 괄호 문자열" 이라면 문자열 v에 대해 1단계부터 다시 수행합니다.
 3-1. 수행한 결과 문자열을 u에 이어 붙인 후 반환합니다.
4. 문자열 u가 "올바른 괄호 문자열"이 아니라면 아래 과정을 수행합니다.
 4-1. 빈 문자열에 첫 번째 문자로 '('를 붙입니다.
 4-2. 문자열 v에 대해 1단계부터 재귀적으로 수행한 결과 문자열을 이어 붙입니다.
 4-3. ')'를 다시 붙입니다.
 4-4. u의 첫 번째와 마지막 문자를 제거하고, 나머지 문자열의 괄호 방향을 뒤집어서 뒤에 붙입니다.
 4-5. 생성된 문자열을 반환합니다.

1번부터 보면 빈 문자열은 빈 문자열 반환하면 된다. p가 빈 문자열이면 그대로 return한다.

2번은 u와 v로 분리해야 한다. u와 v로 분리하기위해 sep(p) 함수를 만들었다. '(' 괄호와 ')' 괄호의 수가 같을 때, 그 지점까지를 u에 넣고 그 뒤에 부분을 v에 넣는다.

3번은 u가 '올바른 괄호 문자열'인지 확인해야 한다. 이를 위해 check(p) 함수를 만들었다. 올바른 괄호 문자열이면 True, 아니면 False를 반환하도록 하였다. 

4번은 재귀적으로 해결하면 된다. 4-4 부분이 해석하기가 어려웠다. 먼저 u를 u[1:-1]로 저장하여 첫번째와 마지막 원소를 떼어낸다. 그리고 괄호방향을 뒤집기 위해 reverse함수를 만들어서 '(' 모양은 ')' 모양으로 ')'모양은 '('모양으로 바꾸어주었다. 

def solution(p):
    answer = ''
    if p == '':
        return p
    if check(p):
        return p
    u=sep(p)[0]
    v=sep(p)[1]
    if check(u):
        return u+solution(v)
    else:
        answer+='('
        answer+=solution(v)
        answer+=')'
        u=u[1:-1]
        for i in reverse(u):
            answer+=i
        return answer


def check(p):
    x = y = 0
    for i in range(len(p)):
        if p[i] == '(':
            x += 1
        elif p[i] == ')':
            y += 1
        if x < y:
            return False
    return True


def sep(p):
    x = y = 0
    num = 0
    u = ''
    v = ''
    while True:
        if p[num] == '(':
            x += 1
        elif p[num] == ')':
            y += 1
        num += 1
        if x == y:
            for i in range(num):
                u += p[i]
            for j in range(num, len(p)):
                v += p[j]
            break
    return u,v
def reverse(strings):
    r = {"(":")", ")": "("}
    return [r[s] for s in strings]
728x90
반응형
728x90
반응형

https://www.acmicpc.net/problem/18405

 

18405번: 경쟁적 전염

첫째 줄에 자연수 N, K가 공백을 기준으로 구분되어 주어진다. (1 ≤ N ≤ 200, 1 ≤ K ≤ 1,000) 둘째 줄부터 N개의 줄에 걸쳐서 시험관의 정보가 주어진다. 각 행은 N개의 원소로 구성되며, 해당 위치

www.acmicpc.net

[문제 풀이]

처음에 많이 헤맸었다. 먼저 BFS를 최대한 활용하려고 했었는데 사실 활용하려고 한 게 더 복잡하게 이끌어 간 것 같다. 

이 문제는 s라는 변수에 시간이 주어지고 그 시간때에 위치를 출력하면 되는 문제이기 때문에 BFS를 활용하기 보다는 s만큼 움직여주고 그때의 값을 출력해주는 것이 더 편했다. queue를 만들어줘서 바이러스가 작은 순으로 저장해주고 각각의 바이러스를 꺼내어 상하좌우를 탐색하였다. 

from collections import deque
n,k = map(int,input().split())
graph=[] # n*n
for i in range(n):
    graph.append(list(map(int,input().split())))
s,u,v = map(int,input().split())
# s초 뒤에 (u,v)에 존재하는 바이러스의 종류 출력 -> 밑에서 x,y를 사용해서 u,v로 바꿈
dx=[-1,1,0,0]
dy=[0,0,1,-1]
q=deque()
for a in range(1,k+1):
    for i in range(n):
        for j in range(n):
            if graph[i][j]==a:
                q.append((i,j))
# print(q.popleft())
for _ in range(s):
    for _ in range(len(q)):
        x,y=q.popleft()
        num = graph[x][y]
        for i in range(4):
            nx=x+dx[i]
            ny=y+dy[i]
            if nx>=0 and nx<n and ny>=0 and ny<n and graph[nx][ny]==0:
                graph[nx][ny]=num
                q.append((nx,ny))
print(graph[u-1][v-1])

 

728x90
반응형
728x90
반응형

리스트뷰는 유사하게 반복되는 뷰를 그리기 위한 도구이다. 리스트뷰는 3가지 방법으로 그릴 수 있다.

1. AddView

AddView는 실제로 리스트뷰를 그리기위해서 잘 사용되지는 않는다. AddView는 Item을 담을 xml을 만들어 주고 그 xml에 내용을 채워주어 Container View에 더해준다. 이 작업을 계속 반복한다.

2. ListView

ListView는 리스트로 만들고 싶은 아이템의 리스트를 준비한 뒤 Inflater로 아이템 하나에 해당하는 뷰를 만들어 준다. 여기서 만든 뷰를 컨테이너 뷰에 붙여준다. Adapter를 사용하게 된다. AddView와의 다른점은 AddView는 리스트의 갯수와 상관없이 한번에 다 그리고 ListView는 보여지는 부분만 그리고 필요한 경우(스크롤을 내릴때) 더 그리게 된다.

3. RecyclerView

RecyclerView의 장점은 ListView의 개선판이다. ViewHolder를 포함하기 때문에 RecyclerView를 사용하면 무조건 ViewHolder를 사용하게 된다. 그리고 LayoutManager를 관리할 수 있다. LayoutManager는 Linear, Grid, StaggerGrid로 활용할 수 있어 유연하게 활용 가능하다. 

https://stackoverflow.com/questions/34216890/android-difference-between-gridlayout-and-staggered-gridlayout

 

Android - Difference between Gridlayout and Staggered Gridlayout

I am working in android material design api & want to display some data in grid format. I tried both GridLayout and StaggeredGridlayout and both look same. For general information, i want to as...

stackoverflow.com

앞으로 개발할때 ListView를 만들경우 RecyclerView를 사용하고 이전에 만들어진 ListView, AddView는 알아야 유지보수 가능하기 때문에 배워야 한다.

 

(RecyclerView를 사용하려면 먼저 dependencies에서 recyclerview를 import시켜줘야한다.)

1. 메인엑티비티에 Recyclerview 추가

<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

2. ItemView 레이아웃 추가

3. Recyclerview Adapter 구현-> class를 새로 만들어서 onCreate에 붙여준다.

- RecyclerView.Adapter를 상속하여 구현

- 오버라이드 필요한 메서드

onCreateViewHolder(ViewGroup parent, int viewType) viewType 형태의 아이템 뷰를 위한 뷰홀더 객체 생성.
onBindViewHolder(ViewHolder holder, int position) position에 해당하는 데이터를 뷰홀더의 아이템뷰에 표시.
getItemCount() 전체 아이템 갯수 리턴.

흐름
1) onCreateViewHolder에서 view를 만든것을 viewholder에 넣어준다(아이템 하나씩)
2) viewholder에서 init으로 carname, carengine만 설정
3) onBindViewHolder에서 setText로 내용 설정

class RecyclerViewAdapter(
    val itemList:ArrayList<CarForList>,
    val inflater:LayoutInflater
):RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>(){

    inner class ViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
        val carName:TextView
        val carEngine:TextView

        init{
            carName=itemView.findViewById(R.id.car_name)
            carEngine=itemView.findViewById(R.id.car_engine)
            itemView.setOnClickListener {
                val position:Int = adapterPosition
                val engineName = itemList.get(position).engine
                Log.d("engine",engineName)
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = inflater.inflate(R.layout.item_view,parent,false)
        return ViewHolder(view)
    }//ViewHolder생성되는 함수

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.carName.setText(itemList.get(position).name)
        holder.carEngine.setText(itemList.get(position).engine)
    }//생성된 뷰홀더에 데이터를 바인딩 해주는 함수

    override fun getItemCount(): Int {
        return itemList.size
    }
}

4. Adapter, LayoutManager 지정

- 어뎁터에 대한 객체와 레이아웃매니저의 객체를 생성한 다음, 각 객체를 setAdapter() 메서드와 setLayoutManager() 메서드를 통해 RecyclerView에 지정한다.

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_recycler_view)

        val carList = ArrayList<CarForList>()
        for(i in 0 until 100) {
            carList.add(CarForList("" + i + "번째 자동차", "" + i + "순위 엔진"))
        }
        val adapter = RecyclerViewAdapter(carList, LayoutInflater.from(this@RecyclerViewActivity))
        recycler_view.adapter=adapter
        recycler_view.layoutManager=LinearLayoutManager(this@RecyclerViewActivity)
        // recycler_view.layoutManager=GridLayoutManager(this@RecyclerViewActivity,2)
    }

ViewHolder : 뷰객체를 기억하고 있을 객체
Adapter : 모든 아이템이 담긴 리스트를 RecyclerView에 Binding 시켜주기 위한 사전 작업이 이루어 지는 객체
LayoutManager: 스크롤 위아래로 할지, 좌우로 할지 이런거 결정

728x90
반응형

'App > Android' 카테고리의 다른 글

[2] Kotlin  (0) 2022.03.08
[1] Android 소개  (0) 2022.03.08
Fragment  (0) 2021.08.23
Intent  (0) 2021.08.23
첫 프로젝트 만들기  (0) 2021.03.07
728x90
반응형

https://www.acmicpc.net/problem/18352

 

18352번: 특정 거리의 도시 찾기

첫째 줄에 도시의 개수 N, 도로의 개수 M, 거리 정보 K, 출발 도시의 번호 X가 주어진다. (2 ≤ N ≤ 300,000, 1 ≤ M ≤ 1,000,000, 1 ≤ K ≤ 300,000, 1 ≤ X ≤ N) 둘째 줄부터 M개의 줄에 걸쳐서 두 개

www.acmicpc.net

1.

처음에 풀었을 때 BFS로 미로찾기와 비슷하게 풀었는데 메모리 초과가 생겼다. 

from collections import deque
import sys
input = sys.stdin.readline
n,m,k,x = map(int,input().split())
#도시의 개수 n, 도로의 개수 m, 거리 정보 k, 출발 도시의 번호 x
graph=[[0]*(n+1) for _ in range(n+1)]
distance=[99999]*(n+1)
visited=[False]*(n+1)
for i in range(m):
    a,b=map(int,input().split())
    graph[a][b]=1

def bfs(v):
    q=deque([v])
    distance[v]=0
    while q:
        x = q.popleft()
        for i in range(1,n+1):
            if graph[x][i]==1:
                q.append(i)
                distance[i]=min(distance[x]+1,distance[i])
bfs(x)
answer=[]
for i in range(len(distance)):
    if distance[i]==k:
        answer.append(i)
if len(answer)==0:
    print(-1)
else:
    for i in range(len(answer)):
        print(answer[i])

거리가 K인 것만 출력하면 된다는 사실과 graph를 통해 모든 지점을 탐색할 필요는 없다는 점을 통해 메모리 초과를 해결할 수 있었다.

 

2.

모든 도로의 거리는 1이다. 너비 우선 탐색을 이용하여 해결할 수 있다. 앞서 미로찾기와 같이 이동할 때의 거리는 +1하면서 해결할 수 있다. 문제의 조건에서 노드의 개수 N은 최대 300000개, 도로의 개수 M은 최대 1000000개이다. 따라서 BFS를 사용하여 시간 복잡도 O(N+M)으로 작동하는 소스코드를 작성하여 해결할 수 있다.

from collections import deque
n,m,k,x = map(int,input().split())
graph = [[] for _ in range(n+1)]
for _ in range(m):
    a,b = map(int,input().split())
    graph[a].append(b)
distance = [-1]*(n+1)
distance[x]=0

q=deque([x])
while q:
    now = q.popleft()
    for next_node in graph[now]:
        if distance[next_node]==-1:
            distance[next_node]=distance[now]+1
            q.append(next_node)
check = False
for i in range(1,n+1):
    if distance[i]==k:
        print(i)
        check = True
if check==False:
    print(-1)

3.

다익스트라 알고리즘이 유용하게 쓰이진 않지만 그 틀을 사용해서 풀었다.

import sys
input = sys.stdin.readline
from collections import deque

n,m,k,x = map(int,input().split())
visited = [False]*(n+1)
graph = [[] for _ in range(n+1)]
answer=[]

for _ in range(m):
    i,j = map(int,input().split())
    graph[i].append(j)

queue=deque()
queue.append((x,0))
visited[x]=True

while queue:
    v,dist=queue.popleft()
    if dist == k:
        answer.append(v)
    elif dist<k:
        for i in graph[v]:
            if not visited[i]:
                visited[i]=True
                queue.append((i,dist+1))

if len(answer)==0:
    print(-1)
else:
    answer.sort()
    for i in range(len(answer)):
        print(answer[i])
728x90
반응형

+ Recent posts