728x90

시스템 프로그래밍 6주차에는 프로세스 정보에 관하여 배운다.

 

  • 프로세스란?

일단 프로그램과 프로세스의 차이에 대하여 먼저 알아보자.

프로그램은 실행 할 프로그램 과 데이터를 합친 컴퓨터 시스템에 실행 요청 전의 상태이다.

프로세스는 실행을 위해 커널에 등록된 작업을 말한다.

 

메모리 안에서 프로세스는

이렇게 차지하게 된다.

 

이제 리눅스 시스템에서 현재 실행중인 process 정보를 확인해보자.

윈도우의 작업관리자라고 생각하면 될 것이다.

ps를 사용하면 현재 실행중인 process 정보를 확인 할 수 있다.

옵션을 추가하자면 -j를 추가하면 job control format으로 출력이 가능하다.

 

top을 이용하면 현재 시스템 내 process 정보를 실시간 확인 가능하다.

사실 이 명령어가 ps 보다 더 작업관리자에 가깝다.

 

kill -signal pid 를 이용하면 해당 process에 signal을 전달한다.

이 부분은 나중에 더 자세히 배워보도록 하자.

 

  • 프로세스 계층

Process ID

Process에 부여된 식별 번호로 시스템 내 유일한 번호가 할당이 된다.

 

Parent process

자신을 생성한 프로세스이며, 모든 프로세스는 부모 프로세스가 있다.

최상위 process = kernel (pid = 0)

Parent process의 ID를 PPID라고 한다.

 

Getting PID/PPID

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

pid_t getpid(void);
pid_t getppid(void);

parameter는 없고 Process ID와 Parent process ID를 반환한다.

 

바로 예제 코드를 작성해보자

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

int main(void){
	printf("PID : %d\n", (int)getpid());
	printf("PPID : %d\n", (int)getppid());

	return 0;
}

그냥 단순히 ID들을 가져오며 실행을 해보면

Process group

관련 process들을 묶은 것을 process group이라고 한다.

하나의 job 수행을 목적으로 하며, Process group에 전달 된 signal은 그룹 내 모든 프로세스에게 전달 된다.

 

그리고 그 중 가장 중심이 되는 Process를 Process group leader라고 한다.

Process Group의 ID는 leader의 PID를 따라간다.

 

Process group을 확인해보자.

우선 shell script를 작성한다.

echo test.sh ... start
ping 192.168.0.1
echo test.sh ... end

실행을 하고 터미널을 하나 더 열어서 Process ID들을 확인해본다.

이 화면이 Shell Script를 실행한 화면이고

ps -j e 명령어를 사용한 화면이다.

PGID가 같은 것들이 있는 것을 확인 할 수 있다.

 

kill -[signal num] -[groupID]로 해당 그룹 전체에 신호를 보낼 수 있다.

이 부분은 다음 시간에...

 

Setting process group ID

#include <unistd.h>

int setpgid(pid_t pid, pit_t pgid);

-pid

옮기고 싶은 target process의 pid

-pgid

설정하려는 group의 pgid

 

pid를 0으로 주면 current process로 설정하고, pgid = 0이면 pgid를 pid로 설정한다.

 

근데 여기서, 아무 group으로 이동할 수는 없고 같은 session 내에서만 이동이 가능하다.

 

session은 사용자가 로그인해 작업하고 있는 터미널 단위로 프로세스 그룹을 묶은 것을 말한다.

session은 하나의 foreground process group과 0개 이상의 background process group을 가진다.

foreground process group은 사용자의 입력을 받는 process group이고

background process group은 foreground process group 외의 process group을 말한다.

 

session에도 leader가 있는데 login shell process이다.

 

  • Process 실행 시간

Process running time = system running time + user running time 이며

 

직관적으로 알 수 있겠지만

System running time은 System call에 소요된 시간

User running time은 사용자 모드에서 수행한 시간으로 사용자 작성 코드를 실행하는데 걸린 시간이다.

 

Getting process running time

#include <sys/times.h>

clock_t times(struct tms *buf);

-buf

running time 정보를 저장할 tms 구조체의 포인터이다.

return: 특정 시점부터 경과한 시간, -1(error)

 

parameter로 들어가는 tms 구조체는

struct tms {
	clock_t tms_utime;      /* [XSI] User CPU time */
	clock_t tms_stime;      /* [XSI] System CPU time */
	clock_t tms_cutime;     /* [XSI] Terminated children user CPU time */
	clock_t tms_cstime;     /* [XSI] Terminated children System CPU time */
};

이렇게 구성이 되어 있다.

 

바로 코드를 작성해보자.

#include <sys/types.h>
#include <sys/times.h>
#include <time.h>
#include <limits.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(void){
	time_t t;
	struct tms mytms;
	clock_t t1, t2;

	if((t1 = times(&mytms)) == -1) {perror("times 1"); exit(1);}

	sleep(5);

	for(int i = 0; i < 999999; i++) time(&t);

	if((t2 = times(&mytms)) == -1) {perror("times 2"); exit(1);}

	printf("Read time : %.1f sec\n", (double)(t2 - t1) / sysconf(_SC_CLK_TCK));
	printf("User time : %.1f sec\n", (double)mytms.tms_utime / sysconf(_SC_CLK_TCK));
	printf("System time : %.1f sec\n", (double)mytms.tms_stime / sysconf(_SC_CLK_TCK));

	return 0;
}

이렇게 실행시간을 측정해보면

User time과 System time까지 구할 수 있다.

 

  • 환경변수

환경 변수는 Process 실행환경에 정의되어 있는 변수를 말한다.

우선 shell 환경에서 먼저 보자면

 

env를 사용해서 환경변수 목록을 확인할 수 있다.

$export 를 사용해서 환경변수를 선언할 수 있다. ($export 환경변수명 = 값)

Getting enviroment variables

프로그램을 만들 때에도 환경 변수를 가져올 수 있다.

 

environ 전역 변수 사용

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

extern char **environ;

int main(void){
	char **env;

	env = environ;
	while(*env){
		printf("%s\n", *env);
		env++;
	}

	return 0;
}

 

main의 인자로 전달

#include <stdio.h>

int main(int argc, char **argv, char **envp){
	char **env;

	env = envp;
	while(*env){
		printf("%s\n", *env);
		env++;
	}

	return 0;
}

 

#include <stdlib.h>

char *getenv(const char *name);

-name

읽어올 환경변수 명

return: 환경변수 값, NULL(error)

 

이렇게 원하는 환경변수만을 가져올 수 있다.

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

int main(void){
	char *val;

	val = getenv("MY_NAME");
	if(val == NULL) printf("MY_NAME not defined\n");
	else printf("MY_NAME = %s\n", val);

	return 0;
}

 

Setting enviroment variables

#include <stdlib.h>

int putenv(char *string);
int setenv(const char *name, const char *value, int overwrite);

-string

추가할 환경변수와 값 (변수=값)

-name

추가할 환경변수의 이름

-value

추가할 환경변수의 값

-overwrite

추가되는 인수

 

Process에 의해 추가되는 환경 변수는 자신과 자식 프로세스에서만 유효하다.

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

int main(void){
	char *val;

	val = getenv("MY_NAME");
	if (val == NULL) printf("SHELL not defined\n");
	else printf("1. MY_NAME = %s\n", val);

	putenv("MY_NAME=SK");

	val = getenv("MY_NAME");
	printf("2. MY_NAME = %s\n", val);

	return 0;
}

이렇게 MY_NAME에 해당하는 환경변수를 변경해도

그 process에서만 유효하다.

 

Unsetting enviroment variables

#include <stdlib.h>

int unsetenv(const char *name);

-name

삭제할 환경변수

return: 0(success), -1(error)

 

직관적으로 알 수 있듯 환경변수를 삭제하는 system call이다.

728x90

시스템프로그래밍 5주차에는 시스템 정보에 관하여 배운다.

 

MAC에서는 사용자 전환이 잘 안되어서... groom으로 진행하겠습니다.

 

  • 시스템 정보

시스템에 설치된 운영체제에 관한 정보, 호스트명 정보, 하드웨어 종류에 대한 정보 등을 얻는 방법을 알아보자.

 

uname

shell에 uname을 사용하면 시스템의 기본 정보를 출력한다.

system call로 알아보자

#include <sys/utsname.h>

int uname(struct utsname *buf);

-buf

읽은 정보를 저장할 utsname 구조체의 포인터

return: 0(success), -1(error)

 

여기에 들어가는 utsname 구조체에 대해 살펴보면

struct  utsname {
	char    sysname[_SYS_NAMELEN];  /* [XSI] Name of OS */
	char    nodename[_SYS_NAMELEN]; /* [XSI] Name of this network node */
	char    release[_SYS_NAMELEN];  /* [XSI] Release level */
	char    version[_SYS_NAMELEN];  /* [XSI] Version level */
	char    machine[_SYS_NAMELEN];  /* [XSI] Hardware type */
};

이고 이 구조체를 이용하여 시스템의 기본 정보를 가져오는 코드를 작성해보자.

#include <sys/utsname.h>
#include <stdlib.h>
#include <stdio.h>

int main(void){
	struct utsname uts;

	if(uname(&uts) == -1){
		perror("uname");
		exit(1);
	}

	printf("OSname : %s\n", uts.sysname);
	printf("Nodename: %s\n", uts.nodename);
	printf("Release: %s\n", uts.release);
	printf("Version: %s\n", uts.version);
	printf("Machine: %s\n", uts.machine);
	
	return 0;
}

sysinfo

sysinfo를 사용해서 다른 정보들을 가져올 수도 있다.

#include <sys/sysinfo.h>

int sysinfo(struct sysinfo *info);

-info

읽은 정보를 저장할 sysinfo 구조체의 포인터

return: 0(success), -1(error)

 

sysconf

#include <unistd.h>

long sysconf(int name);

-name

검색할 정보를 지칭하는 상수

return: 요청한 정보의 값, -1(error)

 

이번엔 리소스의 정보를 가져오는 system call이다.

 

상수의 예

상수 설명
_SC_ARG_MAX(1) argv[]와 envp[]를 합한 최대 크기로, 바이트 단위로 표시한다.
_SC_CHILD_MAX(2) 한 UID에 허용되는 최대 프로세스 개수를 나타낸다.
_SC_CLK_TCK(3) 초당 클록 틱 수를 나타낸다.
_SC_OPEN_MAX(5) 프로세스당 열 수 있는 최대 파일 개수를 나타낸다.
_SC_VERSION(8) 시스템이 지원하는 POSIX.1의 버전을 나타낸다.
_SC_PASS_MAX(9) 패스워드의 최대 길이를 나타낸다.
_SC_LOGNAME_MAX(10) 로그인명의 최대 길이를 나타낸다.
_SC_PAGESIZE(11) 시스템 메모리의 페이지 크기를 나타낸다.

sysconf를 이용해서 리소스의 정보를 출력해보자면

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

int main(void){
	printf("Clock Tick : %ld\n", sysconf(_SC_CLK_TCK));
	printf("Max Open File : %ld\n", sysconf(_SC_OPEN_MAX));
	printf("Max Login Name Length : %ld\n", sysconf(_SC_LOGIN_NAME_MAX));
	
	return 0;
}

  • 사용자 정보

사용자를 추가하거나 권한을 관리하는 방법들이다.

우선 shell에서 사용자(seungkyu)를 추가해보자.

sudo adduser [사용자 이름]을 입력하면 된다.

일단 이렇게 모두 디폴트로 설정을 하면 된다.

 

해당 사용자로 접근을 해보자.

su [사용자 이름] 명령어를 사용하면 해당 사용자로 접근할 수 있다.

exit로 나갈 수 있다.

UID는 그 사용자에게 부여된 ID 번호이며, Login name은 문자 형태의 사용자 이름이다.

이렇게 id [사용자 이름]으로 확인 할 수 있다.

Process에 입장 할 때의 사용자에 대해 알아보자.

- Real user ID

최초에 Process를 실행한 user의 UID

- Effective user ID

현재 Process가 행사하는 UID

- Saved user ID

Process의 최초의 effective user ID

 

Effective User ID는 기본적으로는 Real UID와는 같다, 실행 파일의 setuid bit가 1인 경우 해당 파일 소유자의 UID가 effective UID가 된다.

 

Getting/Setting user IDs

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

uid_t getuid(void);
uid_t geteuid(void);

return: 얻으려고 하는 uid

uid와 euid를 반환 받는 getuid와 geteuid 함수이다.

 

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

int seteuid(uid_t uid);

Process의 EUID를 설정할 수 있는 seteuid 함수이다.

 

바로 실습으로 가보도록 하자.

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

int main(void){
	uid_t uid, euid;

	uid = getuid();
	euid = geteuid();
	printf("[Init] UId = %d, EUID = %d\n", (int)uid, (int)euid);

	seteuid(getuid());
	int cur_euid = (int)geteuid();
	printf("[seteuid(uid)] UID = %d, EUID = %d\n", (int)uid, (int)cur_euid);

	seteuid(euid);
	cur_euid = (int)geteuid();
	printf("[seteuid(euid)]UID = %d, EUID = %d\n", (int)uid, (int)cur_euid);

	return 0;
}

이렇게 프로세스 내에서의 userID 변동을 살펴 볼 수 있다.

 

User information

우선 /etc/passwd 라는 파일을 한 번 보도록 하자.

이렇게 사용자들의 정보가 쭉 나온다.

LoginID:PassWord:UID:GID:UserInfo:HomeDir의 정보로 출력이 된다.

 

당연히 시스템 콜에서도 사용자의 정보를 불러 올 수 있고, 사용자에 대한 구조체가 존재한다.

struct passwd {
	char	*pw_name;		/* user name */
	char	*pw_passwd;		/* encrypted password */
	uid_t	pw_uid;			/* user uid */
	gid_t	pw_gid;			/* user gid */
	char	*pw_gecos;		/* Honeywell login info */
	char	*pw_dir;		/* home directory */
	char	*pw_shell;		/* default shell */
};

이렇게 존재한다.

 

Reading the passwd file

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

struct passwd *getpwnam(const char *name);
struct passwd *getpwuid(uid_t uid);

passwd 파일을 가져오는 함수들이다.

-name

passwd file에서 정보를 읽어올 사용자의 이름

-uid

passwd file에서 정보를 읽어올 사용자의 uid

return: 해당 사용자에 대한 passwd structure가 저장된 pointer, -1(NULL)

 

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

int main(void){
	struct passwd * pw;

	pw = getpwuid(getuid());
	printf("UID : %d\n", (int)pw->pw_uid);
	printf("Login Name : %s\n", pw->pw_name);

	return 0;
}

uid로 사용자를 불러온 모습이다.

 

Reading the group file

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

struct group *getgrname(const char *name);
struct group *getgrgid(gid_t gid);

struct group *getgrent(void);
void setgrent(void);
void endgrent(void);

사용자 뿐만 아니라 그룹에 대한 정보도 가져올 수 있다.

위와 비슷하고 그룹이란 것만 변경이 되었기 때문에 바로 실습으로 가보도록 하겠다.

 

group의 구조체는 아래와 같다.

struct group {
	char	*gr_name;		/* [XBD] group name */
	char	*gr_passwd;		/* [???] group password */
	gid_t	gr_gid;			/* [XBD] group id */
	char	**gr_mem;		/* [XBD] group members */
};

 

그룹에 대한 정보를 읽어오는 프로그램을 만들어보자.

 

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

int main(void){
	struct group *grp;

	grp = getgrnam("root");
	printf("Group Name : %s\n", grp->gr_name);
	printf("GID : %d\n", (int)grp->gr_gid);


	return 0;
}

그룹의 이름을 이용해 정보를 가져왔다.

  • 시간 정보

리눅스의 시간은 1970년 1월 1일 0시 0분 0초를 기준으로 흘러가며 현재까지 경과한 시간을 초 단위로 저장한다.

 

Getting time

#include <sys/time.h>

time_t time(time_t *tloc);

-tloc

얻어올 초를 저장할 주소

return: 얻어온 초, -1(error)

 

하지만 이렇게 얻어온 시간은 흘러간 초로 표현이 되기 때문에 읽기가 매우 힘들다.

그래서 이 time_t를 우리가 보기 편한 시간으로 보여주는 함수가 있고 그 시간을 저장하는 구조체가 있다.

 

우선 해당 구조체부터 살펴보자면

struct tm {
	int	tm_sec;		/* seconds after the minute [0-60] */
	int	tm_min;		/* minutes after the hour [0-59] */
	int	tm_hour;	/* hours since midnight [0-23] */
	int	tm_mday;	/* day of the month [1-31] */
	int	tm_mon;		/* months since January [0-11] */
	int	tm_year;	/* years since 1900 */
	int	tm_wday;	/* days since Sunday [0-6] */
	int	tm_yday;	/* days since January 1 [0-365] */
	int	tm_isdst;	/* Daylight Savings Time flag */
};

이렇게 연부터 초들이 들어가게 되고

 

Seconds <-> tm struct

#include <time.h>

struct tm *gmtime(const time_t *timep);
struct tm *localtime(const time_t *timep);

time_t mktime(struct tm *tm);

-*timep

변환하려는 time_t 구조체

return: 변환된 tm 구조체

 

-tm

변환하려는 tm 구조체

return: 변환된 time_t 구조체

 

변환하여 출력해보자.

#include <time.h>
#include <stdio.h>

int main(void){
	struct tm *tm;
	time_t t;

	time(&t);
	printf("Time(sec) : %d\n", (int)t);

	tm = gmtime(&t);
	printf("GMTIME=Y:%d", tm->tm_year);
	printf("M:%d ", tm->tm_mon);
	printf("D:%d ", tm->tm_mday);
	printf("H:%d ", tm->tm_hour);
	printf("M:%d ", tm->tm_min);
	printf("S:%d\n", tm->tm_sec);

	tm = localtime(&t);
	printf("LOCALTIME=Y:%d", tm->tm_year);
	printf("M:%d ", tm->tm_mon);
	printf("D:%d ", tm->tm_mday);
	printf("H:%d ", tm->tm_hour);
	printf("M:%d ", tm->tm_min);
	printf("S:%d\n", tm->tm_sec);

	return 0;
}

이렇게 tm과 time_t 간에 변환이 된 모습이다.

728x90

시스템프로그래밍 4주차에는 리눅스의 파일에 관하여 배운다.

 

리눅스는 모두 파일로 저장이 되고 종류로는 Regular file, Special file, Directory, Symbolic link file... 등이 있다.

파일의 종류를 확인하고 싶으면 ls -l 명령어를 사용하면 된다.

정보 가장 앞에 나온 문자로 확인을 한다.

문자 파일의 종류
- 일반 파일
d 디렉토리
b 블록 장치 특수 파일
c 문자 장치 특수 파일
l 심볼릭 링크
  • Types of file

- Special file

장치와 데이터를 주고 받는 통로이다, 데이터 블록이 없으며 장치 번호를 inode에 저장한다.

단위에 따라 Character device file, Block device file로 나뉜다.

- Regular file

Text or binary data file이다.

- Directory(윈도우의 파일)

파일의 목록을 저장한 파일이다.

- Symbolic link file

이미 존재하는 파일이나 디렉토리에 접근 할 수 있는 새로운 이름이다.

 

  • File organization

파일을 구성하는 요소는 3가지이다.

- File name

파일의 이름으로 사용자가 파일에 접근할 때 사용한다.

- inode

파일에 대한 정보를 저장한다, 번호를 통해 관리/접근 한다.

- Data block

실제 데이터가 저장된 디스크의 공간이다.

inode는 ls -i로 확인할 수 있다.

 

2주차에서 보았던 file table을 다시 보자면

file table은 직접 해당 파일을 가리키는 것이 아니라

i-node를 가리키고 inode에서 해당 파일을 가리키는 것이다.

 

시스템 콜을 사용을 해서 i-node에 저장되어 있는 파일 정보를 읽어 올 수 있다.

 

File information

#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int stat(const char *pathname, struct stat *buf);
int fstat(int fd, struct stat *buf);

file descriptor를 사용하면 fstat를 사용한다.

- pathname(file path or file descriptor)

파일의 경로(파일의 이름을 포함한)

-buf

파일의 정보를 저장한 주소

return: 0(success), -1(error)

당연히 파일에 읽기 권한이 있어야 한다.

 

struct stat의 구조체를 살펴보면

struct stat {
	dev_t           st_dev;         /* [XSI] ID of device containing file */
	ino_t           st_ino;         /* [XSI] File serial number */
	mode_t          st_mode;        /* [XSI] Mode of file (see below) */
	nlink_t         st_nlink;       /* [XSI] Number of hard links */
	uid_t           st_uid;         /* [XSI] User ID of the file */
	gid_t           st_gid;         /* [XSI] Group ID of the file */
	dev_t           st_rdev;        /* [XSI] Device ID */
	time_t          st_atime;       /* [XSI] Time of last access */
	long            st_atimensec;   /* nsec of last access */
	time_t          st_mtime;       /* [XSI] Last data modification time */
	long            st_mtimensec;   /* last data modification nsec */
	time_t          st_ctime;       /* [XSI] Time of last status change */
	long            st_ctimensec;   /* nsec of last status change */
	off_t           st_size;        /* [XSI] file size, in bytes */
	blkcnt_t        st_blocks;      /* [XSI] blocks allocated for file */
	blksize_t       st_blksize;     /* [XSI] optimal blocksize for I/O */
	__uint32_t      st_flags;       /* user defined flags for file */
	__uint32_t      st_gen;         /* file generation number */
	__int32_t       st_lspare;      /* RESERVED: DO NOT USE! */
	__int64_t       st_qspare[2];   /* RESERVED: DO NOT USE! */
};

이렇게 작성이 되어 있다.

 

함수와 구조체를 이용하여 i-node의 정보를 가져오는 코드를 작성해보자.

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

int main(void){
    struct stat buf;

    stat("stat.c", &buf);

    printf("Inode = %d\n", (int)buf.st_ino);
    printf("Mode = %o\n", (unsigned int)buf.st_mode);
    printf("Nlink = %o\n", (unsigned int)buf.st_nlink);
    printf("UID = %d\n", (int)buf.st_uid);
    printf("GID = %d\n", (int)buf.st_gid);
    printf("SIZE = %d\n", (int)buf.st_size);
    printf("Atime = %ld\n", buf.st_atime);
    printf("Mtime = %ld\n", buf.st_mtime);
    printf("Ctime = %ld\n", buf.st_ctime);
    printf("Blksize = %d\n", (int)buf.st_blksize);
    printf("Blocks = %d\n", (int)buf.st_blocks);

    return 0;
}

이렇게 우리가 작성한 stat.c의 정보를 가져오는 코드이다.

실행을 해보면

i-node 번호부터 시작해서 저장된 정보가 출력되는 것을 볼 수 있다.

 

st_mode에 저장된 데이터를 이용하면 파일의 타입까지 체크할 수 있다.

상수명 상수값(16진수) 기능
S_IFMT 0XF000 st_mode 값에서 파일의 종류를 정의한 부분을 가져옴
S_IFIFO 0X1000 FIFO 파일
S_IFCHR 0X2000 문자 장치 특수 파일
S_IFDIR 0X4000 디렉토리
S_IFBLK 0X6000 블록 장치 특수 파일
S_IFREG 0X8000 일반 파일
S_IFLNK 0XA000 심볼릭 링크 파일
S_IFSOCK 0XC000 소켓 파일

임시로 파일을 만들고 해당 파일의 타입을 확인해보자.

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

int main(void){
    struct stat buf;
    int kind;

    stat("hello.txt", &buf);

    printf("Mode = %d (Hexa: %x)\n", (unsigned int)buf.st_mode, (unsigned int)buf.st_mode);

    kind = buf.st_mode & S_IFMT;

    printf("Kind = %x\n", kind);

    switch(kind){
        case S_IFIFO:
            printf("hello.txt : FIFO\n"); break;
        case S_IFDIR:
            printf("hello.txt: Directory\n"); break;
        case S_IFREG:
            printf("hello.txt: Regular File\n"); break;
    }

    return 0;
}

이렇게 and 연산에 의해 해당 파일의 모드가 출력되게 된다.

이렇게 and 연산 말고도 macro 함수를 사용할 수도 있기는 하다.

 

Checking permission

#include <unistd.h>

int access(const char *pathname, int mode);

-pathname

파일의 경로(파일 이름 포함)

-mode

R_OK, W_OK, X_OK, F_OK(파일의 존재를 확인)

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

extern int errno;

int main(void){
    int per;

    if(access("hi.txt", F_OK) == -1 && errno == ENOENT) printf("hi.txt: File not exist.\n");

    per = access("hello.txt", R_OK);
    if(per == 0) printf("linux.txt: Read permission is permitted.\n");
    else printf("hello.txt: Read permission is not permitted.\n");

    return 0;
}

return: 0(권한 있음), -1(error)

이렇게 파일의 권한이 있다고 보여지는 것을 볼 수 있다.

 

Changing permission

#include <sys/stat.h>

int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);

-pathname(file path or file descriptor)

파일의 경로

-mode

적용하려는 접근 권한

return: 0(success), -1(error)

 

해당 파일의 권한을 변경할 수 있다.

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

extern int errno;

int main(void){
    int per;

    if(access("hi.txt", F_OK) == -1 && errno == ENOENT) printf("hi.txt: File not exist.\n");

    per = access("hello.txt", R_OK);
    if(per == 0) printf("linux.txt: Read permission is permitted.\n");
    else printf("hello.txt: Read permission is not permitted.\n");

    return 0;
}

이렇게 변경되는 것을 볼 수 있다.

 

  • Directory

이번에는 파일이 아닌 directory를 알아보자.

file name들과 file name이 가리키는 inode 번호를 담고 있는 파일을 directory라고 한다.

 

Current directory를 기준으로 작성한 파일 경로를 상대 경로라고하고, Root directory를 기준으로 작성한 파일 경로를 root directory라고 한다.

 

우선 우리가 사용했던 mkdir, rmdir, rename을 만들어보자.

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

int mkdir (const char *pathname, mode_t mode);

-pathname

만들 directory의 이름이다.

-mode

만들 directory의 모드이다.

#include <unistd.h>

int rmdir(const char *pathname);

-pathname

삭제할 directory의 이름이다.

#include <stdio.h>

int rename(const char *oldpath, const char *newpath);

-oldpath

변경하고 싶은 directory의 이름이다.

-newpath

변경할 이름이다.

 

바로 실습을 해보면

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

#define PRINT_ERR_EXIT(_msg) {perror(_msg); exit(1);}

int main(void){
    if(mkdir("system", 0755) == -1) PRINT_ERR_EXIT("system");

    if(mkdir("programming", 0755) == -1) PRINT_ERR_EXIT("programming");

    if(rename("system", "systemProgramming") == -1) PRINT_ERR_EXIT("systemProgramming");

    if(rmdir("programming") == -1) PRINT_ERR_EXIT("programming");

    return 0;
}

mkdir로 system directory와 programming directory를 만들고 system directory의 이름을 systemProgramming으로 변경한다.

그리고 programming directory를 제거하는 프로그램이다.

실습을 해보면 systemProgramming directory가 생성된 것을 볼 수 있다.

 

getcwd

#include <unistd.h>

char *getcwd(char *buf, size_t size);

현재 directory 주소를 가져오는 함수이다.

-buf

경로를 저장할 buffer의 주소

- size

buffer의 사이즈이다.

return: Path가 저장된 buf의 주소, NULL(error)

 

chdir

#include <unistd.h>

int chdir(const char *path);

working directory를 변경하는 함수이다.

-path

변경할 directory의 주소

return: 0(success), -1(fail)

 

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

int main(void){
    char *cwd;
    char wd[BUFSIZ];

    cwd = getcwd(NULL, BUFSIZ);
    printf("1. Current Directory : %s\n", cwd);

    chdir("newDir");

    getcwd(wd, BUFSIZ);
    printf("2. Current Directory: %s\n", wd);

    return 0;
}

이렇게 실습을 진행해보면

첫번째에 현재 디렉토리가 출력되는 것을 볼 수 있고, 두번째에 변경된 디렉토리가 출력되는 것을 볼 수 있다.

Open/Close a directory file

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

DIR *opendir(const char *name);

-name

열려는 디렉토리의 이름

return: 열린 디렉토리의 DIR 포인터, NULL(error)

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

int closedir(DIR *dirp);

-dirp

닫으려는 DIR 포인터

return: 0(success), -1(fail)

 

Reading directory information

#include <dirent.h>

struct dirent *readdir(DIR *dirp);

-drip

읽으려는 directory file의 DIR 포인터

return: 현재 읽어온 항목의 dirent 구조체를 가리키는 포인터, NULL(더 이상 읽어올 항목이 없음)

 

여기서 dirent 구조체는

struct dirent {
	__uint64_t  d_ino;      /* file number of entry */ \
	__uint64_t  d_seekoff;  /* seek offset (optional, used by servers) */ \
	__uint16_t  d_reclen;   /* length of this record */ \
	__uint16_t  d_namlen;   /* length of string in d_name */ \
	__uint8_t   d_type;     /* file type, see below */ \
	char      d_name[__DARWIN_MAXPATHLEN]; /* entry name (up to MAXPATHLEN bytes) */ \
}

이렇게 구성이 되어 있다.

이 정보들을 하나씩 읽어오는 것이다.

 

위에서 만들었던 systemProgramming directory에 대충 파일을 하나 만든 후 실습을 해보자면

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

int main(void){
    DIR *dp;
    struct dirent *dent;

    if((dp = opendir("systemProgramming")) == NULL){
        perror("opendir: systemProgramming");
        exit(1);
    }

    while((dent = readdir(dp))){
        printf("Name : %s   ", dent -> d_name);
        printf("Inode : %d\n", (int)dent -> d_ino);
    }

    closedir(dp);

    return 0;
}

이렇게 systemProgramming directory에 들어있는 모든 파일들이 순서대로 출력되는 것을 볼 수 있다.

 

  • Link

이번에는 link에 대해 알아본다.

 

Hard link

먼저 하드 링크부터 알아본다.

해당 inode에 mapping 된 이름을 말한다.

동일한 file system 내에 여러 개의 hard link를 생성할 수 있고

링크 수 = hard link 수이며, 링크 수 = 0 은 파일 삭제를 의미한다.

 

Soft link

실제 파일의 경로명을 저장하는 파일이며, 동일한 inode를 사용하는 하드 링크와는 다르게 새로운 inode를 사용한다.

 

링크는 shell에서 ln을 사용해서도 만들 수 있다.

ln에 -s 명령어를 추가하면 soft link를 만든다.

 

우리는 이 명령어를 직접 만드는 실습을 해보자.

 

Making a link

#include <unistd.h>

int link(const char *oldpath, const char *newpath);
//Hard link 생성
#include <unistd.h>

int symlink(const char *target, const char *linkpath);
//soft link 생성

return: 0(success), -1(fail)

 

바로 하드 링크의 코드를 작성해보면

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

int main(void){
    struct stat buf;

    stat("hello.txt", &buf);
    printf("Before link count = %d\n", (int)buf.st_nlink);

    link("hello.txt", "hello.ln");
    
    stat("hello.txt", &buf);
    printf("After Link Count = %d\n", (int)buf.st_nlink);

    return 0;
}

새롭게 hello.ln의 파일이 생성되고 hello.txt의 링크 수가 증가하는 것을 볼 수 있다.

softlink는 매우 간단하다.

이렇게 간단하게 코드를 작성하고

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

int main(void){
    symlink("hello.txt", "hello.sym");

    return 0;
}

실행을 해보면

화살표로 링크를 나타내는 파일이 생긴 것을 볼 수 있다.

 

Removing a link

#include <unistd.h>

int unlink(const char *pathname);
#include <stdio.h>

int remove(const char *pathname);

링크를 제거하는 방법에는 이렇게 unlink와 remove 2가지 방법이 있다.

-pathname

삭제하려는 HardLink의 경로

return: 0(success), -1(error)

 

둘 다 File System에서 이름을 삭제하고 해당 파일이 마지막 Link인 경우, file을 삭제한다.

하지만 차이로는 remove는 directory에도 사용이 가능하지만, unlink는 불가능하다.

 

Get symbolic link file Info

#include <unistd.h>

ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);

Symbolic link에 저장된 데이터를 읽는 함수이다.

-pathname

읽을 파일의 경로

-buf

데이터를 저장할 buffer

-bufsiz

읽을 크기

return: 읽은 byte의 수, -1(error)

 

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#define PRINT_CNT_INODE {\
    printf("hello.txt : Link Count = %d\n", (int)buf.st_nlink);\
    printf("hello.txt: Inode = %d\n", (int)buf.st_ino);}

int main(void){
    struct stat buf;

    printf("1. stat : hello.txt ---\n");
    stat("hello.txt", &buf);
    PRINT_CNT_INODE;

    printf("2. stat : hello.sym ---\n");
    stat("hello.sym", &buf);
    PRINT_CNT_INODE;

    printf("3. lstat : hello.sym ---\n");
    lstat("hello.sym", &buf);
    PRINT_CNT_INODE;
}

이렇게 hello.sym가 가진 정보를 출력해보면 lstat로 가져온 정보만 다르다는 것을 볼 수 있다.

#include <limits.h>
#include <stdlib.h>

char *realpath(const char *path, char *resolved_path);

Symbolic link가 가리키는 파일의 절대 경로를 얻는다.

-path

symbolic link file의 경로

-resolved_path

결과 값을 저장할 buffer

return: 결과가 저장된 buffer의 포인터, NULL(error)

 

#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#define PRINT_ERR_EXIT(_msg) {perror(_msg); exit(1);}

int main(void){
    char buf[BUFSIZ];
    int n;

    n = readlink("hello.sym", buf, BUFSIZ);
    if(n == -1) PRINT_ERR_EXIT("readlink");

    buf[n] = '\0';
    printf("hello.sym : READLINK = %s\n", buf);

    realpath("hello.sym", buf);
    printf("hello.sym : READPATH = %s\n", buf);

    return 0;
}

이렇게 link로 가져온 파일의 원래 위치를 가져온다.

728x90

시스템 프로그래밍 3주차는 2주차의 내용에 연장으로 표준 입출력에 대해 공부한다.

 

  • Standard IO

Standard IO는 platform에 독립적인, user-buffering solution이다.

 

File pointer

File operation을 관리하는 구조체를 가리키는 포인터

내부적으로 file descriptor와 연동됨

 

Stream

프로그램과 file을 연결한 통로

 

파일 IO에 대한 일의 순서는

File open -> File access -> File close로

파일을 열고 사용을 한 후 파일을 닫는 순서이다.

 

  • File open & close

Opening a file/stream

#include <stdio.h>

FILE *fopen(const char *path, const char *mode);

-path(file path)

열려는 파일의 경로

-mode(file open mode)

파일 열기 모드

return: file pointer (NULL: error)

 

파일 열기 모드(mode)

  모드 파일이 존재하지 않을 때 파일이 존재 할 때
r 읽기 열기 실패 열기 성공
w 쓰기 파일이 생성 덮어 씀(기존 내용은 지워 짐)
a 덧붙이기 파일이 생성 기존 파일 뒤에 기록
r+ 읽기+ 모드 / 쓰기 모드로 전환 가능 읽기 <-> 쓰기 모드 전환 시,
반드시 fflush(), fseek(), fstepos(), rewind() 중 하나를 호출해야 함
w+ 쓰기+ 모드/  읽기 모드로 전환 가능
a+ 덧붙이기+ / 읽기 모드로 전환 가능
b 이진 파일 모드 이진 파일 형식으로 파일을 연다
읽기/쓰기 모드는 위의 지정자들로 지정

이진 파일은 사람이 읽을 수 있는 문자로 채워진 Ascii 파일과 다르게 이진 데이터가 직접 저장된 파일을 말한다.

 

Closing a files/streams

#include <stdio.h>

int fclose(FILE *stream);

-stream

닫으려는 stream

return: 0(-1: error)

 

열고 닫는 방법을 배웠으니 바로 한 번 해보도록 하자.

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

int main(void){
    FILE *fp;

    if ((fp = fopen("hello.txt", "w")) == NULL){
        perror("fopen: hello.txt");
        exit(1);
    }

    fclose(fp);

    return 0;
}

딱 파일만 열고 바로 닫는 예제이다.

실행을 해보면 w 모드이기 때문에 

없던 hello.txt 파일이 생성되는 것을 볼 수 있다.

 

  • File read & write

파일을 열고 닫는 방법을 공부했으니 이제 순서의 중간인 파일의 사용에 대해 공부해보자

 

Character-based

Chararcter-based reading

#include <stdio.h>

int fgetc(FILE *stream);
int getc(FILE *stream); //macro 형태로 구현되어 있어, 인자에서 계산은 X
int getchar(void); //getc(stdin), 표준입력에서 입력을 받음

-stream

File operation을 수행할 stream

return : 읽은 문자 (-1: error)

 

파일로부터 문자 하나씩 읽는 방법이다.

 

Character-based writing

#include <stdio.h>

int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c); //putc(c, stdout)

-stream

File operation을 수행할 stream

-c

쓰려는 문자

return: 기록한 문자(-1:error)

 

Characted-based로 실습을 해보자

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

int main(void){
    FILE *rfp, *wfp;
    int c;

    if((rfp = fopen("hello.txt", "r")) == NULL){
        perror("fopen: hello.txt");
        exit(1);
    }

    if((wfp = fopen("hello.out", "w")) == NULL){
        perror("fopen: hello.out");
        exit(1);
    }

    while((c = fgetc(rfp)) != EOF){ //하나씩 계속 읽어서 hello.out에 작성
        fputc(c, wfp);
    }

    fclose(rfp);
    fclose(wfp);

    return 0;
}

이렇게 파일을 작성하면 hello.txt에서 문자를 하나씩 읽어서 hello.out에 저장하게 된다.

String-based

String-based reading

#include <stdio.h>

char *gets(char *s); //get from stdin
char fgets(char *s, int n, FILE *stream);

-s

읽은 문자열을 저장할 buffer

-n

buffer의 크기

-stream

File operation을 수행할 stream

return: Buffer의 시작 주소(NULL: error)

 

String-based writing

#include <stdio.h>

int puts(char *s); //put to stdout
int fputs(char *s, FILE *stream);

-s

기록할 문자열을 저장한 buffer

-stream

File operation을 수행할 stream

return: 양수(음수: error)

 

문자열 단위로 실습을 해보자

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

int main(void){
    FILE *rfp, *wfp;
    char buf[BUFSIZ];
    printf("BUFSIZ = %d\n", BUFSIZ);

    if((rfp = fopen("hello.txt", "r")) == NULL){
        perror("fopen: hello.txt");
        exit(1);
    }

    if((wfp = fopen("hello.out", "a")) == NULL){
        perror("fopen: hello.txt");
        exit(1);
    }

    while(fgets(buf, BUFSIZ, rfp) != NULL) fputs(buf, wfp);

    fclose(rfp);
    fclose(wfp);

    return 0;
}

문자열 단위로 읽어와서 hello.out에 저장한다.

이렇게 문자열 단위로 잘 가져오는 것을 볼 수 있다.

 

Binary IO

#include <stdio.h>

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

-ptr

Pointer to buffer

-size

size of an item

-nmemb

number of items to read/write

-stream

File operation을 수행 할 stream

return: read/write 한 item의 수(EOF: 파일의 끝)

 

이진 파일을 작성하는 예제이다.

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

int main(void){
    char *fileName = "binary.bin";
    int data[5] = {10, 20, 30, 40, 50};
    FILE *fp;

    if(!(fp = fopen(fileName, "wb"))){
        fprintf(stderr, "Fail to open the file - %s\n", fileName);
        exit(1);
    }

    size_t i = fwrite(data, sizeof(int), 5, fp);
    printf("Success to write %d objects.\n", i);

    fclose(fp);
    return 0;
}

그러고 cat으로 파일을 확인해보면 이진파일이기 때문에 알 수 없는 내용이 출력된다.

해당 이진파일의 내용을 그대로 보고 싶다면

xxd binary.bin을 사용하면 된다.

이제 이진파일을 읽어보자

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

int main(void){
    int buf[5] = {0};
    FILE *fp = fopen("binary.bin", "rb");

    if(!fp){
        fprintf(stderr, "Fail to open the file - %s\n", "binary bin");
        exit(1);
    }

    size_t i = fread(buf, sizeof(int), 5, fp);
    printf("Success to read %d object\n", i);
    for(int i = 0; i < 5; i++) printf("%d ", buf[i]);

    fclose(fp);
    return 0;
}

이렇게 이진파일을 정수로 읽어오는 것을 볼 수 있다.

이진 파일은 사람이 바로 읽을 수 없지만 동일한 데이터를 저장하는 데 Ascii 파일보다 적은 공간을 요구한다는 장점이 있다.

 

Formatted IO

#include <stdio.h>

int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);

int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);

-format

입출력 형식

-stream

File operation을 수행 할 stream

return: 입출력 한 문자의 수(음수: error)

 

자세히 볼 필요도 없을만큼 많이 사용한 함수들이다.

바로 작성을 하는 예제이다.

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

typedef struct{
    int ID;
    char name[8];
    float score;
}Student;

int fileOpen(FILE **fp, char *fileName, char *mode){
    *fp = fopen(fileName, mode);
    if(!*fp){
        printf("Fail to open - %s\n", fileName);
        return -1;
    }
    return 0;
}

int main(void){
    Student info = {0};
    char *fileName = "StudentList.txt";
    FILE *fp = NULL;

    if(fileOpen(&fp, fileName, "a") < 0) exit(1);

    while(1){
        printf("Enter Id Name Score (Exit: -1): ");
        scanf("%d", &info.ID);
        if(info.ID < 0) break;
        scanf("%s %f", &info.name, &info.score); getchar();
        fprintf(fp, "%d %s %.1f\n", info.ID, info.name, info.score);
    }

    fclose(fp);

    return 0;
}

-1을 입력할 때까지 입력을 받고 StudentList.txt에 작성을 하는 예제이다.

이렇게 StudentList.txt를 만들어 그곳에 작성을 하는 것을 볼 수 있다.

 

이제 이 파일을 읽어보자

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

typedef struct{
    int ID;
    char name[8];
    float score;
}Student;

int fileOpen(FILE **fp, char *fileName, char *mode){
    *fp = fopen(fileName, mode);
    if(!*fp){
        printf("Fail to open - %s\n", fileName);
        return -1;
    }
    return 0;
}

int main(void){
    Student info = {0};
    char *fileName = "StudentList.txt";
    FILE *fp = NULL;
    
    if(fileOpen(&fp, fileName, "r") < 0) exit(1);

    int numStudent = 0;
    float sum = 0;
    while(!feof(fp)){
        fscanf(fp, "%d %s %f\n", &info.ID, &info.name, &info.score);
        sum += info.score;
        numStudent++;
    }

    printf("%d students, Average = %2.f\n", numStudent, sum / numStudent);

    fclose(fp);
}

해당 파일을 잘 읽는 것을 볼 수 있다.

 

Synchronizing with the disk

#include <stdio.h>

int fflush(FILE *stream);

-stream

File operation을 수행 할 stream

return: 0(-1:error)

 

stream에 있는 내용들을 바로 디스크에 작성하는 방법

 

  • File offset & File pointer

Handling file offset

#include <stdio.h>

int fseek(FILE *stream, long offset, int whence);
int ftell(FILE *stream);
void rewind(FILE *stream);
int fsetpos(FILE *stream, const fpost_t *pos);
int fgetpos(FILE *stream, fpost_t *pos);

-stream

File operation을 수행 할 stream

-offset

이동시킬 byte 수(양수, 음수 모두 가능)

-whence

기준 위치(SEEK_SET, SEEK_CUR, SEEK_END)

-pos

offset을 저장 할 fpost_t 주소

 

File Pointer <-> File Descriptor

#include <stdio.h>

FILE *fdopen(int fd, const char *mode);

-fd

file descriptor

-mode

파일 열기 모드

return: File pointer(NULL: error)

 

#include <stdio.h>

int fileno(FILE *stream);

-stream

변환 할 stream

return: File descriptor(-1: error)

 

서로 pointer와 descriptor간의 변환하는 함수이다.

728x90

2주차에는 파일 입출력에 대해 공부한다.

 

  • File

Unix/Linux에서의 file 타입은 이렇게 있다.

 

Regular file(일반 파일)

- text or binary data file

 

Directory

- 윈도우의 폴더이며 Unix/Linux에서는 directory도 하나의 파일로 본다

 

Special file(특수 파일)

- 파일 형태로 표현된 커널 내 객체

- 데이터 전송, 장치 접근 시 사용하는 파일

 

Basic commands for file

 

ls

현재 directory 내의 파일 목록 출력

주요 Option

-l : 상세 파일 정보 출력

-a: 모든 파일(숨겨진 파일 포함 목록 출력)

 

touch

빈 파일을 생성하거나 해당 파일이 있다면 time stamp를 변경해준다.

 

rm

파일 삭제

주요 Option

-r: directory 삭제

 

cat

파일 내용 출력

 

copy

파일 복사

주요 Option

-r: directory 복사

 

mv

파일을 이동하거나 이름을 변경

 

File access permission

Owner, Group, others 사용자에 따라

읽기(r), 쓰기(w), 실행(x) 권한을 가짐

3비트 3비트 3비트
소유자 접근 권한 그룹 접근 권한 기타 사용자 접근 권한
r w x r w x r w x
Bit Octal value Text value Corresponding permission
8 400 r - - - - - - - -  Owner may read
7 200 - w - - - - - - -  Owner may write
6 100 - - x - - - - - - Owner may execute
5 040 - - - r - - - - - Group may read
4 020 - - - - w - - - - Group may write
3 010 - - - - - x - - - Group may execute
2 004 - - - - - - r - - Everyone else may read
1 002 - - - - - - - w - Everyone else may write
0 001 - - - - - - - - x Everyone else may execute

권한은 이렇게 주어지며 권한은 변경할 수도 있다.

 

chmod

파일 권한 변경

가운데 권한을 비트연산으로 넣어준다.

 


  • Open and Close a file

Low-level file IO vs High-level file IO

Low-Level File IO(System call) High-Level File IO(Buffered IO)
System call을 이용해서 파일 입출력 수행 C Standard library를 사용해서 파일 입출력 수행
File descriptor 사용 File Pointer 사용
Byte 단위로 디스크에 입출력 버퍼(block) 단위로 디스크에 입출력
특수 파일에 대한 입출력 가능  

 

Opening files

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags [, mode_t mode]);

- pathname (file path)

열려는 파일의 경로 (파일 이름 포함)

- flags(file state flags)

파일을 여는 방법 설정

-mode (file access permission)

파일을 새로 생성할 때만 유효

return: file descriptor

 

아래는 flags이며 여러 플래그들을 조합 가능하다 (OR bit operation인 |을 사용)

자주 사용하는 flag들만 소개하겠다.

종류 기능
O_RDONLY 파일을 읽기 전용으로 연다
O_WRONLY 파일을 쓰기 전용으로 연다
O_RDWR 파일을 읽기와 쓰기가 가능하게 연다
O_CREAT 파일이
없으면 파일을 생성한다.

아래는 mode이다, flag와 마찬가지로 조합이 가능하며 파일 권한 설정 값을 사용한다.

플래그 모드 설명
S_IRWXU 0700 소유자 읽기/쓰기/실행 권한
S_IRUSR 0400 소유자 읽기 권한
S_IWUSR 0200 소유자 쓰기 권한
S_IXUSR 0100 소유자 실행 권한
S_IRWXG 0070 그룹 읽기/쓰기/실행 권한
S_IRGRP 0040 그룹 읽기 권한
S_IWGRP 0020 그룹 쓰기 권한
S_IXGRP 0010 그룹 실행 권한
S_IRWXO 0007 기타 사용자 읽기/쓰기/실행 권한
S_IROTH 0004 기타 사용자 읽기 권한
S_IWOTH 0002 기타 사용자 쓰기 권한
S_IXOTH 0001 기타 사용자 실행 권한

 

 

file descriptor

여기서 file descriptor는 열려 있는 파일을 구분하는 정수 값이다.

파일을 열 때 순차적으로 할당이 되며

Default fds

0: stdin

1: stdout

2: stderr

이기 때문에 보통 3번부터 할당이 된다.

 

File table

열린 파일을 관리 하는 표이다.

kernel이 process 별로 유지하며 열린 파일에 대한 각종 정보를 관리한다.

Access mode, file offset, pointer to files

 

 

Closing files

#include <unistd.h>

int close(int fd);

- fd(file descriptor)

닫으려는 file descriptor

return: 0(success), -1(error)

file descriptor를 끊어주는 역할을 한다.

 

그럼 open과 close를 이용하여 간단하게 파일을 열고 닫는 프로그램을 만들어보자

이렇게 작성을 하고 컴파일을 해본다면

이렇게 새로운 파일이 생성되는 것을 볼 수 있다.

 

Error handling for System call

System call은 실패 시 -1을 반환

Error code는 errno에 저장 되며

perror를 활용해 error message를 출력 할 수 있다.

#include <stdio.h>

void perror(const char *str);

 

Reading a file

#include <stdio.h>

ssize_t read(int fd, void *buf, size_t count);

-fd(file descriptor)

읽으려는 파일의 file descriptor

-buf(buffer)

읽은 내용을 저장할 buffer의 시작 주소

-count

읽을 byte의 수

return: 실제로 읽은 byte의 수 (0: 파일의 끝인 EOF에 도달, -1: 에러)

 

Writing to a file

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

-fd(file descriptor)

기록하려는 파일의 file descriptor

-buf(buffer)

기록할 내용이 저장된 buffer의 시작 주소

-count

기록할 byte의 수

return: 실제로 기록한 byte의 수 (-1: 에러)

 

File offset(File position)

File operation을 적용할 위치

파일의 시작점부터 현재 위치까지의 byte 수

 

Read/Write 수행시, 읽은/기록한 byte 수 만큼 순차적으로 이동

file offset을 이용해 해당 파일을 어디까지 읽었는지, 어디까지 작성했는지 기억한다.

 

그럼 한 번 Read와 Write까지 활용해서 파일을 작성해보자

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

int main(void){
	int rfd, wfd, n;
	char buf[10];

	rfd = open("hello.txt", O_RDONLY);
	if(rfd == -1){
		perror("Open hello.txt");
		exit(1);
	}

	wfd = open("hello.bak", O_CREAT | O_WRONLY | O_TRUNC, 0644);
	if(wfd == -1){
		perror("Open hello.bak");
		exit(1);
	}

	while((n = read(rfd, buf, 6)) > 0)
		if(write(wfd, buf, n) != n) perror("wrote");

	if(n == -1) perror("Read");
	
	close(rfd);
	close(wfd);

	return 0;
}

이렇게 read를 이용하여 offset을 따라 모두 읽고 write하는 것을 볼 수 있다.

 

우리는 당연히 파일을 처음부터 순차적으로 따라 읽으며 내려왔다.

하지만 파일의 원하는 Block에 직접 접근 하는 방법도 있다.

 

Moving the file offset

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

off_t lseek(int fd, off_t offset, int whence);

-fd(file descriptor)

대상 file descriptor

-offset

이동시킬 byte 수 (양수 or 음수)

-whence

기준 위치

return: 이동 후 file offset (-1: 에러)

 

whence에는

SEEK_SET: 파일의 시작

SEEK_CUR: 현재 위치

SEEK_END: 파일의 끝

 

이렇게 3가지 종류가 있다.

 

한 번 사용해보도록 하자

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

int main(void){
    int fd, n;
    off_t start, cur;
    char buf[256];

    fd = open("linux.txt", O_RDONLY);
    if(fd == -1){
        perror("Open linux.txt");
        exit(1);
    }

    start = lseek(fd, 0, SEEK_CUR);
    n = read(fd, buf, 255);
    buf[n] = '\0';
    printf("Offset start=%d, Read Str=%s, n=%d\n", (int)start, buf, n);
    cur = lseek(fd, 0, SEEK_CUR);
    printf("Offset cur=%d\n", (int)cur);

    start = lseek(fd, 6, SEEK_SET);
    n = read(fd, buf, 255);
    buf[n] = '\0';
    printf("Offset start=%d, Read Str=%s", (int)start, buf);

    close(fd);

    return 0;
}

실행을 시키면 이렇게 offset이 이동하는 것을 볼 수 있다.

 

Page cache & write-Back

Page ache

- In-memory store of recenlty accessd dat from an on-disk filesystem

- Disk 접근 시간 절약을 위해 kernel 내부적 기법

 

page write-back

- Page cache에 변경 된 내용을 disk에 반영하는 것

- 반영 시기는 kernel이 결정

 

Synchronizing with disks

#include <unistd.h>

int fsync(int fd);

-fd(file descriptor)

대상 file descriptor

return: 0(success), -1(error)

page write-back을 강제로 수행한다.

 

  • File descriptor

Duplicating FD

#include <unistd.h>

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

-oldfd(old file descriptor)

복사하려는 file descriptor

-newfd

새로운 fd 지정

dup()의 경우 할당 가능한 fd 중 가장 작은 값으로 할당

return: oldfd를 복사한 새로운 fd (-1: 에러)

 

한번 실습을 해보자

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

int main(void){
    int fd, fd1;

    fd = open("tmp.aaa", O_CREAT | O_WRONLY | O_TRUNC, 0644);
    if(fd == -1){
        perror("Create tmp.aaa");
        exit(1);
    }
    
    close(1);

    fd1 = dup(fd);

    printf("DUP FD = %d\n", fd1);
    printf("Standard Output Redirection\n");
    close(fd);

    return 0;
}

이거를 실행해보면 stdout이 tmp.aaa로 연결이 되어

터미널에 출력이 되지 않고 tmp.aaa에 저장이 되는 것을 볼 수 있다.

 

 

728x90

10일 정도 안에 마칠 계획이고

보통은 WSL Ubuntu를 사용하겠지만 MAC을 가지고 있기 때문에 MAC을 이용해서 시스템 프로그래밍을 정리할 예정이다.

 

  • 시스템 프로그래밍의 개요

Systems programming, or system programming, is the activity of programming computer system software. The primary distinguishing characteristic of systems programming when compared to application programming is that application programming aims to produce software which provides services to the user directly (e.g. word processor), whereas systems programming aims to produce software and software platforms which provide services to other software, are performance constrained, or both - from wikipedia

 

시스템 프로그래밍은 시스템 호출을 이용해서 프로그램을 작성하는 것이다.

시스템 호출은 운영체제에서 제공하는 서비스를 프로그램에서 이용할 수 있도록 지원하는 프로그래밍 인터페이스를 의미한다.

 

시스템 프로그램은 시스템을 구성하는 자원을 다루는 프로그램이며 응용프로그램에 서비스를 제공하는 프로그램으로 OS, firmware, compiler 등이 있다.

 

프로그래밍을 하면서 라이브러리는 많이 사용을 해보았을 것이다.

Library function과 System call을 비교해보면

System call: 시스템의 기능을 이용하는 통로, Kernel 내 모듈을 직접 호출

Library function: 미리 컴파일 된 함수, 자주 사용되는 기능을 독립적으로 구현 (내부적으로 System call을 활용)

 

미리 만들어진 Library를 사용할 수 있지만 굳이 System programming을 배워 사용하는 이유는

직접 System call을 사용한다면 더 많은 것들을 만들 수 있으며 중간 과정을 건너 뛰고 직접 사용한다면 더 높은 성능을 얻을 수 있기 때문이다.

 

이 시스템 프로그래밍 공부의 목표는

  • 시스템을 효율적으로 활용하는 능력 함양

Unix/Linux 기반 시스템 및 새로 만나는 시스템

 

  • 시스템 자원에 대한 이해 향상

- File / Directory

- System information

- Process

- Signal

- Memory

- Communication interface

 

그냥 프로그램을 만드는 것이 아니라 더 높은 성능의 프로그램을 만들 수 있도록 시스템에 대해 이해하는 것이다.

 

  • Unix/Linux architecture

시스템 프로그래밍 중 사용할 Linux/Unix에 대해 알아보자

현재, 다양한 시스템에서 가장 널리 사용하는 OS이다.

 

Unix/Linux architecture

 

- Kernel

OS의 핵심 기능을 모아놓은것

 

-System call

OS 기능 중 응용프로그램이 사용하도록 허락된 기능들의 집합

 

-Shell

사용자와 OS 사이의 인터페이스

사용자의 입력을 해석하여 커널의 함수 호출

 

  • Basic Unix/Linux commands

- Login/Logout

telnet: 유닉스 시스템에 접속

이거는 잘 안써서 사용 방법을 모르겠다....

logout: 유닉스 시스템에서 접속해제

ex) logout

exit: 유닉스 시스템에서 접속해제

ex) exit

 

-Process management

ps: 현재 실생중인 프로세스를 출력

ex) ps -u 사용자ID

kill: process 강제 종료

ex) kill -9 사용자ID

 

-File/Directory

pwd: 현재 디렉토리 경로 출력

ex) pwd

ls: 디렉토리 내용 출력

ex) ls -l

cd: 현재 디렉토리 변경

ex) cd /tmp

cp: 파일/디렉토리 복사

ex) cp a.txt b.txt

mv: 파일/디렉토리 이름변경과 이동

ex) mv a.txt b.txt

rm: 파일/디렉토리 삭제

ex) rm a.txt

mkdir: 디렉토리 생성

ex) mkdir dir1

rmdir: 빈 디렉토리 삭제

ex) rmdir dir2

cat: 파일 내용 출력

ex) cat a.txt

more: 파일 내용을 쪽 단위로 출력

ex) more a.txt

chmod: 파일 접근권한 변경

ex) chmod 777 a.txt

grep: 패턴 검색

ex)grep abcd a.txt

 

- Others

su: 사용자 계정 변경

ex) su -seungkyu

tar: 파일/디렉토리 묶기

ex) tar cvf a.tar *

whereis: 파일 위치 검색

 

Man Page

Unix/Linux 시스템 사용 설명서로 

Shell command, System Call, Standard library에 대한 설명을 제공

 

man [options][section] page

의 명령어를 입력하여 사용한다.

 

  • VI editor

Linux를 사용하다보면 VI editor를 굉장히 많이 사용할 것이다.

일단 배운 명령어들로 실습을 할 디렉토리를 만들고 들어가준다.

그럼 이 실습 디렉토리에서 vi helloLinux.c 라는 명령어를 입력하여 VI 에디터를 실행해보자

VI 에디터로 helloLinux.c라는 C 소스코드 파일을 작성하겠다는 명령어이다.

 

그러면

이런 창이 나타나게 된다.

윈도우에서의 메모장이라고 생각하면 된다.

 

VI Editor의 명령어들이다.

입력모드 전환: i, a, o, O

한글자 수정: r

단어수정: cw

붙이기: p

글자삭제: x

명령취소: u, U

복사: y

행삭제: d

저장하고 종료: wq!

저장하지 않고 종료: :q!

 

명령어들은 외우려고 하기 보다 쓰다보면 자연스럽게 익혀지기 때문에 많이 사용해보는 것이 좋다.

정리하는 단계이니 일일이 소개하지는 않고

 

바로 프로그램을 만들어보도록 하겠다.

이렇게 작성을 하고 이 코드를 컴파일해보자

gcc -o helloLinux helloLinux.c

gcc를 이용해서 컴파일을 한다.

이렇게 입력을 하면 컴파일이 되고

./helloLinux를 이용해 컴파일이 된 프로그램을 실행할 수 있다.

 

 

  • Makefile

GNU C compiler는 Unix/Linux에서 가장 기본이 되는 compiler이다.

위에서 대충 사용해봤겠지만 자세한 사용 방법은 아래와 같다.

 

gcc [options] filename

options:

-c: object file(.o)만 생성

-o: execution file name 지정(default는 a.out이고 사용자가 이름 지정가능)

이렇게 -c 옵션을 주면 .o 파일을 만들 수도 있다.

Makefile & Make

컴파일을 도와주는 Make에 대해 알아보자

Makefile은 compile 방법을 기술 하는 파일이다.(관련 파일 정보, compile 명령, 실행 파일명 등), 여러 파일로 구성된 프로젝트 설정과 비슷한 개념

 

Make은 주어진 Makefile에 따라 compile을 수행하고, 실행파일을 생성. 최초 컴파일 이후에는, 변경이 있는 파일만 컴파일 함

 

Makefile을 사용하면

$ gcc -c -o main.o main.c
$ gcc -c -o foo.o foo.c
$ gcc -c -o bar.o bar.c

$ gcc -o app.out main.o foo.o bar.o

이렇게 항상 하나하나 모두 직접 컴파일을 해줘야 했던 일을

 

이렇게 미리 Makefile에 작성하여

app.out: main.o foo.o bar.o
	gcc -o app.out main.o foo.o bar.o
    
main.o: foo.h bar.h main.c
	gcc -c -o main.o main.c
    
foo.o: foo.h foo.c
	gcc -c -o foo.o foo.c
    
bar.o: bar.h bar.c
	gcc -c -o bar.o bar.c

$ make 명령어로 한 번에 컴파일 할 수 있다.

이렇게 미리 작성을 해두면 오타날 위험이 적다는 장점도 있다.

그리고 make를 할 때마다 모든 파일들을 컴파일 하는 것이 아니라 수정된 파일들만 찾아서 컴파일을 해주게 된다.

 

Rule block

Makefile의 Rule block은

<Target>: <Dependencies>
	<Recipe>

Target: Build 대상 이름, 일반적으로 최종 결과 파일명 사용

Dependency: Build 대상이 의존하는 Target이나 파일 목록

Recipe: Build 대상을 생성하는 명령어

 

Implicit rules

자주 사용되는 규칙을 자동으로 처리해줌(Source를 Compile해서 Object 파일을 생성하는 규칙)

Target에 대한 dependency까지는 명시를 해주어야 한다.

 

app.out: main.o foo.o bar.o
	gcc -o app.out main.o foo.o bar.o
    
main.o: foo.h bar.h main.c
foo.o: foo.h foo.c
bar.o: bar.h bar.c

이렇게 아래부분을 더 짧게 바꿀 수 있다.

 

variables

사용하는 명령어들과 파일 명들을 변수로 치환해서 사용할 수 있다.

CC=gcc
CFLAGS=-g pWall
OBJS=main.o foo.o bar.o
TARGET=app.out

$(TARGET): $(OBJS)
	$(CC) -o $@ $(OBJS) // $@: current target name
    
main.o: foo.h bar.h main.c
foo.o: foo.h foo.c
bar.o: bar.h bar.c

 

Clean rule

Build로 생성된 파일들 삭제하는 규칙

 

make로 파일을 만든 후에 필요 없어지는 파일들이 생길 수 있다.

그럴 때도 make 파일에 clean을 작성 한 후 한번에 제거할 수 있다.

clean:
    rm -f *.o
    rm -f $(TARGET)

이렇게 작성을 해둔 후

$make clean 명령어를 입력하면 clean에 작성된 명령어들이 한 번에 실행이 된다.

 

CC=gcc # (built-in variable) compiling C programs
CFLAGS=-g pWall # (built-in variable) extra flags to give to CC
OBJS=main.o foo.o bar.o
TARGET=app.out

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) -o $@ $(OBJS) // $@: current target name
    
main.o: foo.h bar.h main.c
foo.o: foo.h foo.c
bar.o: bar.h bar.c

clean:
    rm -f *.o
    rm -f $(TARGET)

이렇게 배워본 makefile을 정리해 보았다.

+ Recent posts