반응형
1단계 - 저수준 입출력 루틴

1. 유닉스, 리눅스에서의 파일과 디렉토리
유닉스, 리눅스에서의 모든것들은 파일입니다. 일반 텍스트 파일, 장치, 포트, 디렉토리 등등
모든 것이 파일입니다. 모든것이 파일이라 이상할지 모르겟지만 파일은 운영체제, 장치에 대해 
일관된 인터페이스를 제공하므로써 프로그래밍 작업을 편리하게 하는(?) 장점이 있습니다.

디렉토리라고 하는것은 각 파일에 대한 링크를 가지고 있는 파일 입니다. 

2. 저수준 파일 액세스
2-1. write 시스템 콜
#include <unistd.h>
size_t write(int fildes, const void *buf, size_t nbytes);
write 시스템 호출은 fildes 와 관련된 파일에 buf를 nbytes만큼 씁니다. 쓰기가 성공하면 
쓰여진 바이트 수를 반환하고 실패하면 -1 를 반환하고 errno 변수를 설정합니다.
다음은 write 시스템 호출을 이용해 문자열을 출력하는 예제입니다.

<write.c>
#include <unistd.h>
#include <stdlib.h>

int main()
{
	if((write(1, "Hello, World!\n", 15)) == 15)
		write(1, "success\n", 9);
	exit(0);
}

$ gcc -o write write.c
$ ./write
Hello, World!
success

실행해 본 결과 Hello, World! 와 success 가 출력되었습니다. 첫번째 출력은 첫번째 write 
시스템 호출이 1번 스트림에 문자열을 쓰기때문에 출력되었고, 두번째 출력은 첫번째 출력이
성공 되어서 if 문으로 인해 write 문으로 1번 스트림에 문자열이 쓰여서 출력된 겁니다.

2-2. read 시스템 콜
#include <unistd.h>
size_t read(int fildes, void *buf, size_t nbytes);

read 시스템 호출은 fildes와 관련된 파일에서 nbytes 만큼 읽어 들여서 buf에 저장합니다.
성공하면 읽힌 바이트 수를 반환하고 실패하면 -1 를 반환합니다.
read.c 는 read 시스템 호출을 사용하여 사용자 입력을 받아 들여 출력합니다.

<read.c>
#include <unistd.h>
#include <stdlib.h>

int main()
{
	char buffer[128];
	int nread;

	nread = read(0, buffer, sizeof(128));
	if(nread != -1)
		printf("result: %s\n", buffer);
	exit(0);
}

$ gcc -o read read.c
$ ./read
Input
result: Input

실행하면 read 프로그램은 read 시스템콜로 0번 스트림에서 읽어 드린 문자열을 출력합니다.

2-3. open 시스템 호출
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int open(const char *path, int oflags);
int open(const char *path, int oflags, mode_t mode);

open 시스템 호출은 path에 지정된 파일을 oflags모드로 열어서 그 기술자를 반환합니다.
선택적인 인자로 mode 인자를 줄수 있는데 이 모드는 파일을 생성할때의 퍼미션을 지정하는 겁니다.

oflags 모드의 종류로는

O_RDONLY - 읽기 전용
O_WRONLY - 쓰기 전용
O_RDWR - 읽기/쓰기 전용

또, 조합될수 잇는 모드로는

O_APPEND - 파일의 끝에서 부터 쓴다.
O_TRUNC - 기존의 내용을 제거하고, 파일의 길이를 0으로 설정한다.
O_CREAT - 파일이 없다면 mode 인자에 주어진 상태로 파일을 생성한다.
O_EXCL - 파일을 생성할때 파일이 이미 존재 한다면 에러를 반환하고 open 을 실패하게 한다.

등이 있습니다.

또한 O_CREAT 플래그를 사용하여 파일을 생성할때의 퍼미션을 지정하는 mode 인자에 조합될수
있는 인자들은

S_IRUSR - 소유자 읽기 허용
S_IWUSR - 소유자 쓰기 허용
S_IXUSR - 소유자 실행 허용
S_IRGRP - 그룹 읽기 허용
S_IWGRP - 그룹 쓰기 허용
S_IXGRP - 그룹 실행 허용
S_IROTH - 기타 읽기 허용
S_IWOTH - 기타 쓰기 허용
S_IXOTH - 기타 실행 허용

이 있습니다.

만약에

filedes = open("file", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IXUSR | \
			S_IRGRP | S_IXGRP | S_IXOTH);

이렇게 했다면 file 이라는 이름의 파일을 쓰기 전용으로 읽고, 파일이 없으면 751 퍼미션으로
파일을 생성하여 열어서 그 기술자를 filedes 에 반환합니다.

파일을 생성할때 영향을 줄수 있는 값으로 umask가 있는데 이 umask 는 파일을 생성할때 
금지할 퍼미션을 가지고 있습니다. O_CREAT 모드로 파일을 생성할때 umask 의 영향을 받지 
않으려면 umask 프로그램이나 umask 시스템 호출을 이용하여 영향을 받지 않을수 있습니다.

2-4. close 시스템 호출
#include <unistd.h>

int close(int fildes);

close 시스템 호출은 fildes 와 관련된 파일을 닫습니다. 에러가 발생하면 -1 을 반환합니다.

아래의 file_copy 프로그램은 저수준 파일 제어 시스템 호출로 파일을 복사하는 프로그램 입니다.

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

int main(int argc, char *argv[])
{
	char buffer[1024];
	int infile, outfile;
	int size;

	if(argc != 3)
	{
		fprintf(stderr, "Usage: %s infile outfile", argv[0]);
		exit(1);
	}

	if((infile = open(argv[1], O_RDONLY)) == -1)
	{
		perror("open");
		exit(1);
	}

	if((outfile = open(argv[2], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)) == -1)
	{
		perror("open");
		exit(1);
	}

	memset(buffer, 0, sizeof(buffer));

	while(read(infile, buffer, sizeof(buffer)))
	{
		write(outfile, buffer, strlen(buffer));
	}

	close(infile);
	close(outfile);

	return(0);
}

$ gcc -o file_copy file_copy.c
$ echo "hello world" > infile
$ ./file_copy infile outfile
$ cat outfile
hello world
$

file_copy 프로그램은 입력파일로 지정된 파일을 읽기 전용으로 열고 내용을 read 함수로 모두
읽어서 출력파일로 지정된 파일에 write 함수로 씁니다. 출력파일이 없다면, 600 퍼미션으로 
생성 합니다.

2-5. lseek 시스템 호출
#include <unistd.h>
#include <sys/types.h>

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

lseek 시스템 호출은 fildes와 관련된 파일에서 파일 위치 포인터를 whence 기준에서 offset 만큼
파일 위치 포인터를 이동 시킵니다. 

whence의 종류는 다음과 같습니다.

SEEK_SET - 파일의 처음
SEEK_CUR - 파일의 현재 위치
SEEK_END - 파일의 마지막

lseek는 성공하면 위치를 이동하고, 실패하면 이동한 후의 파일 포인터의 위치를 반환합니다.

만약
lseek(infile, 10, SEEK_CUR);
라고 하면 infile 의 처음에서 10 만큼 위치 포인터를 이동 시킨다.

2-6. fstat, stat, lstat
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

int fstat(int fildes, struct stat *buf);
int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct stat *buf);

셋다 파일에 관련된 정보를 보여주는 함수지만 차이점이 있습니다. fstat는 열려있는 파일기술자와
관련된 파일에 대한 정보를 반환하고, stat와 lstat는 path에 지정된 파일에 대한 정보를 반환하지만
lstat는 링크 파일이 가르키는 파일의 정보가 아니라 링크파일 자체의 정보를 반환합니다.

구조체 stat는 위에 셋 시스템 호출에서 반환된 정보를 저장하는 구조체 변수의 타입입니다.
stat의 구조는 다음을 포함합니다.

st_mode - 파일 허용 권한과 파일 형태 정보
st_ino - 파일과 관련된 inode
st_dev - 파일이 존재하는 장치
st_uid - 파일 소유자의 사용자 식별자
st_gid - 파일 소유자의 그룹 식별자
st_atime - 마지막 액세스 시간
st_ctime - 허용권한, 소유자, 그룹, 내용에 대한 마지막 변경시간
st_mtime - 내용에 대한 마지막 변경시간
st_nlink - 파일에 대한 하드 링크의 수

st_mode에 들어가는 파일 허용 권한 플래그는 open 에서 보았던 플래그와 같고 파일형태 플래그는
다음을 포함합니다.

S_IFBLK - 블록 장치이다.
S_IFDIR - 디렉토리 이다.
S_IFCHR - 문자 장치 이다.
S_IFIFO - FIFO 이다.
S_IFREG - 일반적인 파일이다.
S_IFLNK - 기호 링크이다.

그 밖의 모드 플래그는 다음을 포함합니다.

S_ISUID - 실행 시에 setUID를 가진다.
S_ISGID - 실행 시에 setGID를 가진다.

st_mode 플래그를 변환하기 위한 마스크는 다음을 포함합니다.

S_IFMT - 파일 형태
S_IRWXU - 사용자 읽기/쓰기/실행 권한
S_IRWXG - 그룹 읽기/쓰기/실행 권한
S_IRWXO - 기타 읽기/쓰기/실행 권한

파일 형태를 테스트 하는 플래그는 다음을 포함합니다.

S_ISBLK - 블록 파일 테스트
S_ISCHR - 문자 파일 테스트
S_ISDIR - 디렉토리 테스트
S_ISFIFO - FIFO 테스트
S_ISREG - 일반적인 파일 테스트
S_ISLNK - 기호 링크 테스트

위에서 나열한 플래그 들을 사용하는 예를 봅시다.

struct stat buf;
mode_t modes;

stat("myfile", &buf);
modes = buf.st_mode;

if(S_ISDIR(modes))
{
	printf("myfile은 디렉토리 입니다.\n");
}
else if(S_ISREG(modes))
{
	printf("myfile은 일반적인 파일입니다.\n");
}
else
{
	printf("myfile은 장치나 FIFO나 기호링크 파일 입니다.\n);
}

S_IS* 테스트 플래그를 이용하여 myfile가 무슨 타입인지 테스트합니다.


2-7. dup와 dup2
#include <unistd.h>

int dup(int fildes);
int dup2(int fildes, int fildes2);

dup 시스템 호출은 지정된 파일 기술자를 사용되지 않은 가장 작은 수의 기술자로 복사합니다.
dup 시스템 호출과 비슷한 dup2는 파일기술자가 복사될 다른 파일 기술자를 닫아 주고 기술자를
그 기술자로 복사합니다.

예를 들면 copydes = dup2(4, filedes); 처럼 하면 파일기술자 4가 닫혀지고 file가 4로 복사되어
4 기술자가 copydes 로 반환됩니다.

__eof__
 
/***********************************************************************************************/
2단계 - 표준 입출력 라이브러리

▶ fopen
#include <stdio.h>

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

fopen함수는 파일을 열고 기술자를 반환합니다. filename 인자는 파일 이름을 지정해 주는것이고
mode는 파일을 여는 방법을 지정 합니다. mode 의 종류는 다음과 같습니다.

"r" 또는 "rb" - 읽기 전용으로 연다.
"w" 또는 "wb" - 쓰기전용으로 열고, 파일이 있을때는 파일을 초기화(?) 시키고 파일이 없을때는
                새로 만든다.
"a" 또는 "ab" - 쓰기전용으로 여는데, 파일의 끝에서 부터 쓰게 한다.
"r+" 또는 "rb+" 또는 "r+b" - 읽기와 쓰기 상태로 연다.
"w+" 또는 "wb+" 또는 "r+b" - 갱신 상태(읽기와 쓰기)로 열고, 파일을 초기화 한다.
"a+" 또는 "ab+" 또는 "a+b" - 갱신상태로 열고, 파일의 끝에서 부터 쓰게한다.

fopen함수 실행이 성공하면 파일기술자를 반환하고 실패하면 NULL을 반환하고 errno변수를 변경합니다.

▶ fread
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);

fread 함수는 stream 파일 기술자에서 size * nitems 크기의 데이터를 읽어와 ptr이 가르키는 
변수에 저장합니다.

▶ fwrite
#include <stdio.h>
size_t fwrite(void *ptr, size_t size, size_t nitems, FILE *stream);

fwrite 함수는 fread 함수와 형식이 같지만, ptr이 가르키는 변수의 데이터를 읽어와 size * item
만큼 stream 기술자에 출력합니다.

▶ fclose
#include <stdio.h>
size_t fclose(FILE *stream);

stream 파일 기술자를 닫습니다.

▶ fflush
#include <stdio.h>

int fflush(FILE *stream);

stream 와 관계된 데이터를 즉시 쓰게 합니다.

▶ fseek
#include <stdio.h>

int fseek(FILE *stream, long int offset, int whence);

fseek 함수는 stream의 위치 포인터를 whence를 기준으로 offset만큼 이동하게 합니다.

▶ fgetc, getc, getchar
#include <stdio.h>

int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar();

fgetc와 getc는 지정된 스트림에서 한문자를 읽어와 반환하고, getchar는 키보드에서 한 문자를
읽어 반환합니다. fgetc와 get는 실패하면 EOF를 반환합니다.

▶ fputc, putc, putchar
#include <stdio.h>

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

fputc와 putc는 지정한 스트림에 c 문자를 출력합니다. 실패하면 EOF를 반환합니다.
putchar는 화면에 한 문자를 출력합니다.

▶ fgets, gets
#include <stdio.h>

char *fgets(char *s, int n, FILE *stream);
char *gets(char *s);

fgets함수는 stream 기술자에서 n - 1만큼 데이터를 읽어와 s에 지정된 변수에 저장합니다.
실패하면 EOF를 반환합니다. gets함수는 키보드에서 데이터를 읽어 s에 지정된 변수에 저장합니다.

▶ printf, fprintf, sprintf
#include <stdio.h>

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

printf계열 함수들은 형식화된 출력을 할때 사용합니다. printf는 format에 지정된 포맷스트링과
변환문자에 대응하는 인자들을 받아 변환한후 화면에 출력할때 사용하고, sprintf는 이 출력을
s가 가르키는 문자열 변수에 쓰기를 합니다. fprintf는 출력을 stream 스트림에 합니다.

추가적인 인자를 변환하는데 쓰이는 변환지정자는 다음과 같습니다.

%d, %i - 10진수로 정수를 출력한다.
%o, %x - 8진수와 16진수로 정수를 출력한다.
%c - 문자를 출력한다.
%s - 문자열을 출력한다.
%f - 부동 소수형 값을 출력한다.
%e - 고정 형식으로 배정도값을 출력한다.
%g - 일반 형식으로 배정도값을 출력한다.

d앞에는 long int를 가르키는 l이나, short int를 가르키는 h가 붙을수 있습니다.

또, 필드 지정자라는 것이 있는데 출력공간의 폭을 설정할수 있습니다. 필드 지정자는 %문자 다음에
숫자로 주어집니다. 왼쪽을 기준으로 출력할려면 숫자 앞에 - 를 붙입니다.

출력을 성공적으로 수행했다면 출력한 문자들의 갯수를 반환하고 실패했다면 음수 값을 반환하고
errno를 설정합니다.

▶ scanf, fscanf, sscanf
#include <stdio.h>

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

scanf계열 함수는 형식화된 입력을 할때 사용합니다. printf함수와 마찬가지로 포맷스트링을 인자로
받습니다. scanf는 키보드에서 입력을 받아 format의 변환문자에 대응되는 추가적인 인자에, 입력
받은것을 각각 저장해 놓습니다. fscanf는 이 입력을 stream 스트림에서 받고, sscanf는 s문자열 에서
입력을 받습니다.

유용한 포맷스트링에 %[] 와 %%가 있는데, %[]는 괄호 안에 있는 숫자 만큼만 받아서 인자에 저장하고
%% 는 %를 입력받습니다.

▶ 그밖의 스트림 함수들

fgetpos - 파일 스트림에서 현재 위치를 구한다.
fsetpos - 파일 스트림에서 현재 위치를 설정한다.
ftell - 스트림에서 현재 파일 오프셋을 반환한다.
rewind - 스트림에서 파일 위치를 재설정 한다.
freopen - 파일 스트림을 재사용한다.
setvbuf - 스트림을 위한 버퍼링 구성을 설정한다.
remove - path파라미터가 디렉토리가 아니라면 unlink와 같고, 디렉토리이면 rmdir과 같다.

▶ 스트림 에러 관련 함수들
#include <stdio.h>

int ferror(FILE *stream);
int feof(FILE *stream);
void clearerr(FILE *stream);

ferror은 스트림의 에러지시자를 테스트하고, 이것이 설정되면 0이 아닌 값을 반환하고,
그렇지 않으면 0을 반환한다.
feof는 스트림이 끝인지 알아보고 끝이 아니라면 0이 아닌 정수값을 반환하고 끝이라면 0을 반환한다.
clearerr은 stream이 가리키는 스트림의 파일 마지막 지시자와 에러지시자를 지운다.

▶ fileno, fdopen
#include <stdio.h>

int fileno(FILE *stream);
FILE *fdopen(int fildes, const char *mode);

fileno함수는 stream이 어떤 파일 기술자를 사용하는지 알아보고 그 기술자를 반환한다.
fdopen은 fildes 기술자를 기반으로 새로운 스트림을 만듭니다.


__eof__
/***********************************************************************************************/
3단계 - 디렉토리, 화일시스템 및 특수화일

+ 파일과 디렉토리 관리
▶ chmod
#include <sys/stat.h>

int chmod(const char *path, mode_t mode);

chmod 는 path에 지정된 파일의 퍼미션을 mode와 같이 바꾼다. 여기서 mode는 OR연산으로 각각의
권한과 조합되고, open()의 mode 인자와 같은 플래그를 갖는다.

▶ chown
#include <unistd.h>

int chown(const char *path, uid_t owner, gid_t group);

chown 는 path에 지정된 파일의 소유자와 소유 그룹을 owner인자에 지정된 uid와 group에 지정된
gid로 바꾼다.

▶ unlink, link, symlink
#include <unistd.h>

int unlink(const char *path);
int link(const char *path1, const char *path2);
int symlink(const char *path1, const char *path2);

unlink 시스템 호출은 path에 지정된 파일의 링크를 끊어버려 파일을 삭제 한다.
link 시스템 호출은 path1에 지정된 파일에 path2에 지정된 파일이름으로 하드링크를 건다.
symlink 시스템 호출은 path1에 지정된 파일에 path2에 지정된 파일 이름으로 심볼릭 링크를 건다.

▶ mkdir, rmdir
#include <sys/stat.h>

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

mkdir 시스템 호출은 path에 지정된 이름과 mode 퍼미션으로 디렉토리를 생성한다.

#include <unistd.h>

int rmdir(const char *path);

rmdir 시스템 호출은 path에 지정된 이름을 가지는 비어있는 디렉토리를 지운다.

▶ chdir, getcwd
#include <unistd.h>

[bang1575@RealSkulls system]$ cat system3.txt 
3주차 - 디렉토리, 화일시스템 및 특수화일

+ 파일과 디렉토리 관리
▶ chmod
#include <sys/stat.h>


int chmod(const char *path, mode_t mode);

chmod 는 path에 지정된 파일의 퍼미션을 mode와 같이 바꾼다. 여기서 mode는 OR연산으로 각각의
권한과 조합되고, open()의 mode 인자와 같은 플래그를 갖는다.

▶ chown
#include <unistd.h>

int chown(const char *path, uid_t owner, gid_t group);

chown 는 path에 지정된 파일의 소유자와 소유 그룹을 owner인자에 지정된 uid와 group에 지정된 
gid로 바꾼다.

▶ unlink, link, symlink
#include <unistd.h>

int unlink(const char *path);
int link(const char *path1, const char *path2);
int symlink(const char *path1, const char *path2);

unlink 시스템 호출은 path에 지정된 파일의 링크를 끊어버려 파일을 삭제 한다.
link 시스템 호출은 path1에 지정된 파일에 path2에 지정된 파일이름으로 하드링크를 건다.
symlink 시스템 호출은 path1에 지정된 파일에 path2에 지정된 파일 이름으로 심볼릭 링크를 건다.

▶ mkdir, rmdir
#include <sys/stat.h>

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

mkdir 시스템 호출은 path에 지정된 이름과 mode 퍼미션으로 디렉토리를 생성한다.

#include <unistd.h>

int rmdir(const char *path);

rmdir 시스템 호출은 path에 지정된 이름을 가지는 비어있는 디렉토리를 지운다.

▶ chdir, getcwd
#include <unistd.h>

int chdir(const char *path);
char *getcwd(char *buf, size_t size);

chdir는 path에 지정된 디렉토리로 현재 디렉토리를 이동한다.
getcwd는 buf에 지정된 변수에 size만큼 현재 디렉토리 이름을 저장한다.


+ 디렉토리
▶ opendir
#include <sys/types.h>
#include <dirent.h>

DIR *opendir(const char *name);

opendir은 name에 지정된 디렉토리를 열어서 디렉토리 스트림을 생성하고 DIR구조체에 대한 
포인터를 반환하고 함수실행을 실패하면 널 포인터를 반환한다.

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

struct dirent *readdir(DIR *dirp);

readdir은 dirp에 지정된 DIR 구조체를 참조하여 다음 디렉토리 항목의 정보(dirent구조체)에
대한 포인터를 반환한다.

dirent구조체의 멤버는 다음을 포함한다.

ino_t d_ino - 파일의 inode
char d_name[] - 파일의 이름

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

long int telldir(DIR *dirp);

telldir 함수는 dirp 디렉토리 스트림이 현재 가르키는 디렉토리 항목 위치를 반환한다.

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

void seekdir(DIR *dirp, long int loc);

seekdir 함수는 dirp 디렉토리 스트림에서 디렉토리 항목 포인터를 설정한다. loc 에 지정된
수만큼 설정한다.

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

int closedir(DIR *dirp);

closedir 함수는 dirp디렉토리 스트림을 닫는다.

▶ 예제
다음은 인자로 주어진 디렉토리의 파일 목록을 현재 디렉토리 위치와 함께 출력한다.
<printdir.c>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>

char dirname[256], buffer[256];

void printdir(char *dir)
{
        DIR *dp;
        struct dirent *dir_info;
        struct stat dir_stat;

        if((dp = opendir(dir)) == NULL)
        {
                perror("opendir");
                return(1);
        }

        chdir(dir);

        while((dir_info = readdir(dp)) != NULL)
        {
                lstat(dir_info->d_name, &dir_stat);

                if(S_ISDIR(dir_stat.st_mode))
                {
                        if(strcmp(".", dir_info->d_name) == 0 || strcmp("..", dir_info->d_name) == 0)
                                continue;
                        getcwd(dirname, sizeof(dirname));

                        if(!strncmp(dirname, "/", 1))
                                printf("%s/%s/\n", dirname, dir_info->d_name);
                        else
                                printf("/%s/%s/\n", dirname, dir_info->d_name);
  
                        printdir(dir_info->d_name);
                }
                else
                {
                        getcwd(dirname, sizeof(dirname));
                        printf("%s/%s\n", dirname, dir_info->d_name);
                }              
        }

        chdir("..");

        closedir(dp);
}

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

        printdir(argv[1]);

        return(0);
}

이 예제는 실용성은 없어 보이지만-_- 파일/디렉토리 관련 함수를 설명하기에는 충분한?같다.


+ fcntl
#include <fcntl.h>

int fcntl(int fildes, int cmd);
int fcntl(int fildes, int cmd, long arg);

fcntl 함수는 fildes 기술자를 cmd에 지정된 값에 따라 다른 제어를 한다. cmd에 따라 선택적인 인자
arg를 갖는다. fcntl함수에 대한 자세한 사항은 man페이지를 참조하자.

__eof__
/***********************************************************************************************/
4단계 - 프로세스

1. 프로세스란
프로세스는 쉽게, "현재 실행중인 프로그램" 으로 볼수 있다. 유닉스나 리눅스와 윈도우 같은
운영체제에서는 멀티태스킹 기능이 지원되어 동시에 많은 프로그램을 실행시킬수 있는데, 이는
우리의 눈에는 동시에 실행되는 것처럼 보이지만, 실제로는 각각의 프로세스의 우선순위에 따라
CPU 를 점유하며 프로세스들 하나하나씩 실행된다. 그 CPU를 점유하는 시간이 매우 짧기 때문에
우리눈에는 마치 동시에 실행되는것처럼 보이는 것이다.

2. 프로세스의 구조와 ps 명령
각각의 프로세스는 실행되면서, 프로세스를 구분할때 쓰이는 PID와 소스코드와 변수와 라이브러리 
또 file 식별자 등을 갖게된다. 라이브러리에는 공유될수 있는 라이브러리인 공유라이브러리가 
있는데, 공유 라이브러리는 메모리에 하나의 함수 사본만을 갖게 되므로 라이브러리 코드를 갖지
않으므로 크기를 줄일수 있다.

프로세스를 볼때는 ps 명령을 사용할수 있다. ps만을 실행하면 자신이 실행하고 있는 프로세스의
목록이 뜨게되고, -ax나 -aux옵션과 함께 실행하면 루트나 다른사람들이 실행하고 있는 프로세스와
자세한 정보를 볼수있다.

또한 nice 명령을 실행하여 프로세스의 우선순위를 변경할수 있다. nice와 함께 프로그램을 
실행하면 기준값인 10을 갖게 되고 renice를 실행하면 값을 변경할수 있다. ps 명령의 옵션을 -l
나 -f로 해서 실행하면 NI열을 통해 기준값을 볼수있다.

3. 프로세스 시작, 생성, 기다리기
3-1. 명령 실행하기
쉘에서 명령어을 실행하기 위해서는 system 함수를 사용할수 있다.

#include <stdlib.h>

int system(const char *string);

system 함수는 string에 전달된 명령을 쉘로 전달하여 실행하게 한다. 만약 

        system("ls -al");

를 실행한다면 현재 디렉토리의 모든 파일 목록이 출력되고 프로그램 실행이 제개될것이다.
system은 쉘을 사용하기 때문에 와일드 카드 확장이 가능하고 & 로 명령의 백그라운드 실행이
가능하다. 하지만 & 로 명령을 실행한다면 쉘이 명령어 실행을 끝내면 바로 리턴하기 때문에
실행된 명령어가 출력을 한다면 뒤죽박죽이 될수 있으므로 주의하자.

3-2. 명령 실행하기2
C에서 프로그램을 시작하기 위해서 사용되는 또다른 함수인 exec계열 함수들이 있다. 이 함수들은
system함수와 비슷하지만 인자를 넘겨줄수 있고, 프로세스를 실행하려는 프로세스로 대체한다.
exec계열의 함수들은 여러가지가 있는데 다음 함수 원형을 보자.

#include <unistd.h>

char **environ;

int execl(const char *path, const char *arg0, ..., (char *)0);
int execlp(const char *file, const char *arg0, ..., (char *)0);
int execle(const char *path, const char *arg0, ..., (char *)0, const char *envp[]);
int execv(const char *path, const char *argv[]);
int execvp(const char *file, const char *argv[]);
int execve(const char *path, const char *argv[], const char *envp[]);

execl, execlp, execle는 널포인터로 끝나는 변칙적인 수의 인수를 받아 들이는데, execl는
path에 전달된 프로그램을 변칙적인 수의 인수와 함께 실행하여 현재 프로세스와 대체 하고,
execlp는 execl과 같지만 PATH 환경 변수를 검색한다. 실행파일에 대한 절대경로가 없을때 
실행하려 할때 필요 할것이다. 또, execle는 대체될 새로운 프로세스의 환경을 지정한 스트링의
배열을 인자로 받는다.

execv, execvp, execve는 execl* 과 비슷하게 동작하지만 선택적인 인자를 스트링의 배열로 받는다.

3-3. 프로세스 생성(복제)
프로세스를 생성하기 위해서는 fork() 를 호출할수 있다.

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

pid_t fork(void);

fork 는 현재의 프로세스를 복사하여 자식프로세스를 생성하고 부모프로세스에게는 자식프로세스의 
pid를 자식 프로세스에게는 0을 반환한다. 프로세스를 생성하기 위해서는 fork 로 생성된 자식 
프로세스에서 exec 계열 함수를 호출하면 될것이다.

복사되어 생성된 자식 프로세스는 fork() 를 실행한 이후의 코드부터 실행된다.

부모와 자식은 fork로 부터 반환된 pid로 구별로 구별될수 있을것이다.

3-4. 프로세스 기다리기
fork로 자식과 부모관계를 형성 하고 난후, 부모가 먼저 종료되고 자식이 출력을 한다면 출력
화면이 복잡해지고 엉망진창이 될것이다. 이런 경우를 대비하여 부모프로세스를 자식프로세스가
종료될때 까지 기다리게 할수 있다. 자식프로세스가 종료될때까지 기다리게 할때는 wait를 사용해야
한다.

#include <sys/type.h>
#inlcude <sys/wait.h>

pid_t wait(int *stat_loc);

wait 시스템 호출은 자식프로세스가 종료될때까지 기다리고, 종료되면 종료된 자식프로세스의 
pid를 반환하고 stat_loc가 널이 아니라면 stat_loc가 가르키는 변수에 종료상태를 저장할것이다.

종료상태를 검사하는 매크로를 사용하여 정보를 구할수 있을것이다. 

WIFEXITED(stat_val) - 자식 프로세스가 정상적으로 종료되면 0이 아님
WEXITSTATUS(stat_val) - WIFEXITED가 0이 아니면 이것은 자식프로세스의 종료 코드를 반환한다
WIFSIGNALED(stat_val) - 자식 프로세스가 처리할 수 없는 시그널에 의해 종료되면 0이 아님
WTERMSIG(stat_val) - WIFSIGNALED 가 0이 아니면 이것은 시그널의 번호를 반환한다
WIFSTOPPED(stat_val) - 자식프로세스가 중단되었다면 0이 아님
WSTOPSIG(stat_val) - WIFSTOPPED가 0이 아니면 이것은 시그널의 번호를 반환한다

3-5. 좀비프로세스
부모와 자식 관계에서 자식이 먼저 종료되면, 부모가 정상적으로 종료될때 까지나 wait를 호출 할때
까지 wait에서 받아들이는 종료코드를 유지해야 하므로 자식은 좀비 프로세스로 남겨지게 된다.
좀비프로세스는 부모가 정상적으로 종료되지 않았다면 PPID(Parent PID) 가 1(init)로 된다.
좀비프로세스들은 init가 정리할때까지 남겨지게되어 리소스를 낭비하게 된다.

특정 자식 프로세스를 기다리게 하는 함수인 waitpid가 있다.

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

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

waitpid는 pid에 지정된 pid를 갖고잇는 프로세스를 options에 지정된 값에 맞게 기다리거나, 
반환하고 stat_loc에 상태를 저장한다. options의 종류는 다음과 같다.

WUNTRACED - 자식프로세스를 기다린다.
WNOHANG - 자식프로세스가 종료되지않았다면 0을 반환하고 그렇지 않으면 자식프로세스의 pid를
반환하고 stat_loc가 가르키는 변수에 상태를 저장한다.

다음은 wait를 호출했을때와 같은 효과를 낸다.

        waitpid(child_pid, &stat_loc, WUNTRACED);


3-6. 입출력 재지정
exec 계열 함수들은 파일 기술자를 물려받으므로 어떤 파일기술자를 표준입력이나 표준출력 기술자
으로 복사한다면, exec 함수로 인해 대체된 프로세스에서는 재지정된 입/출력을 사용할수 있을것이다.

__eof__
/***********************************************************************************************/
5단계 - 시그널과 시그널 처리

1. 시그널이란?
시그널은 프로세스가 어떤 조건에 대응하여 동작을 수행할 수 있도록 유닉스 시스템에 의해 발생되는 
이벤트이다. 프로세스가 메모리 침범, 부동소수 처리 에러나 부적절한 지시어와 같이 일부 에러 조건에 
의해 생성되거나 직접 사용자에 의해 생성된다.

2. 시그널의 종류
SIGABORT - *프로세스 취소
SIGALRM - 알람 시계
SIGFPE - *부동 소수 예외
SIGHUP - 접속 끊김
SIGILL - *부적절한 지시어
SIGINT - 터미널 인터럽트
SIGKILL - 처리하거나 무시할 수 없는 상태
SIGPIPE - 리더가 없는 파이프에 대한 쓰기
SIGQUIT - 터미널 종료
SIGSEGV - *무효한 메모리 세그먼트 액세스
SIGTERM - 종료
SIGUSR1 - 사용자 정의 시그널 1
SIGUSR2 - 사용자 정의 시그널 2

설명 앞에 *가 붙은것은 시스템에 따라 덤프파일을(core) 생성한다.

2. 시그널 처리
시그널을 처리할때 쓰는 기본적인 함수는 signal() 이다.

#include <signal.h>

void (*signal(int sig, void (*func)(int)))(int);

signal 함수는 sig인자에 지정된 시그널이 프로세스에 발생되면 func 함수 포인터가 가르키는 함수를
생성한다. func 함수 포인터가 가르키는 함수는 하나의 int 인자를 갖고 있어야 되는데, 이는 발생한
시그널 번호를 넣어주기 위함이다.

func에 지정될수 있는 개별적인 값은 다음과 같다.

SIG_IGN - 시그널을 무시한다.
SIG_DFL - 기본 동작을 복구한다.

지정된 시그널을 무시하거나 기본동작을 복구할때 위의 두 값을 사용할수 있을것이다. 
signal함수는 성공하면 func에 지정된 함수포인터를 반환한다.

다음 예제는 signal 함수로 시그널을 처리하는 예이다.

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

void sig_int(int sig)
{
        printf("This is INT signal handler!!\n");
        (void) signal(SIGINT, SIG_DFL);
}

int main()
{
        int count = 0;

        (void) signal(SIGINT, sig_int);

        while(1)
        {
                printf("%d\n", count);
                sleep(1);
                count++;
        }
}

3. 시그널 전달하기
시그널을 전달할때는 명령어 kill 과 비슷한 kill 함수를 사용한다.

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

int kill(pid_t pid, int sig);

kill 함수는 pid 에 지정된 PID에 해당하는 프로세스에 sig 시그널을 보낸다. 성공할 경우에는 0을
반환한다.
시그널을 전달할때에는 pid 프로세스가 자신의 권한과 같아야 한다. 루트라면 어느 프로세스라도
시그널을 전달할수 있다.

한가지 유용한 함수로는 alarm이 있는데 이것은 지정된 초 후에 SIGALRM 시그널을 전달한다.

#include <unistd.h>

unisigned int alarm(unsigned int seconds);

4. 안정적인 시그널 인터페이스
시그널을 처리하는데 signal함수를 사용하겠지만, 그보다더 안정적인 처리를 지원하는 sigaction()
함수를 사용하는것이 좋다.

#include <signal.h>

int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);

sigaction은 sig 시그널을 sigaction 구조체에 지정된 수행할 동작들에 따라 처리한다. act는
sigaction 구조체를 지정하고 oact에는 sig 시그널의 이전 동작을 기록한다.

sigaction 구조체의 멤버들은 다음과 같다.

void (*) (int) sa_handler /* 함수, SIG_DFL 또는 SIG_IGN */
sigset_t sa_mask /* sa_handler에서 방지할 시그널 */
int sa_flags /* 시그널 동작 변경자 */

sa_mask에 들어가야 하는 값은 sa_handler에서 방지할 시그널을 말하는데, 이것은 시그널 핸들러가
진행중일때 어떠한 시그널이 전달되어 시그널 핸들러가 중단 되는것을 방지하기 위함이다.
sa_flags에 포함될수 있는 값들은 다음과 같다.

SA_NOCLDSTOP - 자식 프로세스가 중단할 때 SIGCHLD를 생성하지 않는다.
SA_RESETHAND - 시그널을 전달받을 때의 동작을 SIG_DFL로 재설정한다.
SA_RESTART - EINTR에 대해 에러가 아니라 인터럽트 가능 함수를 재시작 한다.
SA_NODEFER - 시그널을 가로챌 때 시그널 마스크에 시그널을 추가하지 않는다.

5. 시그널 모음
시그널 모음은 시그널 블록 함수나 sigaction 구조체의 sa_mask 등에서 사용될 시그널을 모은것이다.
시그널 모음을 처리할때 쓰는 함수는 다음과 같다.

#include <signal.h>
int sigaddset(sigset_t *set, int signo);
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigdelset(sigset_t *set, int signo);
int sigismember(sigset_t *set, int signo);

sigaddset - set이 가르키는 시그널 모음에 signo 시그널을 추가한다.
sigemptyset - set 시그널 모음을 빈 상태로 초기화 한다.
sigfillset - set 시그널 모음이 모든 시그널을 포함하도록 초기화 한다.
sigdelset - set 시그널 모음에서 signo 시그널을 삭제한다.
sigismember - set 시그널 모음에 signo 시그널이 있는지 검사한다.

시그널 마스크를 설정 할때에는 sigprocmask 함수를 사용한다.

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

how는 시그널 마스크를 변경시킬 방법을 지정하는 값이고, set은 기록할 시그널 모음이고 oset는
이전의 시그널 마스크가 기록된다.

how 인자는 다음중 하나가 될수 있다.

SIG_BLOCK - set의 시그널이 시그널 마스크에 추가된다.
SIG_SETMASK - 시그널 마스크는 set으로 설정된다.
SIG_UNBLOCK - set의 시그널이 시그널 마스크로부터 제거된다.

sigprocmask는 성공적이면 0을 반환하고, -1을 반환하고 errno를 설정한다.

프로세스에 의해 방지되는 시그널은 전송되지 않지만 남아 있을 것이다. 프로그램은 sigpending 함수로
이 시그널을 확인할수 있다.

#include <signal.h>

int sigpending(sigset_t *set);

set에 전송되지 않은 시그널이 기록될 것이다.

프로세스는 시그널이 전달 될때까지 실행을 중지하기 위해 sigsuspend 함수를 사용할수 있다.

#include <signal.h>

int sigsuspend(const sigset_t *sigmask);

sigsuspend는 시그널 마스크를 sigmask에 지정된 시그널 모음으로 대체하고 실행을 중지할 것이다.
이것은 시그널을 받아 시그널 핸들러가 실행된 이후에 다시 재개할 것이다. 전달된 시그널이 프로그램을
종료하지 않으면 sigsuspend는 errno를 EINTR로 설정하고 -1을 반환한다.
/***********************************************************************************************/
6단계 - 프로세스간 통신 : 파이프

1. 파이프란 무엇인가?
파이프는 한 프로세스의 출력을 특정 프로세스의 입력으로 연결 시켜주는 방법을 의미한다.
실제 생활에서의 파이프를 생각해 보자. 실생활에서의 파이프는 물을 이동하게 해주거나 까스
배관등으로 쓰이는데, 파이프의 한쪽으로 물을 넣으면 다른 한쪽에서는 그 물이 나온다. 또, 
한쪽으로 까스를 넣으면 다른 한쪽에서 까스가 나온다. 이런것들과 같은 개념으로 컴퓨터의
파이프를 본다면 파이프의 한쪽에 데이터를 집어 넣으면 다른 한쪽에서는 그것을 읽어 들일수
있는것으로 이해될수 있을 것이다.

쉘에서는 직접 파이프를 이용하는 프로그램을 짜지 않아도 파이프를 이용할수 있는 기능을
제공한다.
예를 들어

  $ ls -al | wc -l

이렇게 한다면 ls -al 명령을 실행할때 출력되는 데이터가 wc -l 명령의 표준 입력으로
받아질 것이다.

2. 파이프 사용하기(popen, pclose)
▶ popen
#include <stdio.h>

FILE *popen(const char *command, const char *open_mode);

popen 함수는 command 에 지정된 프로그램을 새로운 프로세스를 생성하여 실행하고
open_mode 에 지정된 값에 따라 그 프로세스에서 보내온 데이터를 읽거나, 데이터를 써서
그 프로세스의 표준입력으로 받아 들일수 있게 한다.
즉, 특정 프로세스와의 파이프를 생성해주는 함수이다.

open_mode에는 "r" 또는 "w" 가 들어갈수 있다. r 로 지정하면 파이프와 연결된 다른 
프로세스의 출력을 이 프로세스에서 받아들일수있다. w 로 지정하면 r과 반대 기능을
수행한다.

popen함수는 파이프를 성공적으로 생성하면 그 파이프에 대한 파일 스트림을 반환하고,
실패하면 -1 를 반환한다.

▶ pclose
#include <stdio.h>

int pclose(FILE *stream_to_close);

pclose 함수는 stream_to_close에 지정된 파일스트림(파이프)를 닫는다. pclose는 파이프와
연결된 다른 프로세스가 종료되지 않았다면 그 프로세스가 끝날때까지 대기할 것이다.

성공하면 파이프에 연결됬었던 프로세스의 종료코드를 반환할것이고, 실패하면 -1를 반환
할것이다.


다음은 popen함수와 pclose함수를 사용하는 예제이다.

<< popen.c >>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
        FILE *in, *out;
        char string[256] = { 0x00, };
        int chars_read;

        if(argc < 3)
        {
                fprintf(stderr, "Usage: %s [command1] [filename]\n", argv[0]);
                exit(1);
        }

        if((out = popen(argv[1], "w")) == NULL)
        {
                perror("popen");
                exit(1);
        }

        if((in = fopen(argv[2], "r")) == NULL)
        {
                perror("fopen");
                exit(1);
        }

        while((chars_read = fread(string, sizeof(char), sizeof(string), in)) > 0)
        {
                fwrite(string, sizeof(char), chars_read, out);
        }

        pclose(out);
        fclose(in);

        return(0);
}
$ gcc -o popen popen.c
$ ./popen cat popen.c
#include <unistd.h>
#include <stdio.h>
.
.
나머지 파일 내용
.

이 프로그램은 두번째 인자로 지정된 파일을 읽어 들여 popen을 사용하여 열려진 파이프를
이용하여 첫번째 인자로 지정된 프로그램에 쓴다. 그 결과 데이터를 받은 프로세스는 
데이터를 처리하고 결과를 출력한다.

popen에 대한 부가적인 설명을 덧붙이자면, popen은 쉘에서 command 프로그램을 실행하기 
때문에 쉘 확장이 가능하다. * 같은 문자를 사용할수 있다는 뜻이다. 
 
3. 파이프 사용하기2(pipe)
▶ pipe
파이프를 생성할때 사용하는 또다른 함수는 pipe이다.
#include <unistd.h>

int pipe(int fd[2]);

pipe 함수는 두개의 정수형 변수로 구성되는 배열을 받고, 파이프를 생성하여 fd[0] 에는 
파이프의 읽기 부분을 가르키는 파일 기술자를 넣어주고, fd[1] 에는 파이프의 쓰기 부분을 
가르키는 파일기술자를 넣어주고 0을 반환한다. 실패할 경우 -1를 반환하고 errno를 설정한다.

pipe함수를 실행하고 나서 fd[1] 에 데이터를 쓰기를 할경우 fd[0]에서 읽을수 있다.
프로세스 안에서만 쓰기/읽기를 할경우 파이프의 존재가 별로 도움이 안될것이지만, fork를
사용하여 자식 프로세스를 생성함으로써 부모프로세스와 자식프로세스와의 통신을 할수 
있을것이다. pipe를 사용하고 나서 부모/자식간에 데이터를 주고 받을려면 관련없는 
파이프 기술자는 닫혀져야 한다.

또, pipe함수는 popen함수와 달리 파일 기술자를 반환하기 때문에 read()와 write()를
이용하여 읽거나 쓰여야한다.

pipe함수와 read, write 사용하여 부모와 자식간의 데이터 전송을 보여준 예제를 보자.
<< pipe.c >>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>

int main()
{
	int fd[2];
	int size;
	char buffer[256];
	pid_t child_pid;

	pipe(fd);
	
	if((child_pid = fork()) == -1)
	{
		perror("fork");
		exit(1);
	}
	else if(child_pid == 0)
	{
		close(fd[1]);
		size = read(fd[0], buffer, sizeof(buffer));
		printf("Result: %s, %d bytes\n", buffer, size);
		close(fd[0]);

		exit(0);
	}
	else
	{
		close(fd[0]);
		size = write(fd[1], "Hello, World!", 14);
		printf("Result: written %d bytes\n", size);

		close(fd[1]);
	}
	return(0);
}

$ gcc -o pipe pipe.c
$ ./pipe
Result: written 14 bytes
$ Result: Hello, World!, 14 bytes		

위 프로그램에서는 pipe로 파이프를 생성하고 부모는 자식에게 파이프로 데이터를 전달하고
자식에선 그걸 전달받아 출력하였다. 부모가 데이터를 보내고 먼저 종료 되니 위와 같이
출력이 복잡하게 된것을 볼수 있다.

부모와 자식의 통신에선 표준 입력 또는, 표준 출력으로 파이프를 관련 시키기 위해선 
dup, dup2 함수의 호출이 필요하다.

▶ dup, dup2
#include <unistd.h>

int dup(int file_descriptor);
int dup2(int file_descriptor_one, file_descriptor_two);

dup함수는 file_descriptor 파일기술자를 현재 열려있지 않은 가장 낮은 파일기술자로
복사한다. dup2는 file_descriptor_two에 지정된 파일기술자를 닫고 파일기술자가 그
파일기술자로 복사 되게 한다.

앞에서 부모와 자식의 통신에선 dup, dup2 가 필요할수 있다고 했다. 파이프를 표준입력이나
표준 출력과 연관 시킬려면 close()를 이용해 0이나 1을 닫고 파이프 기술자를 0이나 1로 
복사해야 한다. 만약 부모에서 파이프로 보낸 데이터를 자식에서 표준 입력으로 받기 원한다면
fork 호출후 자식프로세스에서 close(0); 로 표준 입력을 닫고 dup(pipe[0]); 으로 파이프의
입력쪽을 표준입력으로 만들면 될것이다. 그 후로는 부모에서 데이터를 보내면 자식에서는
표준입력으로 그것을 받아 볼수 있을것이다. 또, exec 계열 함수를 이용하면 파일 기술자가
상속되기 때문에 다른 개별적인 프로그램과 파이프를 이용하여 데이터를 주고 받을수 있을것이다.

이것을 간단한 예제로 나타내면 다음과 같다.
=================================================================
	.
	.
	child_pid = fork();
	if(child_pid == 0) {
		close(0);
		dup(pipe[0]);
		close(pipe[0]);
		close(pipe[1]);

		execlp("uname", "uname", "-a", NULL);
	}
	else {
		close(pipe[0]);
	.
	.
=================================================================

4. 명명 파이프 : FIFO
명명파이프로 알려진 FIFO는 파이프와 개념은 같지만 파일시스템 안에 실제 파일로 존재
한다. 기존의 파이프는 부/모 관계에있는 프로세스에게만 정보를 주거나 받을수 있지만
이 FIFO는 파일시스템 안에 파일로 존재 하기때문에 어떠한 프로세스라도 정보를 공유
할수 있다.

우선, FIFO 파일을 만들때 쉘에서 사용하는 명령부터 알아보자. FIFO파일을 만들기 위해서는 
mknod 명령이나 mkfifo 명령을 사용한다. 사용법은 다음과 같다.

$ mknod filename p
$ mkfifo filename

프로그램 내에서 FIFO를 생성할수 있는 함수 역시 위에 나온 것들과 같다.

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

int mkfifo(const char *filename, mode_t mode);
int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t) 0);

filename 은 생성할 FIFO 파일이름을 의미하고 mode는 생성할때 쓰일 퍼미션을 지정해주면
된다. mode는 umask에 의해 제한받을수 있다.

FIFO파일을 액세스 하는 방법은 기존의 파일들을 액세스 하는 방법과 흡사하다.
먼저, open이나 fopen 호출해야 한다. open의 옵션 인자에 따라 FIFO가 동작하는 방법도 
달라지는데 그 경우를 살펴보자.

open(const char *path, O_RDONLY);
이경우에 open은 이미 O_WRONLY 로 파일이 다른 프로세스로 인해 열려 있다면 즉시 반환하고
그렇지 않다면 O_WRONLY 로 열려질때까지 대기할것이다.
open(const char *path, O_RDONLY | O_NONBLOCK);
FIFO가 O_WRONLY로 열려있지 않았더라도 즉시 반환한다.
open(const char *path, O_WRONLY);
FIFO가 O_RDONLY로 이미 열려져있다면 즉시 반환하고, 그렇지 않다면 O_RDONLY로 열려질때
까지 대기할 것이다.
open(consht char *path, O_WRONLY | O_NONBLOCK);
이경우에는 O_RDONLY 로 이미 열려있었다면 정상적인 기술자를 반환할것이지만, 그렇지
않다면 -1를 반환할 것이다.

O_NONBLOCK 이 붙지 않은 옵션을 갖고 열려진다면, 그 기술자에 대해 read나 write의
수행은 데이터를 읽거나 쓸수 있을때까지 방지될것이다.
그러나 O_NONBLOCK이 붙은 옵션을 갖고 열려진다면, read나 write가 방지되지 않고
0을 반환할것이다.

open으로 열었다면, 이제는 read로 FIFO로 들어오는 데이터를 읽거나 write로 FIFO로
데이터를 쓰면 된다. 그리고 모든 데이터를 처리 했다면 끝났다는걸 알리위해 close()로 
기술자를 닫으면 될것이다.

파이프나 FIFO에 한번에 데이터를 기록할수 있는 크기는 PIPE_BUF 상수를 통해 정의 
되어 있다. PIPE_BUF이상의 데이터를 기록하고자 한다면 일부 데이터만 기록할 것이다.

다음은 FIFO를 이용하여 서버와 클라이언트 간에 정보를 공유하는 것을 보여주는 예제이다.
<< server.c >>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFONAME "/tmp/my_fifo"

int main()
{
	int fd, nbytes;
	char buffer[PIPE_BUF];
	
	mkfifo(FIFONAME, 0777);
	fd = open(FIFONAME, O_RDONLY);
	
	do {
		nbytes = read(fd, buffer, sizeof(buffer));
		if(nbytes > 0)
			printf("%s", buffer);
	} while(nbytes > 0);

	close(fd);
	unlink(FIFONAME);
	
	return(0);
}

<< client.c >>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFONAME "/tmp/my_fifo"

int main()
{
	int fd;
	int count;
	char buffer[PIPE_BUF];
	
	fd = open(FIFONAME, O_WRONLY);
	
	while(count < 5)
	{
		sprintf(buffer, "Hello, World! - %d\n", count); 
		write(fd, buffer, strlen(buffer));
		count++;
	}

	close(fd);
	return(0);
}

$ gcc -o server server.c;gcc -o client client.c
$ ./server &
$ ./client
Hello, World! - 1
Hello, World! - 2
Hello, World! - 3
Hello, World! - 4
Hello, World! - 5
[1]+  Done                    ./fifo1
$ 

그다지 효율적인 예제는 아니지만 FIFO를 설명하는데는 충분할것이다.

__eof__
/***********************************************************************************************/
7단계 - IPC : 세마포어, 메시지큐, 공유메모리

1. 세마포어
세마포어는 프로세스들이 하나의 리소스를 액세스하려 할때, 단 하나의 프로세스만이 리소스를
제어할수있게 해주는 일종의 변수이다.
세마포어는 두가지 형태가 있는데 0과 1의 값만 가질수있는 변수인 바이너리 세마포어와
1이상의 양수값을 가질수 있는 범용 세마포어로 나뉘어 진다. 범용 세마포어보다는 바이너리
세마포어가 많이 쓰인다.

세마포어 변수는 1이나 다른 양수의 값(범용 세마포어)으로 초기화 되는데, 이 세마포어
변수는 다수의 프로세스에 의해 더해지거나 빼진다. 그 다수의 프로세스는 같은 리소스를
액세스하기 위해 세마포어 변수를 공유한다. 세마포어 변수가 -1이 되어 0이 된다면 특정섹션
에 진입하게 되고 그 섹션이 다시 세마포어 변수를 +1 하여 0이상의 값이 되지 않는 이상,
다른 프로세스는 세마포어 변수를 -1 할때 이미 세마포어 변수가 0이기 때문에 +1이 될때까지
방지된다. 섹션에서 다시 세마포어 변수의 값이 0에 +1 되면 방지되고 있던 다른 프로세스는
다시 행동을 재개하게 되고 이 프로세스 역시 -1하여 0이 된다.
쉽게 말하자면, 세마포어 변수를 사용하여 동기화를 유지하려 하는 두 프로세스가 있다면,
한 프로세스가 세마포어 변수를 감소하고 특정섹션에 돌입하면 세마포어변수를 감소하려던
다른 프로세스는 방지된다는 것이다. 섹션에 돌입해 있던 프로세스가 세마포어변수를 증가하면
다른 프로세스는 실행이 다시 재게되게 된다. 즉, 특정 리소스를 액세스하려던 여러 프로세스가
있다면, 그 프로세스들은 세마포어를 증가하고 감소함으로써 동시에 액세스 되지 않고 한
프로세스만이 리소스를 액세스 할수 있는 것이다.

▶ semget
#include <sys/sem.h>

int semget(key_t key, int num_sems, int sem_flags);

semget 함수는 세마포어를 생성하고 세마포어 기술자를 반환한다. key는 관련없는 프로세스가
같은 IPC객체를 액세스하게 해주는데 사용되는 정수값이다. num_sems는 생성하려는 세마포어
수이다. 거의다 1이다.
sem_flags는 세마포어에 대한 허가권과 OR 연산을 통해 결합될수 있는 IPC_CREAT이다.
IPC_CREAT는 세마포어를 새로 생성할때 쓰며 key에 관련있는 프로세스가 이미 세마포어를 생성
했으면, 이 플래그는 무시되고 이미 생성되어 있는 세마포어에 대한 기술자를 반환한다.

semget 함수는 성공하면 생성된 세마포어에 대한 식별자를 반환하고, 실패하면 -1을 반환한다.

▶ semop
#include <sys/sem.h>

int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

semop은 세마포어의 값을 변경하기 위해 사용한다. sem_id는 semget으로 반환된 세마포어
기술자이다. sem_ops는 세마포어를 변경하기 위한 값이 담겨있는 sembuf 구조체의 배열에 대한 
포인터이다. 

struct sembuf {
	short sem_num;
	short sem_op;
	short sem_flg;
}

sem_num은 세마포어 번호이고, sem_op은 세마포어의 값을 변경시키기 위한 기준값이다. 바이너리
세마포어라면 -1이나 +1이 될수있다. sem_flg는 일반적으로 SEM_UNDO로 설정되는데 SEM_UNDO는
세마포어에 가해지는 변경을 관리하고, 프로세스가 세마포어를 제거하지 않고 끝내더라도 자동해제
하게 해준다.

▶ semctl
#include <sys/sem.h>

int semctl(int sem_id, int sem_num, int command, ...);

semctl함수는 세마포어에 대해 직접 제어하게 해준다. sem_id는 세마포어 식별자이고 sem_num은
세마포어 번호이다. command는 세마포어에 대해 수행할 동작이고 네번째 파라미터는 세마포어를
제어할때 필요한 값을 갖고 있는 공용체 semun이다. 다음은 최소한의 멤버를 가지는 semun이다.

union semun {
	int val;
	struct semid_ds *buf;
	unsigned short *array;
}

command의 종류는 다음과 같다.

IPC_STAT : 현재 세마포어의 상태를 네번째 파라미터에 기록한다.
IPC_SET : 세마포어에 대한 허가정보를 semid_ds 구조체에 대한 포인터인 buf의 내용으로 변환한다.
SETVAL : 세마포어를 네번째 파라미터의 val 멤버의 값으로 초기화한다.
IPC_RMID : 세마포어를 삭제한다.

2. 공유메모리
공유메모리는 서로 관련 없는 두 프로세스가 같은 메모리를 액세스 하게 해준다. 공유메모리는
물리메모리에 생성되고 프로세스들의 자체적인 논리 메모리로 연결되게 된다. 공유메모리를 사용하는
프로세스들은 같은 메모리 세그먼트를 공유할수 있고, 한 프로세스에서 공유메모리에 가해지는
변경은 다른 프로세스에도 반영된다. 공유메모리는 많은 장점을 제공하지만 동기화 특징을 제공하지
않으므로 프로그래머가 직접 동기화 특징을 구현해야 할것이다.

▶ shmget
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);

shmget 함수는 공유메모리를 생성하게 해준다. key는 다른 프로세스에서도 같은 공유메모리
세그먼트를 사용하기 위해 사용하는 정수값이고, size는 공유메모리 세그먼트의 크기이다.
shmflg는 메모리에 대한 허가권플래그 이고, IPC_CREAT플래그와 비트 OR연산을 통해 결합될수 있다. 
semget와마찬가지로 IPC_CREAT플래그를 이용하여 아직 key와 관련된 공유메모리가 생성되지 
않았다면 생성한다. 이미 생성되어 있다면 IPC_CREAT는 무시되고 기존의 공유메모리에 대한 
기술자를 반환한다.

shmget은 성공적으로 실행됬다면 생성된 공유메모리에 대한 기술자를 반환하고, 실패할 경우에는
-1을 반환한다.

▶ shmat
#include <sys/shm.h>

void *shmat(int shm_id, const void *shm_addr, int shmflg);

shmat 함수는 물리메모리에 생성되어 있는 공유메모리를 프로세스의 논리메모리에 연결시켜 준다.
이것은 꼭 필요한 작업이며 공유메모리를 사용하기 위해서는 필수이다. shm_id는 공유메모리
기술자이고 shm_addr은 공유메모리가 연결될 프로세스의 논리 메모리의 주소이다. 메모리 주소를
직접 지정해 주는것은 하드웨어에 매우 의존적이므로, 시스템이 자동으로 선택해 줄수 있도록
0으로 해주는것이 좋다. shmflg는 비트 플래그의 모음으로써, shm_addr과 함꼐 공유 메모리가
연결되는 어드레스를 제어하는 SHM_RND와 연결된 메모리를 읽기 전용으로 만드는 SHM_RDONLY가
있다.

shmat는 성공적으로 실행됬다면 공유메모리가 연결된 프로세스의 논리 메모리의 주소를 반환하고
실패했다면 -1를 반환한다.

▶ shmdt
#include <sys/shm.h>

int shmdt(const void *shm_addr);

shmdt는 현재 프로세스로부터 shm_addr에 지정된 논리 메모리(공유메모리)를 제거한다. 즉,
공유메모리 자체를 삭제하는것이 아니라 물리메모리에 생성된 공유메모리와 연결되있는
현재 프로세스의 논리 메모리를 무효하게 만든다. 
shmdt는 성공하면 0을 반환하고 실패하면 -1를 반환한다.

▶ shmctl
int shmctl(int shm_id, int command, struct shmid_ds *buf);

shmctl은 공유메모리에 대해 직접 액세스 할수있게 해준다. shm_id는 공유메모리 식별자이고
command는 수행할 동작이다. 그리고 buf는 shmid_ds구조체에 대한 포인터로써 공유메모리를
위한 모드와 허용권한을 갖는다. shmid_ds는 다음과 같다.

struct shmid_ds {
	uid_t shm_perm.uid;
	uid_t shm_perm.gid;
	mode_t shm_perm.mode;
}

command의 종류는 다음과 같다.

IPC_STAT : 공유 메모리와 관련된 값을 반영하기 위해 shmid_ds 구조체에서 데이터를 설정한다.
IPC_SET : 프로세스가 허용 권한을 가진다면 공유 메모리와 관련된 값을 shmid_ds 데이터
          구조에서 제공되는 것으로 설정한다.
IPC_RMID : 공유 메모리 세그먼트를 삭제한다.

만약, shmdt를 이용해서 현재 프로세스로 부터 공유메모리를 제거 하지 않고 IPC_RMID 플래그로
공유메모리를 삭제하려 한다면, 마지막 프로세스로 부터 연결 해제될 때까지 계속해서 동작할
것이다.

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

3. 메시지 큐
메시지 큐는 명명파이프와 비슷하지만 파이프작업보다 단순하고 동기화와 방지 문제를 거의
대부분 방지할수 있다. 메시지 큐는 각 데이터 블록에 부과되는 최대크기 제한인 MSGMSX와
큐의 최대크기 제한인 MSGMNB에 의해 제한 받을수 있다.

▶ msgget
#include <sys/msg.h>

int msgget(key_t key, int msgflg);

key는 관련없는 프로세스가 같은 메시지큐를 사용하기위해 사용되는 정수형태의 값이고, msgflg는
메시지큐에대한 허가권이며, 메시지큐를 새로 생성하게 해주는 IPC_CREAT 플래그와 OR 연산을 통해
결합될수 있다.

msgget 함수는 성공하면 메시지큐에 대한 기술자를 반환하고, 실패하면 -1를 반환한다.

▶ msgsnd
#include <sys/msg.h>

int msgsnd(int msqid, void *msg_ptr, size_t msg_sz, int msgflg);

msgsnd는 메시지큐에 메시지를 추가하게 해준다. msqid는 메시지큐 식별자이며 msg_ptr은 추가할
메시지에 대한 포인터 이다. 이 메시지는 꼭 long int 형의 메시지 타입을 가르키는 변수로 시작해야
한다. 다음과 같이 말이다.

struct my_message {
	long int message_type;
	/* 전송하기 원하는 데이터 */
}

msg_sz는 long int형의 메시지타입 변수를 제외한 메시지의 크기이고, msgflg는 메시지큐가 가득
차거나 큐에 저장되는 메시지에 대한 시스템 범위의 제한에 도달했을때 발생하는것을 제어한다.
msgflg에는 IPC_NOWAIT가 들어갈수 있는데 IPC_NOWAIT는 큐의 공간이 유효할때까지 기다리지 않고
즉시 반환하게 한다.

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

▶ msgrcv
#include <sys/msg.h>

int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);

msgrcv 함수는 메시지큐로부터 메시지를 구한다.
msqid는 메시지큐 식별자이고 msg_ptr은 메시지큐로부터 구한 메시지를 받을 변수에 대한 
포인터이다. msg_sz는 long int형 메시지타입변수의 크기를 제외한 받을 메시지 크기이고
msgtype은 메시지큐에서 받아 들일 메시지의 타입이다. 다시말해, msgtype이 1이면 큐에서
메시지의 타입이 1인 처음의 메시지를 구한다. 특정 메시지를 구하고 하면 그 메시지는 큐로부터
제거 된다. msgtype이 0이면 큐에서 유효한 메시지부터 차례로받아 들인다. msgflg는 IPC_NOWAIT
값을 갖을수 있고, 이 플래그가 설정된다면 메시지가 전달될수 있을때까지 기다리지 않고 즉시 
반환할것이다.

msgrcv 함수는 성공하면 버퍼에 저장된 바이트 수를 반환하고, 실패하면 -1를 반환한다.

▶ msgctl
#include <sys/msg.h>

int msgctl(int msqid, int command, struct msqid_ds *buf);

msgctl은 메시지큐를 직접 제어할수 있게 해준다. msqid는 메시지큐 식별자이고, command는
수행할 동작이다. msqid_ds 구조체에대한 포인터인 buf는 다음의 멤버를 가진다.

struct msqid_ds {
	uid_t msg_perm.uid;
	uid_t msg_perm.gid;
	mode_t msg_perm.mode;
}

command의 종류는 다음과 같다.

IPC_STAT : 메시지 큐와 관련된 값을 반영하기 위해 MSQID_DS 구조체에서 데이터를 설정한다.
IPC_SET : 프로세스가 허용 권한을 가진다면 메시지 큐와 관련되는 값을 msqid_ds 데이터
          구조체에서 제공되는 것으로 설정된다.
IPC_RMID : 메시지 큐를 삭제한다.

msgctl함수는 성공하면 0을 반환하고, 실패하면 -1를 반환한다.


4. IPC 관련 명령어
ipcs {-s | -m | -q} : 세마포어, 공유메모리, 메시지큐 상태보기
ipcrm {sem | shm | msg} id : 세마포어, 공유메모리, 메시지큐 삭제하기
/***********************************************************************************************/
8단계 - 개발도구(gcc, make, gdb)

1. GCC
GCC는 GNU(Gnu is Not Unix)으로 인해 개발된 리눅스/유닉스 환경에서 사용하는 컴파일러
이다. 기능도 좋고 사용하기 편할뿐만 아니라, 공짜여서 리눅스/유닉스 에서 가장 많이
사용되는 컴파일러이다.

- 오브젝트 파일 생성하기
먼저 오브젝트 파일을 생성할때 사용하는 옵션을 알아보자. 오브젝트 파일을 생성할때는
-c 옵션을 사용한다.

$ gcc -c [srcfile] 

srcfile은 복수가 될수 있다. 소스파일이 여러개라면 각각의 소스파일에 대한 오브젝트
파일을 생성한다. 예를 들어보자.

$ gcc -c hello.c hello2.c
$ ls *.o
hello.c hello.o hello2.c hello2.o

- 실행파일 생성하기 
실행파일을 생성할때는 두가지 방법을 사용할수 있다. 아무런 옵션없이 gcc를 실행시키는 것과
-o을 사용하는 것이다. 아무런 옵션없이 사용하는 거라면

$ gcc [srcfile]

이렇게 사용하면 된다. gcc는 srcfile을 컴파일하고 링크하여 a.out 라는 실행파일을 생성한다.
-o 옵션은 방금전 처럼 실행파일을 생성하지만 생성할 실행파일 이름을 지정해 줄수 있다.

$ gcc -o hello hello.c 혹은
$ gcc hello.c -o hello
$ ./hello
Hello, World!

소스파일 이름에는 여러개가 올수있으며, 오브젝트파일도 된다.

- l 옵션과 L 옵션
l옵션과 L옵션은 라이브러리와 관계된 옵션이다. -l 옵션은 링크할 라이브러리 명을 지정해주는
옵션이다. 예를 들어, 다음과 같이 한다면

$ gcc -o hello hello.c -lmylib

libmylib.a 라이브러리를 링크과정에서 hello.o와 링크시켜준다. 하지만 위의 명령은 실패할것이다.
-L 옵션은 라이브러리를 찾을 위치를 지정해주는 옵션이다. 방금전의 명령이 실패할것이라고 
했는데, 왜냐하면 libmylib.a 는 현재 디렉토리에 있는데 gcc가 표준 라이브러리파일 디렉토리에서
찾으려고 하고 때문이다. 이때 우리는 -L 옵션을 사용함으로써 라이브러리를 찾을수 있다.

$ gcc -o hello hello.c -lmylib -L.

현재 디렉토리(.)에서 라이브러리를 찾게 하였다.

2. make
make는 복수의 소스파일, 오브젝트파일을 컴파일-링크 할때 쓰는 개발도구이다. make가 실행될때는
현재 디렉토리에서 Makefile 또는 makefile 이라는 파일을 읽어 들인다. 이 파일에는 make에서
인식되는 애플리케이션의 구성을 나타내는 내용을 갖고있다. Makefile이나 makefile이라는 이름이
아니라 다른 이름이면 make -f [filename] 으로 사용할수 있다.

Makefile의 예를 보자.
--------------------------------------------------
hello: main.o hello.o
	gcc -o hello hello.c

main.o: main.c
	gcc -c main.c

hello.o: hello.c myfunc.h
	gcc -c hello.c
--------------------------------------------------
분석을 해보자. 왼쪽부분에 있는 hello, main.o, hello.o 는 대상의 이름이고 : 옆에 있는것은
그 대상을 만들기 위해 의존하는 파일목록이다. hello는 main.o와 hello.o에게 의존한다. 이것이
생성되어 있지 않다면 main.o 부분으로 간다. main.o는 또 main.c에게 의존한다. 만약 main.c가
생성되어 있으면 gcc -c main.c를 이용하여 main.o를 생성한다. 다시 hello의 의존을 보면
hello.o가 있는데 hello.o가 생성되어있지 않다면 이번 역시 hello.o로 옴겨가서 의존성을 
검사하고 그밑에 있는 명령을 실행한다. 마치 복잡한 사슬같다. 실행 해보자.

bash-2.05a$ make
gcc -c main.c
gcc -c hello.c
gcc -o hello hello.c

성공적으로 hello를 생성하는것을 볼수있다.

makefile에도 주석문이 있는데 # 를 앞에 사용함으로써 주석으로 만들수 있다.
애플리케이션을 효율적으로 구성할려면 매크로 라는것을 이용해야 한다. 매크로는 변수와
비슷한 개념인데 생성하는법은 "변수의이름 = 값" 형태로 생성하고 사용할때는 $(변수의이름)
으로 사용하면된다. make 자체가 제공하는 매크로가 있는데 그것은 다음과 같다.

$? - 현재 대상보다 최근에 변경된 필수 조건의 목록
$@ - 현재 대상의 이름
$< - 현재 필수 조건의 이름
$* - 확장자를 제외하고 현재 필수 조건의 이름

진보된 makefile을 보자.
---------------------------------------------------
all: hello

# 컴파일러
CC = gcc

# 설치 위치
INSTDIR = /usr/local/bin

# 헤더파일 위치
INCLUDE = .

hello: main.o hello.o
	$(CC) -o hello hello.c -I$(INCLUDE)

main.o: main.c
	$(CC) -c main.c -I($INCLUDE)

hello.o: hello.c myfunc.h
	$(CC) -c hello.c -I($INCLUDE)
clear:
	-rm main.o hello.o

install: hello
	@cp hello $(INSTDIR);\
	@chmod 755 $(INSTDIR);\
	@echo "Installed in $(INSTDIR)";
---------------------------------------------------
all은 지시된 대상이 없으면 기본적으로 실행할 대상 목록을 갖고있다. 여기서는 CC, INSTDIR 등의
매크로를 사용하였다. 매크로는 $() 으로 사용하였으며 각 대상의 명령에서 사용하였다. 이렇게
매크로를 사용하는것은 많은 Makefile에서 자주 볼수있다. 2개의 대상이 더 추가되었는데 clear과
install이다. clear는 오브젝트 파일을 지우고 install는 INSTDIR에 지정된 디렉토리에 hello를
설치한다. 이 두개의 대상도 역시 많은 Makefile에서 볼수있을것이다. 특정 대상만을 실행하기
위해서는 make 대상의이름 하면된다.

마지막으로, 내장규칙을 알아보자. Make는 메이크파일을 단순화 시키기위한 내장규칙을 제공한다.
내장규칙중에 오브젝트를 생성하는 것이있는데, 그것은 다음과 같다.

%.o: %.c
#  commands to execute (built-in):
        $(COMPILE.c) $(OUTPUT_OPTION) $<

우리는 이 내장규칙을 이용하여 구지 명령을 안적어 줘도 .o 파일을 .c으로 간편하게 만들수있다.

3. gdb
gdb는 Gnu DeBugger 의 약자로 프로그램의 버그를 고칠때 쓰는 디버거이다. gdb로 디버깅 하기
위해서는 디버깅할려는 소스를 gcc 로 -g옵션과 함께 컴파일 해야한다.

$ gcc -g -o hello hello.c
$ gdb hello

- run
run 명령은 프로그램을 실행한다. run의 인수는 프로그램의 인수로 전달된다.

- backtrace(bt)
이 위치까지의 진행과정을 보여준다.

- print expr
수식의 값을 보여준다(변수, 수식).

- list
프로그램의 C 소스코드를 보여준다.

- break [file:]function
function 또는 특정 주소에 정지점을 설정한다. 프로그램 실행후 이부분에 도착하면 정지된다. 
여러개의 break가 가능하다.

- continue(c)
정지점등에서 정지한 프로그램을 계속 실행한다.

- step
다음행을 수행한다. 그 행에서 수행되는 함수를 수행한다.

- next
다음행을 수행한다. 그 행에서 수행되는 함수를 수행하지 않고 건너뛴다.

- disassemble [function]
function 함수의 어셈블리 코드를 출력한다.

- help [name]
name에 관환 도움말을 보여준다.

- quit
GDB를 종료한다.
/***********************************************************************************************/
9단계 - 소켓

1. 소켓이란 무엇인가?
소켓은 한 시스템이나 네트워크 상에서 통신을 가능케 해주는 통신 인터페이스이다.
소켓은 버클리 유닉스 버전에 의해 소개되었다. 소켓을 사용하면 운영체제의 종류에
관계 없이 서버/클라이언트 환경을 구축할수있다.

2. 소켓 사용하기
소켓을 사용하기 위해서는 소켓 어드레스 정보가 담긴 구조체를 사용해야 한다. 
로컬 시스템에서 사용하기위한 소켓은 sys/un.h 에 정의된 sockaddr_un 을 사용해야
한다.

struct sockaddr_un {
	sa_family_t	sun_family;	/* 소켓 도메인(AF_UNIX) */
	char		sun_path[];	/* 어드레스 파일 경로   */
};

네트워크에서 사용하기 위한 소켓은 netinet/in.h 의 sockaddr_in 을 사용한다.

struct sockaddr_in {
	short int          sin_family;  /* 소켓 도메인(AF_INET) */
	unsigned short int sin_port;    /* 포트 번호            */
	struct in_addr     sin_addr;    /* 인터넷 주소(IP)      */
	unsigned char      sin_zero[8]; /* sockaddr를 위한 변수 */
};

마지막의 sin_zero 는 실제적으로 데이터를 보내는데 사용되는 데이터 타입인
sockaddr 구조체와 크기를 같게하기 위한 변수이다. 이것은 0으로 채워야 한다.

▶ socket() - 소켓 생성
#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

domain은 소켓 도메인(소켓종류)인데 AF_UNIX 나 AF_INET 이 될수있다. type 은 소켓
타입으로써 TCP를 사용하여 신뢰된 통신을 하기위한 SOCK_STREAM, UDP를 사용하는
SOCK_DGRAM 이 있다. protocol은 기본적으로 0으로 해준다.

socket 함수는 소켓을 생성하여 성공하면 새로운 소켓 기술자를, 실패하면 -1를 반환
한다.

▶ bind() - 소켓 어드레스 할당
#include <sys/socket.h>

int bind(int socket, const struct sockaddr *address, size_t address_len);

bind는 address에 담겨있는 어드레스를 socket 소켓에 할당한다. 인터넷 개념과 함께
말하자면 소켓을 로컬시스템의 포트에 연결하는 작업이다. address_len 은 address의
크기이다. bind는 어드레스를 할당하여 성공하면 0을 반환하고, 실패하면 -1을 반환
하고 errno를 셋팅한다.

▶ listen() - 소켓 큐 생성
#include <sys/socket.h>

int listen(int socket, int backlog);

listen 함수는 소켓을 위한 backlog 크기의 큐를 생성해 준다. 이 큐는 socket 과
관련된 포트에 접속하는 클라이언트들이 대기하는 하며, 후에 accept 를 사용하여
클라이언트와 통신할수 있다.

listen은 큐를 생성하여 성공할경우 0을 반환하고, 실패하면 -1를 반환하고 errno를
셋팅한다.

▶ accept() - 접속 받아들이기
#include <sys/socket.h>

int accept(int socket, struct sockaddr *address, size_t *address_len);

accept 함수는 socket과 관련된 큐에서 대기하고 있는 클라이언트와 통신할수 있도록
새로운 소켓 기술자를 반환한다. 클라이언트의 정보는 address 가 가르키는 sockaddr
구조체에 저장된다. address_len은 address의 길이를 지정한다. 이 accept는 소켓의
큐에 접속되어있는 클라이언트가 없다면, 클라이언트가 접속을 수행할때까지 대기
(방지) 될것이다. 함수는 성공하면 새로운 기술자를 반환하고 실패하면 -1를 반환한다.

▶ connect() - 접속하기
#include <sys/socket.h>

int connect(int socket, const struct sockaddr *address, size_t address_len);

connect 함수는 socket 을 address가 가르키는 주소에 접속 시킨다. address_len은
address의 크기이다. connect로 접속되고 나면 서버/클라이언트 관계가 형성되고
read, write 또는 send, recv 등으로 데이터를 주고 받을수 있다.
connect 함수는 성공하면 0을 반환하고 실패하면 -1를 반환한다.

▶ 소켓 닫기
소켓을 닫기 위해서는 일반 파일 기술자를 닫는 close 함수를 사용하면 된다.
close는 소켓이 전송되지 않는 데이터를 가지거나 아직 접속이 닫혀있지 않은
클라이언트가 있다면 방지된다. 소켓을 닫는 함수에는 shutdown 이라는 함수가 있다.

#include <unistd.h>

int shutdown(int sockfd, int how);

how에는 닫는 방법이 들어간다.

0 - 더이상의 수신 금지 
1 - 더이상의 송신 금지 
2 - 더이상의 송수신 금지(close()와 같은 경우) 

shutdown은 에러가 나면 -1를 반환한다.

▶ 호스트와 네트워크 바이트 순서
소켓을 사용하는 컴퓨터는 여러가지가 있다. 그 시스템 중에는 메모리에 1-2-3-4
순으로 저장하는 시스템이 있지만, 4-3-2-1 방식으로 저장하는 시스템이 있다. 서버와
클라이언와 통신을 하기위해서는 서버의 같은 포트를 사용하여 통신을 해야 하는데
서버와 클라이언트의 바이트 순서가 달르게 되면 서버 소켓에서는 1574가 주어지지만
클라이언트에서 1574 포트를 접속하려할때 바이트 순서가 달라 9734 포트로 접속
할수도 있다. 이런 사태를 방지하기 위해 소켓에서는 네트워크 바이트 순서와 호스트
바이트 순서를 서로 변환 시킬수있는 함수를 지원한다. 다음은 변환 함수들이다

#include <netinet/in.h>

unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);

함수는 함수이름 그대로 해석하면 된다

htons()--"Host to Network Short" 
htonl()--"Host to Network Long" 
ntohs()--"Network to Host Short" 
ntohl()--"Network to Host Long"

서버에 접속하거나 대기하려 할때는 sockaddr_in 구조체의 주소 멤버에 htonl로 
호스트 바이트 순서에서 네트워크 바이트 순서로 바꾼 주소값을 넣고, sin_port에는 
htons로 변환한 포트 값을 넣으면 될것이다. 또, 네트워크 순서로 변환되어져 있는
값은 ntohs나 ntohl 로 호스트 바이트 순서로 변환하면 될것이다.

▶ 네트워크 정보(IP, 포트, 서비스)
점으로 구성된 아이피를 호스트/네트워크 바이트 순서로 바꾸기 위해서 다음 함수를
사용한다.

#include <arpa/inet.h>

unsigned long inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);

inet_addr 함수는 점으로 이루어진 아이피 문자열을 unsigned long(NBO)로 변환
해준다. inet_ntoa 함수는 그 반대로 NBO로 이루어진 아이피를 아스키 형식의 문자열로
해준다.  

IP 어드레스에 대한 정보를 알기 위해서는 다음과 같은 함수를 사용한다.

#include <netdb.h>

struct hostent *gethostbyaddr(const void *addr, size_t len, int type);
struct hostent *gethostbyname(const char *name);

위 두 함수로 부터 반환되는 hostent 구조체는 다음과 같다.

struct hostent {
	char *h_name;		/* 호스트의 공식적인 이름    */
	char **h_aliases;	/* 호스트의 별명으로서 NULL 로 끝맺음된다 */
	int h_addrtype;		/* 주소의 종류, 보통 AF_INET */
	int h_length;		/* 주소의 바이트 수          */
	char **h_addr_list;	/* 0으로 끝나는 네트워크 주소들, NBO 구성 */
};

#define h_addr  h_addr_list[0]	/* h_addr_list 속의 첫번째 주소 */

로컬 시스템의 호스트 네임을 알아보기 위해서는 gethostname 함수를 사용한다.

#include <unistd.h>

int gethostname(char *name, int namelength);

gethostname은 namelength 길이의 호스트 네임을 name이 가르키는 공간에 넣는다.
성공하면 0을 반환하고, 실패하면 -1를 반환한다.

때로는 몇가지 서비스에 대한 정보를 알아보아야 할때도 있을 것이다.

#include <netdb.h>

struct servent *getservbyname(const char *name, const char *proto);
struct servent *getsevbyport(int port, const char *proto);

proto는 SOCK_STREAM 를 위한 tcp나 SOCK_DGRAM 을 위한 udp가 될수있다. 두번째
함수의 port 는 NBO이어야 한다. 두 함수에서 반환되는 정보를 갖는 servent 
구조체는 다음 멤버를 가진다.

struct servent {
	char *s_name;		/* 서비스 이름 */
	char **s_aliases;	/* 별칭의 목록 (선택적인 이름) */
	int s_port;		/* IP 포트 번호 */
	char *s_proto;		/* 일반적으로 "tcp"나 "udp"인 서비스 형태 */
};

▶ select() - 동시에 파일 기술자 검사하기
select 함수는 동시에 여러개의 파일 기술자를 읽기, 쓰기 행동이 있는지 알아볼수
있다. 또, 그 파일 기술자에서 읽을수 잇는 데이터나 쓸 데이터가 있을때까지
프로그램이 방지 되게 해준다.

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

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds,
	struct timeval *timeout);

nfds는 테스트할 파일기술자의 숫자인데, 보통 테스트할 최대 파일 기술자 +1 이다.
readfds 는 읽을 데이터가 있는지 테스트할 파일기술자 모음이고, writefds 는
쓸수 있는지, errrorfds는 에러조건을 가지는지 테스트할 파일 기술자 모음
이다. 그리고 마지막 timeout는 timeval 구조체에 대한 포인터 인데, select는
이 timeval 시간 만큼 파일 기술자에 대해 어떠한 행동이 이루어 질때까지 기다릴
것이다. select 함수는 파일 기술자 모음에 있는 파일 기술자중에 어떠한 기술자가
읽기나, 쓰기나, 에러를 가진다면 그 기술자가 변경되었음을 가르키도록 변경하고
리턴한다. 성공하면 파일기술자 모음의 전체 갯수를 반환하고 실패하면 -1를 반환한다.
또, timeout 동안 기달려도 파일기술자들에게 아무 반응이 없다면 0을 반환한다.

timeval 구조체는 다음 멤버를 가진다.

#include <sys/types.h>

struct timeval {
	time_t	tv_sec;	 /* 초 단위 */
	long	tv_usec; /* 밀리 초 단위 */
};

파일 기술자 모음에 파일기술자를 추가하거나 지우거나 반응이 있는지 검사하기 위해
다음 매크로들을 사용한다.

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

void FD_ZERO(fd_set *fdset);  /* 파일 기술자 모음을 0으로 초기화 한다  */
void FD_CLR(int fd, fd_set *fdset);  /* fdset 파일기술자 모음에서 fd를 지운다 */
void FD_SET(int fd, fd_set *fdset);  /* fdset 파일기술자 모음에 fd를 추가한다 */
int FD_ISSET(int fd, fd_set *fdset);  /* fd가 fdset 파일기술자 모음의 요소라면
					0이 아닌값을 반환한다 */

파일기술자가 읽기/쓰기/에러 의 하나에 반응이 있었는지 알아 볼려면 FD_ISSET을
사용하여 해당 파일기술자가 반응이 잇엇는지 체크하면 된다. 반응이 있었다면
FD_ISSET은 1을 반환하고 반응이 없었다면 0을 반환한다.

내 문서는 수학여행이야.. 보고도 남는게 없지.(분위기가 따운됐군 -_-)

__Eof__ 
 
/***********************************************************************************************/
10단계 - POSIX 스레드(thread)

1. 스레드란?
스레드는 한 프로그램의 여러 실행 흐름이다. 한마디로 프로세스 내의 여러 프로세스라고
생각하면 된다. 이전의 프로세스는 단일 스레드로 이루어 지지만, 스레드를 생성하면
프로그램이 여러 갈래로 갈리고 여러개의 실행 흐름이 생겨난다. 그 덕분에 한 프로그램
안에서 동시에 여러 작업을 할수있다. 스레드들은 한 프로세스에서 파일 기술자, 전역변수,
시그널 핸들러, 현재 디렉토리 상태를 공유하고 자체적인 스택을 가진다. 그리고 스레드는
fork 를 호출하는 것보다 처리속도가 빠르고 적은 메모리를 사용한다. 하지만, 스레드는
많은 부분을 공유하기 때문에 한쓰레드가 잘못된 연산을 했을시에는 전체 쓰레드가 영향을
받을수 있다. OS는 fork 로 생성된 프로세스들을 보호할수있는 보호기능을 갖고 있지만
쓰레드를 보호하는 기능은 없다. 때문에 쓰레드를 사용할 시에는 좀더 주의하여야 한다.

2. 스레드 지원가능 여부
완전한 스레드가 지원되기 시작한것은 POSIX1003.1c 부터이다. 현재의 시스템이 스레드를
지원하는지 알아보기위해서는 limits.h, unistd.h, features.h등의 헤더파일을 살펴보거나  
_POSIX_VERSION 상수가 199506L 이상의 값을 지니고 있는지 확인하는 것이다. 199506L
이상 이어야 완전한 스레드를 지원하지만 그보다 낮어도 약간의 스레드는 지원할것이다.
그리고 _POSIX_THREAD_PRIORITY_SCHEDULING 를 확인해 봄으로써 우선순위 스케줄링을
지원하는지 알아볼수있다.

3. 스레드를 생성하고 종료하기
스레드를 생성하기 위해서는 pthread_create 함수를 사용한다.

#include <pthread.h>

int pthread_create(pthread_t *thread, pthread_attr_t *attr,
	void *(start_routine)(void *), void *arg);

thread 인자는 스레드 식별자를 담을 pthread_t 형 변수에 대한 포인터이다. attr 은
옵션을 말하고 기본적으로 NULL 로 설정할수있다. start_routine은 스레드가 시작하는
함수이다. void * 형의 인자를 받아들이고 void *형을 반환하는 함수여야한다. arg 는
start_routine에 들어갈 인자이다. pthread_create는 스레드를 생성하고 성공적이면 0을,
실패하면 에러 코드를 반환한다.

스레드가 끝날때에는 pthread_exit 함수로 종료시켜야 한다.

#include <pthread.h>

void pthread_exit(void *retval);

pthread_exit 함수는 retval 인자를 자신을 생성한 스레드에 반환하고 스레드를 종료
시킨다. 스레드가 종료될때까지 기다리고 스레드로부터 인자를 돌려받기 위해서는
pthread_join 함수를 사용한다.
 
#include <pthread.h>

int pthread_join(pthread_t th, void **thread_return);

pthread_join 은 th 스레드 기술자를 가진 스레드가 종료될때까지 기다리고 종료되면 
thread_return에 리턴값을 돌려받는다. 성공하면 0을 반환하고 실패하면 에러코드를 반환
한다.

스레드를 사용하는 프로그램을 컴파일할려면 -lpthread 옵션과 함께 컴파일 해야한다.

3. 동기화
스레드 사이에는 동기화가 필요한 작업이 있다. 입력 스레드가 입력을 받아야만 출력 
스레드가 화면에 출력을 한다던지, 그런 작업 말이다. 세마포어나 뮤텍스를 이용해서
이런 작업을 할수있다.

3.1 세마포어(semaphore)
세마포어는 0과 1값을 감소 시키거나 증가시키며 특정 자원을 특정 스레드만이 접근할수
있도록 한다. 세마포어를 사용하려는 스레드는 세마포어가 1일때는 계속 진행할수 있으며
진입하고나면 세마포어는 0이 된다. 그 결과 세마포어를 사용하려는 다른 스레드는 
세마포어가 1이 되야지만 계속 진행할수 있다.

세마포어를 생성하고 초기화하기 위해서는 sem_init 함수를 사용한다.

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);

sem은 sem_t 형의 세마포어 변수를 가르키고 pshared는 세마포어의 형태이다. pshared가
0이면 현재 프로세스에서만 사용할수있다. 0이 아니면 다른 프로세스간에도 공유될수 있다.
sem_init는 sem 세마포어를 value 값으로 초기화 하고 성공하면 0을 반환하고 실패하면
-1 를 반환한다.

sem_wait 함수와 sem_post 함수는 세마포어의 값을 제어한다.

#include <semaphore.h>

int sem_wait(sem_t *sem);

int sem_post(sem_t *sem);

sem_wait 는 세마포어의 값을 1 줄이고, sem_post 는 세마포어의 값을 1 늘린다. 두 함수는
모두 조화롭게(간섭받지않게) 실행된다. sem_wait는 세마포어 값이 0이상 이면 1 감소
시키고 계속 실행할것이지만 0이면 1이될때까지 기다린다. 즉, 한 스레드가 sem_wait로
1 감소시키고 특정 부분에 진입하게 되면 sem_wait를 하려던 다른 스레드는 sem_post로
1 증가 될때까지 방지되어 한 스레드만 특정 영역을 실행할수 있는것이다.

sem_destroy 함수로 세마포어를 정리할수 있다.

#include <semaphore.h>

int sem_destroy(sem_t *sem);

세마포어를 파괴하려할때 다른 스레드가 세마포어를 대기중이라면 에러가 발생한다.

3.2 뮤텍스(mutex)
뮤텍스는 스레드 동기화에 사용되는 또다른 방법이다. 뮤텍스는 세마포어와 비슷하지만
뮤텍스는 오로지 한 쓰레드만이 특정 영역을 실행하게 할때 사용될 수 있다. 뮤텍스의
두가지 행동은 잠금(lock)과 해제(unlock)이다. 동기화가 필요한 코드영역에 진입할때 
잠그고 빠져나올때 해제한다. 한 뮤텍스가 잠겨있을때 그 뮤텍스를 잠그려는 다른 쓰레드는
방지된다. 그리고 뮤텍스가 해제되면 그 다른 쓰레드가 다시 잠그고 영역에 진입한다.
뮤텍스를 사용하기 위한 함수는 다음과 같다.

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

int pthread_mutex_destroy(pthread_mutex_t *mutex);

pthread_mutex_init 는 뮤텍스를 초기화하고 pthread_mutex_lock는 뮤텍스를 잠그고, 
pthread_mutex_unlock 는 뮤텍스를 해제한다. pthread_mutex_destroy 는 뮤텍스를 제거
한다. 실행이 성공하면 0을 반환하고 실패하면 에러코드를 반환한다.

3.3 조건 변수(condition variable)
조건변수는 "시그널 발생"과 "시그널 기다림" 으로 뮤텍스보다 좀더 섬세한 동기화 특징을
제공한다. 특정 영역 진입을 위해 뮤텍스를 해제하고 신호를 기다리다가 신호가 도착하면 
뮤텍스를 잠고 다음 코드를 실행한다. 조건변수는 여러가지 상황에서 굉장히 유용하게 
사용된다. 한 스레드가 입력을 받고 다른 스레드는 그것을 출력하는 코드가 있다면
프로그램은 뮤텍스를 사용해야 한다. 그러나 뮤텍스로 동기화하려 한다면 새로운 데이터가 
입력됬는지, 안됬는지도 모르고 출력하려 할것이다. 이때 조건변수를 써서 출력 스레드는 
신호를 기다리게 하고, 입력스레드에서 새로운 입력을 받으면 신호를 보내줘 출력 스레드가 
꼭 새로운 데이터를 출력할수 있게끔 할 수 있을 것이다.

조건변수는 pthread_cond_t 타입으로 선언해야 하고 PTHREAD_COND_INITIALIZER
로 초기화 하거나 pthread_cond_init() 함수로 초기화 해야 한다.

pthread_cond_t cond_var = PTHREAD_COND_INITIALIZER;
		OR
pthread_cond_init(&cond_var);

신호을 보내려면 pthread_cond_signal 함수와 pthread_cond_broadcast 함수를 사용한다.

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

두 함수는 모두 조건변수에 신호를 보낸다. signal 함수는 하나의 스레드만 깨우지만
broadcast 함수는 모든 스레드를 깨운다. 성공하면 0을 리턴한다.

pthread_cond_wait, pthread_cond_timedwait 함수로 신호를 기다릴수 있다.

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, 
				const struct timespec *abstime);

mutex 뮤텍스를 해제하고 cond 조건변수에 신호가 도착할때까지 대기한다. 시그널이 도착
하면 뮤텍스를 잠그고 다음 코드를 실행한다. timedwait 는 timespec 형이 지정하는 시간
만큼만 대기한다. abstime 시간동안 시그널이 도착하지 않는다면 ETIMEOUT 에러코드를
반환한다.

pthread_cond_destroy 함수로 조건 변수를 정리 할 수 있다.

#include <pthread.h>

int pthread_cond(pthread_cond_t *cond);

조건변수를 정리한다. 만약 조건변수를 사용하는 쓰레드가 있다면 에러가 발생한다.

4. 스레드 취소하기(KILL)
스레드를 취소하기 위해서는 스레드에게 종료를 요청하는 함수 pthread_cancel 함수를
사용해야 한다.

#include <pthread.h>

int pthread_cancel(pthread_t thread);

이 함수는 thread 쓰레드에게 종료를 요청한다. 종료 요청을 받는 쓰레드에서도 작업이
필요하다.

#include <pthread.h>

int pthread_setcancelstate(int state, int *oldstate);

state는 취소요청을 받아들이면 취할 행동을 지정해준다. PTHREAD_CANCEL_ENABLE 는 취소
요청을 받아 들이고, PTHREAD_CANCEL_DISABLE 는 무시하게 해준다. oldstate 는 이전의 
상태를 담는다. 다음으로 취소형태를 지정해 줘야 하는데 다음 함수를 이용한다.

#include <pthread.h>

int pthread_setcanceltype(int type, int *oldtype);

취소형태에는 취소 요청을 즉시 받아들이는 PTHREAD_CANCEL_ASYNCHRONOUS와 스레드가 함수
pthread_join, pthread_cond_wait, pthread_cond_timedwait, pthread_testcancel,
sem_wait, sigwait 의 하나를 실행할때까지 취소 요청을 지연시키는 PTHREAD_CANCEL_DEFERRED
가 있다.

6. 스레드 속성 변경하기
스레드 속성을 이용하면 pthread_join 으로 메인스레드에서 다른 쓰레드를 안기달려도 
되게 할수있다. 그리고, 쓰레드의 모드와 스택크기를 조정할수 있다. 스레드 속성 변수를 
초기화할때는 pthread_attr_init 함수를 사용한다.

#include <pthread.h>

int pthread_attr_init(pthread_attr_t *attr);

성공할경우 0을, 실패할 경우 에러코드를 반환한다.

스레드 속성을 셋팅하는 함수들은 굉장히 많다. 그중에 몇가지만 살펴보자.

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

detachstate는 스레드가 조인해야 할 필요를 없애준다. detachstate 에 플래그를 지정할수
있는데, 두 스레드를 조인하게 해주는 PTHREAD_CREATE_JOINABLE과 조인 하지 못하게 하는
PTHREAD_CREATE_JOINABLE이 있다. 기본적으로는 PTHREAD_CREATE_JOINABLE 이다.

int pthread_attr_setscope(pthread_attr_t *attr, int scope);
int pthread_attr_getscope(const pthread_attr_t *attr, int *scope);

scope는 스레드의 모드를 지정하게 해준다. 유저 모드 PTHREAD_SCOPE_SYSTEM 와 커널 모드
PTHREAD_SCOPE_PROCESS 플래그가 있다. 리눅스는 현재 유저 모드만 지원한다.(확실치않다)

스레드 옵션 변수를 정리하기 할려면 다음 함수를 쓴다.

#include <pthread.h>

int pthread_attr_destroy(pthread_attr *attr);

성공하면 0을 반환하고, 실패하면 에러 코드를 반환한다.


이제야... 모든 문서가 끝났군.. 시스템 프로그래밍 프로젝트 문서 완료 -_-)/

참고 서적: Teach Yourself C, Beginning Linux Programing
참고 문서: The Linux Programer's Guide, www.joinc.co.kr
	   Beej's Guide to Network Programming, and so on.  

__Eof__
 
/***********************************************************************************************/
반응형

'C & C++ 관련' 카테고리의 다른 글

Socket Programing 기본 함수  (0) 2007.03.26
포인터  (0) 2007.03.14
확장열(Escape Sequence)  (0) 2007.03.14
Posted by Real_G