Endian 엔디안?

엔디안은 컴퓨터 메모리와 같은 1차원 메모리 공간에 여러 개의 연속된 대상을 배열하는 방법을 말하며, 바이트를 배열하는 방법을 특히 바이트 순서(Byte order)라 한다.

 

 

 

Big Endian 빅 엔디안

빅 엔디안은 큰 단위가 앞에 배열되는 방식이다.

 

 

 

Little Endian 리틀 엔디안

리틀 엔디안은 작은 단위가 앞에 배열되는 방식이다.

 

 

 

정리 (예시)

종류 0x1234의 표현 0x12345678의 표현
빅 엔디언 12 34 12 34 56 78
리틀 엔디언 34 12 78 56 34 12

Shift 연산?

컴퓨터 연산 중에는 shift 연산이 있다. shift 연산이란, 비트 부호를 오른쪽으로 혹은 왼쪽으로 한 칸씩 이동하는 것을 말하는 데, 통상적으로 왼쪽으로 이동하면 x2 의 효과가 나타나고 오른쪽으로 이동하면 /2 의 효과가 나타난다. 이러한 Shift 연산은 두 가지로 나뉘는 데, 그게 바로 Arithmetic shift 와 Logical shift 이다.

 

 

 

Logical Shift 논리적 시프트

왼쪽으로 이동하든 오른쪽으로 이동하든간에 이동이 완료되어서 비어버린 자리에 0을 채워주는 기법을 말한다.

 

 

Arithmatic Shift 산술적 시프트

만약 시프트하려는 수가 Unsigned number, 즉 양수라면 논리적 시프트였을 때와 같이 0을 채워넣어주면 된다. 그러나 만약 시프트하려는 수가 signed number, 즉 음수라면 이야기가 다르다. 음수인 경우, 오른쪽으로 이동한다면 빈 자리에 1을 채워주어야 한다.

먼저 램(RAM)이란?

RAM 은 Random Access Memory 혹은 Rapid Access Memory 의 약자로써 임의의 영역에 접근하여 데이터를 읽을 수 있는 주기억장치이다. 다만, 전원이 off 되면 데이터가 날아가는 휘발성 메모리이다. RAM은 어느 위치에 저장된 데이터이든지 접근하는 시간이 달라지지 않기 때문에 랜덤이라는 명칭이 주어졌다. (반면 디스크의 경우는 가장 최근에 접근한 데이터의 위치나 시간에 따라서 데이터마다 그때그때 접근하는 시간이 달라진다.)

 

 

 

256바이트 램의 구조

왼쪽 상단에 보이는 것은 레지스터이다. 해당 레지스터에 값이 들어오면 비트가 4개씩 이분열이 되어 각각 4x16 다중 입력 게이트에 들어간다. 다중 입력 게이트는 출력되는 값들을 단 한개의 값만 TRUE 를 출력한다. 따라서 램 내부에 존재하는 256개의 격자 중에 단 한 개의 격자만이 선택되는 것이다.

 

 

 

그렇다면 격자 하나가 선택된 이후에는 어떻게 되는걸까?

AND게이트인 x는 단 한 개의 격자에서만 1을 출력할 것이며, 또 그렇게 입력된 데이터가 새로운 입력으로 허락될 때는 s인자가 1로 변할 것이며 s2가 TRUE가 되어 오른쪽에 해당하는 R(레지스터)에 값이 입력될 것이다. 그리고 현재 R(레지스터) 내부에 있는 값의 출력을 원할 때는 e인자가 1로 변할 것이며 자연스럽게 e2가 TRUE가 되어 R의 현재 값이 레지스터를 벗어나 데이터가 흐르고 있는 버스로 흘러들어가게 될 것이다.

 

>> 레지스터 뜯어보기

https://jinn-o.tistory.com/118?category=967431 

 

[논리회로] 레지스터(register) 구조 (바이트 메모리 + 출력 제어기)

바이트 메모리 (Byte Memory) 컴퓨터는 바이트(Byte)단위로 일을 처리한다. 그림에 나와있는 M 은 비트메모리이다. 비트메모리가 8개가 모여서 바이트 메모리가 된다. >> 비트메모리? https://jinn-o.tistory.

jinn-o.tistory.com

 

 

 

결론

그래서 256바이트인 램의 경우, 257개의 레지스터가 들어있을 것이다. 그 중 256개는 바이트를 저장하는 데에 쓰이고, 나머지 하나는 저장 장치에서 특정한 바이트의 위치를 가리키는데 사용된다. 그것을 MAR(Memory Address Register)라고 부른다. 즉, 지금까지 살펴본 MAR는 컴퓨터의 주소와 같은 것이다. 이 주소를 이용해서 램의 특정한 위치에 들어있는 바이트에 접근할 수 있다.

fd (파일디스크립터)를 배정해야 하는 인자에다가 argv[1] 등의 값을 주면 뜨는 오류이다.

fd 에 제대로 fd를 배정해주면 해결된다.

lseek(2)

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

Off_t lssek(int fd, off_t offset, int whence);

lseek()은 파일 디스크립터 파일 오프셋을 지정한 위치로 이동한다. 옮겨갈 위치는 whence라는 인자에서 지정하며, 위치를 정하는 방식은 다음과 같다.

플래그 이동 위치
SEEK_SET 파일을 처음을 기준으로 오프셋 계산 및 이동
n은 양의 정수. 앞을 기준으로 위치는 뒷부분밖에 없기 때문.
SEEK_CUR 현재 위치 기준으로 오프셋 계산 및 이동
n은 양수, 음수 둘다 가능. 해당 위치 기준으로 앞으로도 뒤로도 갈 수 있기 때문.
SEEK_END 파일의 마지막을 기준으로 오프셋 계산 및 이동
n은 음의 정수. 뒤를 기준으로 위치는 앞부분밖에 없기 때문.

 

>> lseek() 시스템 콜 실습하기

https://jinn-o.tistory.com/130

 

[LPI 실습] lseek() 실습 : 원하는 위치부터 파일 읽기

/* file_lseek.c: lseek example, by mjson. jinn_o@naver.com */ #include #include #include #incldue #include #define MAX_BUF 64 char fname[]="newfile_lseek.txt"; char dummy_data[]="abcdefg\n"; int mai..

jinn-o.tistory.com

 

 

 

 

creat()

open() 시스템 콜에 O_WRONLY | O_CREAT | O_TRUNC 의 flags 를 준 것과 동일한 일을 하는 시스템 콜이다.

 

 

 

mkdir(), readdir(), rmdir()

디렉토리 관련 함수. 디렉토리를 생성하고, 읽고, 삭제한다.

 

 

 

pipe()

 

 

 

mknod()

장치 파일을 생성한다.

 

 

 

link(), unlink()

링크를 생성하고 해제한다. (바로가기 기능)

 

 

 

dup(2), dup2(2)

#include <unistd.h>

int dup(int oldfd);
int dup2(int oldfd, int newfd);

인자로 지정한 파일 디스크립터 oldfd를 복제하는 시스템 콜이다.

 

 

 

stat(), fstat()

statistics 통계정보를 출력하는 함수. ls -l 와 비슷한 정보들을 담고있다.

 

 

 

chmod(), fchmod()

접근 권한을 변경한다.

 

 

 

ioctl(2)

#include <sys/ioctl.h>

int ioctl(int fd, unsigned long request, ...);

스트림이 연결된 디바이스에 특화된 작업을 모두 포함하는 시스템 콜이다. 예를 들면, DVD 드라이브 여닫기나 음악 CD 재생, 프린터 구동이나 일시정지, SCSI 디바이스 하드웨어 옵션 설정, 단말 통신 속도 설정 등과 같은 작업을 할 수 있다. 두 번째 인자인 request에 어떤 작업을 할 것인가를 상수로 지정한다. request 별로 추가 지정해야 하는 인자를 세 번째 인자 이후에 지정한다. 사용 가능한 request 목록은 'man ioctl_list'에서 확인할 수 있다.

 

 

 

fcntl(2)

ioctl()의 기능 중에서 파일 디스크립터 관련 작업을 분리하려고 만들어진 것이다. 참고로 cnt 는 control의 준말이다.

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

int fcntl(int fd, int cmd, ...);

여기서도 가변 인자가 사용되고 있다. 두 번째 인자 cmd에 실제로 수행되는 작업을 지정하고, 지정한 작업의 종류에 따라 세 번째 이후의 인자가 결정된다. fcntl()의 가장 단순한 사용 예는 dup()일 것이다. fcntl(fd, F_DUPFD)와 dup(fd)는 동일한 기능을 수행한다. 자세한 내용은 'man 2 fcntl'에서 확인할 수 있다.

 

 

 

sync(), fsync()

/* file_lseek.c: lseek example, by mjson. jinn_o@naver.com */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#incldue <fcntl.h>
#include <errno.h>
#define MAX_BUF 64
char fname[]="newfile_lseek.txt";
char dummy_data[]="abcdefg\n";

int main()
{
    int fd,write_size,read_size,new_offset;
    char buf[MAX_BUF];
    
    fd=open(fname,O_RDWR|O_CREAT|O_EXCL,0664);
    write_size=write(fd,dummy_data,sizeof(dummy_data));
    printf("write_size = %d\n", write_size);
    close(fd);

    fd=open(fname,O_RDONLY);
    new_offset=lseek(fd,3,SEEK_SET);
    read_size=read(fd,buf,MAX_BUF);
    printf("read_size = %d\n",read_size);
    write_size=write(STDOUT_FILENO,buf,read_size);
    close(fd);
}

 

실행 화면

/* file_create.c: create a new file, by mjson. jinn_o@naver.com */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_BUF 64
char fname[]="newfile.txt";
char dummy_data[]="abcdefg\n";

int main(){
    int fd,write_size,read_size;
    char buf[MAX_BUF];
    
    fd=open(fname,O_RDWR|O_CREAT|O_EXCL,0664);
    if(fd<0){
        printf("Can`t create %s file with errno %d\n",fname,errno);
        exit(1);
    }
    write_size=write(fd,dummy_data,sizeof(dummy_data));
    printf("write_size=%d\n",write_size);
    close(fd);
    
    fd=open(fname,O_RDONLY);
    read_size=read(fd,buf,MAX_BUF);
    printf("read_size=%d\n",read_size);
    write_size=write(STDOUT_FILENO,buf,read_size);
    close(fd);
}

 

위의 코드를 자세히 설명하면 다음과 같다.

fd=open(fname,O_RDWR|O_CREAT|O_EXCL,0664);

 파일 디스크립터를 지정함과 동시에 open 시스템 콜 함수를 호출하고 있다. 지정한 파일 경로인 fname을 여는 것인데, flags 옵션들이 O_RDWR(읽고쓰기), O_CREAT(새로운 파일 생성), O_EXCL(배타적으로 생성, 즉 이미 존재하는 파일이면 에러를 출력하란 뜻) 이다. 또한 마지막에 0664는 8진수 표기법이다. 664는 110 110 100 을 의미하며 각각 user 부분의 rwx, group 부분의 rwx, 그외 사용자들 부분의 rwx 에 해당한다.

 만약, 이미 존재하는 파일이라면 뒷 부분에 내용을 추가하는 방식으로 파일을 생성하고 싶다면, O_APPEND 옵션을 추가하면 된다.

 

>> 권한설정 및 8진수표기법 자세히 알아보기

https://jinn-o.tistory.com/121?category=971524 

 

[리눅스 프로그래밍] 리눅스의 다중 사용자 시스템, 권한 설정

다중 사용자 시스템 Multi-User system ?  우리가 리눅스를 사용할 때 가장 먼저 하는 일은 로그인(login)이다. 이 과정을 통해 사용자의 홈 디렉토리에서 셸(Bash 등)이 기동되어 허가된 파일을 읽고 쓰

jinn-o.tistory.com

 

만든 create 함수 실행시켜보기

두번째 실행했을 때 오류가 나는 이유는 파일을 open 할 때 배타적으로 생성했기 때문이다.


// 코드를 작성하는 도중에 필요한 API가 있으면 man 페이지를 참고하여 헤더 파일을 추가하면 된다. 그러나 필요한 헤더 파일을 include 하는 것을 잊어도, gcc에 -Wall 옵션을 붙여서 실행하면 적절하게 경고해준다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stst.h>
#include <fcntl.h>

static void do_cat(const char *path);
static void die(const char *s);

int
main(int argc,char *argv[])
{
    int i;
    if(argv<2){
        // fprintf()는 printf()와 비슷한 기능을 가진 함수다.
        fprintf(stderr,"%s: file name not given\n",argv[0]);
        exit(1);
    }
    for(i=1;i<argc;i++){
        do_cat(argv[i]);
    }
    exit(0);
}

#define BUFFER_SIZE 2048

static void do_cat(const char *path)
{
    int fd;
    unsigned char buf[BUFFER_SIZE];
    int n;
    
    // O_RDONLY, 읽기 전용으로 파일을 열었다.
    fd=open(path,O_RDONLY);
    if(fd<0) die(path);
    // 무한루프. while(1)과 같다.
    for(;;){
        // fd로 지정한 스트림에서 바이트 열을 읽어서 buf에 담는다. 읽어들이는 크기는 최대 sizeof buf, 즉 배열 buf의 크기만큼이다. 배열 buf의 크기는 BUFFER_SIZE로 2048이라고 코드의 윗부분에 정의했다. 따라서 sizeof buf 대신에 BUFFER_SIZE를 사용해도 무관하다.
        n=read(fd,buf,sizeof buf);
        // 파일 처리에 있어서 에러가 발생하면 종료한다.
        if(n<0) die(path);
        // 읽은 바이트 수가 0, 즉 파일의 끝에 도달하여 더 읽어 들일 바이트가 없는지 확인하고 없다면 break, 즉 파일의 끝까지 읽었을 때 종료된다.
        if(n==0) break;
        // STDOUT_FILENO 는 표준 출력이다. 여기서는 n바이트 즉, read()로 읽어들인 바이트 수만큼 쓰고 있다. read()가 언제나 버퍼 크기 가득 읽어들이는 것은 아니다. 예를 들어 길이가 2050인 파일이라면 먼저 2048바이트만 읽고 그다음에는 2바이트만 읽는다. 또한 스트림이 파이프에 연결된 경우에는 읽어들이는 크기가 매번 달라진다.
        if(write(STDOUT_FILENO,buf,n)<0) die(path);
    }
    // 파일을 닫는 동시에 파일이 정상적으로 닫혔는지 확인하기 위해 if문을 사용하고 있다.
    int status=close(fd);
    if(status<0) die(path);
}

static void
die(const char *s)
{
    // perror()은 라이브러리 함수다.
    perror(s);
    exit(1);
}

 

 

 

perror(3)

#include <stdion.h>

void perror(const char *s);

perror() 함수는 errno 값에 해당하는 에러 메시지를 표준 에러 출력에 출력한다. 또한, 문자열 s가 빈 문자열이 아닌 경우에는 s의 내용을 출력하고 이어서 에러 메시지를 출력한다. 위 예에서는 open() 함수로 열려고 했던 파일의 경로를 출력하도록 했다.

 

우리가 작성한 cat 명령어에 에러를 발생시키도록 하여 어떠한 메시지가 출력되는지 살펴보자.

첫 번째 명령어는 존재하지 않는 파일을 실행시켜보았을 때의 예이고, 두 번째 명령어는 내용을 읽기가 가능한 파일이 아니라 디렉토리를 인자로 넣었을 때의 예이고, 세 번째 명령어는 읽기 권한이 없는 파일을 인자로 실행시켜보았을 때의 예이다.

 

strerror()

에러 처리는 perror() 이외에 strerror()라는 라이브러리 함수도 사용할 수 있다.

#include <string.h>

char *strerror(int errnum);

이 함수는 errno 값인 errnum에 해당하는 에러 메시지를 반환한다. strerror()의 반환값은 다시 함수를 호출할 때 덮어써지므로 보통 즉시 출력한다.

사용한 명령어 전체 보기

 

/* mycat program, by mjson. jinn_o@naver.com */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_BUF 64

int main(int argc,char *argv[])
{
    int fd,read_size,write_size;
    char buf[MAX_BUF];
    
    if(argc!=2){
        printf("USAGE: %s file_name\n",argv[0]);
        exit(-1);
    }
    fd=open(argv[1],O_RDONLY);
    if(fd<0){
        // open error handling
    }
    while(1){
        read_size=read(fd,buf,MAX_BUF)'
        if(read_size==0)
            break;
        write_size=write(STDOUT_FILENO,buf,read_size);
    }
    close(fd);
}

 일반적으로 시스템 콜이 실패할 경우 그 원인을 나타내는 정수가 전역변수 errno에 설정된다. errno는 'Error Number(No)'의 약자다. errno에는 파일이 존재하지 않을 때 발생하는 ENOENT나, 인자의 값이 올바르지 않을 때 발생하는 ELNVAL 등의 값이 대입된다. 각 시스템 콜을 사용할 때 발생할 가능성이 있는 errno 값은 man 페이지에서 확인할 수 있다.

write() 시스템 콜 직접 사용해보기 실습

실습(1)의 확장판, 터미널에 읽은 데이터를 출력하기

>> 실습(1)

https://jinn-o.tistory.com/125

 

[LPI 실습] 존재하는 파일으로부터 데이터를 읽기

vi file_test.c 먼저 vi 명령어로 file_test.c 파일을 생성해준다. vi 편집기 내부에서는 다음과 같이 작성했다. /* file_test1.c: read data from a file, by mjson. jinn_o@naver.com */ #include #include #in..

jinn-o.tistory.com

 

 

 

사용한 터미널 명령어 전체보기

참고로 빨간색 밑줄친 부분이 출력 내용이다. \n 처리가 안되서 저렇게 나온거다.

 

file_test_ext.c 파일에 대한 vi 편집기 내부에서는 다음과 같이 작성했다.

/* file_test1.c: read data from a file, by mjson. jinn_o@naver.com */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errono.h>
#define MAX_BUF 16
char fname[]="alphabet.txt";

int main()
{
    int fd,read_size,write_size;
    char buf[MAX_BUF];
    
    fd=open(fname,O_RDONLY);
    if(fd<0){
        printf("Can't open %s file with errno %d\n",fname,errno);
    }
    read_size=read(fd,buf,MAX_BUF);
    write_size=write(STDOUT_FILENO,buf,read_size);
    close(fd);
}

참고로 STDOUT_FILENO 는 표준 출력이기 때문에 터미널에 출력이 되는 것이다.

+ Recent posts