혼자 정리

UNIX시스템의 프로그램과 프로세스 개괄 본문

42/pipex

UNIX시스템의 프로그램과 프로세스 개괄

tbonelee 2021. 6. 14. 16:34

(Advanced Programming in the UNIX Environment, Rago, Stephen A., Stevens, W. Richard 저. 3판 참조)

책에서 사용하는 헤더 && 자체 에러 함수

apue.h

/*
* Our own header, to be included before all standard system headers.
*/
#ifndef _APUE_H
#define _APUE_H

#define _POSIX_C_SOURCE 200809L

#if defined(SOLARIS) /* Solaris 10 */
#define _XOPEN_SOURCE 600
#else
#define _XOPEN_SOURCE 700
#endif

#include <sys/types.h> /* some systems still require this */
#include <sys/stat.h>
#include <sys/termios.h> /* for winsize */

#if defined(MACOS) || !defined(TIOCGWINSZ)
#include <sys/ioctl.h>
#endif

#include <stdio.h> /* for convenience */
#include <stdlib.h> /* for convenience */
#include <stddef.h> /* for offsetof */
#include <string.h> /* for convenience */
#include <unistd.h> /* for convenience */
#include <signal.h> /* for SIG_ERR */

#define MAXLINE 4096 /* max line length */

/*
* Default file access permissions for new files.
*/
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

/*
* Default permissions for new directories.
*/
#define DIR_MODE (FILE_MODE | S_IXUSR | S_IXGRP | S_IXOTH)

typedef void Sigfunc(int); /* for signal handlers */

#define min(a,b) ((a) < (b) ? (a) : (b))
#define max(a,b) ((a) > (b) ? (a) : (b))

/*
* Prototypes for our own functions.
*/
char *path_alloc(size_t *); /* Figure 2.16 */
long open_max(void); /* Figure 2.17 */

int set_cloexec(int); /* Figure 13.9 */
void clr_fl(int, int);
void set_fl(int, int); /* Figure 3.12 */

void pr_exit(int); /* Figure 8.5 */

void pr_mask(const char *); /* Figure 10.14 */
Sigfunc *signal_intr(int, Sigfunc *); /* Figure 10.19 */

void daemonize(const char *); /* Figure 13.1 */

void sleep_us(unsigned int); /* Exercise 14.5 */
ssize_t readn(int, void *, size_t); /* Figure 14.24 */
ssize_t writen(int, const void *, size_t); /* Figure 14.24 */

int fd_pipe(int *); /* Figure 17.2 */
int recv_fd(int, ssize_t (*func)(int,
            const void *, size_t)); /* Figure 17.14 */
int send_fd(int, int); /* Figure 17.13 */
int send_err(int, int,
             const char *); /* Figure 17.12 */
int serv_listen(const char *); /* Figure 17.8 */
int serv_accept(int, uid_t *); /* Figure 17.9 */
int cli_conn(const char *); /* Figure 17.10 */
int buf_args(char *, int (*func)(int,
             char **)); /* Figure 17.23 */
             
int tty_cbreak(int); /* Figure 18.20 */
int tty_raw(int); /* Figure 18.20 */
int tty_reset(int); /* Figure 18.20 */
void tty_atexit(void); /* Figure 18.20 */
struct termios *tty_termios(void); /* Figure 18.20 */

int ptym_open(char *, int); /* Figure 19.9 */
int ptys_open(char *); /* Figure 19.9 */
#ifdef TIOCGWINSZ
pid_t pty_fork(int *, char *, int, const struct termios *,
               const struct winsize *); /* Figure 19.10 */
#endif

int lock_reg(int, int, int, off_t, int, off_t); /* Figure 14.5 */

#define read_lock(fd, offset, whence, len) \
            lock_reg((fd), F_SETLK, F_RDLCK, (offset), (whence), (len))
#define readw_lock(fd, offset, whence, len) \
            lock_reg((fd), F_SETLKW, F_RDLCK, (offset), (whence), (len))
#define write_lock(fd, offset, whence, len) \
            lock_reg((fd), F_SETLK, F_WRLCK, (offset), (whence), (len))
#define writew_lock(fd, offset, whence, len) \
            lock_reg((fd), F_SETLKW, F_WRLCK, (offset), (whence), (len))
#define un_lock(fd, offset, whence, len) \
            lock_reg((fd), F_SETLK, F_UNLCK, (offset), (whence), (len))
pid_t lock_test(int, int, off_t, int, off_t); /* Figure 14.6 */

#define is_read_lockable(fd, offset, whence, len) \
             (lock_test((fd), F_RDLCK, (offset), (whence), (len)) == 0)
#define is_write_lockable(fd, offset, whence, len) \
             (lock_test((fd), F_WRLCK, (offset), (whence), (len)) == 0)

void err_msg(const char *, ...); /* Appendix B */
void err_dump(const char *, ...) __attribute__((noreturn));
void err_quit(const char *, ...) __attribute__((noreturn));
void err_cont(int, const char *, ...);
void err_exit(int, const char *, ...) __attribute__((noreturn));
void err_ret(const char *, ...);
void err_sys(const char *, ...) __attribute__((noreturn));

void log_msg(const char *, ...); /* Appendix B */
void log_open(const char *, int, int);
void log_quit(const char *, ...) __attribute__((noreturn));
void log_ret(const char *, ...);
void log_sys(const char *, ...) __attribute__((noreturn));
void log_exit(int, const char *, ...) __attribute__((noreturn));

void TELL_WAIT(void); /* parent/child from Section 8.9 */
void TELL_PARENT(pid_t);
void TELL_CHILD(pid_t);
void WAIT_PARENT(void);
void WAIT_CHILD(void);

#endif /* _APUE_H */

error출력 소스 코드

#include "apue.h"
#include <errno.h> /* for definition of errno */
#include <stdarg.h> /* ISO C variable aruments */

static void err_doit(int, int, const char *, va_list);

/*
 * Nonfatal error related to a system call.
 * Print a message and return.
 */
void
err_ret(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(1, errno, fmt, ap);
    va_end(ap);
}

/*
 * Fatal error related to a system call.
 * Print a message and terminate.
 */
void
err_sys(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(1, errno, fmt, ap);
    va_end(ap);
    exit(1);
}

/*
 * Nonfatal error unrelated to a system call.
 * Error code passed as explict parameter.
 * Print a message and return.
 */
void
err_cont(int error, const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(1, error, fmt, ap);
    va_end(ap);
}

/*
* Fatal error unrelated to a system call.
* Error code passed as explict parameter.
* Print a message and terminate.
*/
void
err_exit(int error, const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(1, error, fmt, ap);
    va_end(ap);
    exit(1);
}

/*
 * Fatal error related to a system call.
 * Print a message, dump core, and terminate.
 */
void
err_dump(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(1, errno, fmt, ap);
    va_end(ap);
    abort(); /* dump core and terminate */
    exit(1); /* shouldn’t get here */
}

/*
 * Nonfatal error unrelated to a system call.
 * Print a message and return.
*/
void
err_msg(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(0, 0, fmt, ap);
va_end(ap);
}

/*
 * Fatal error unrelated to a system call.
 * Print a message and terminate.
 */
void
err_quit(const char *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    err_doit(0, 0, fmt, ap);
    va_end(ap);
    exit(1);
}

/*
* Print a message and return to caller.
* Caller specifies "errnoflag".
*/
static void
err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
    char buf[MAXLINE];

    vsnprintf(buf, MAXLINE-1, fmt, ap);
    if (errnoflag)
        snprintf(buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s",
          strerror(error));
    strcat(buf, "\n");
    fflush(stdout); /* in case stdout and stderr are the same */
    fputs(buf, stderr);
    fflush(NULL); /* flushes all stdio output streams */
}

프로그램(Program)과 프로세스(Process)

  • Program : 디스크 상에 존재하는 실행 파일. 프로그램이 메모리로 읽혀지게 되고, 커널에 의해 exec계열의 함수를 통해 실행되게 된다.
  • Processes and Process ID : 프로그램을 실행시키는 인스턴스. task로 용어를 사용하는 운영체제도 존재. 유닉스에서는 모든 프로세스가 고유한 숫자 구별자를 가지고 이를 process ID라고 부름. process ID는 항상 비음의 정수값을 갖는다.
#include "apue.h"

int main(){
    printf("hello world from process ID %ld\n", (long)getpid());
    exit(0);
}

컴파일해서 실행하면 다음과 같은 결과 출력.

$ ./a.out
hello world from process ID 489
$ ./a.out
hello world from process ID 511

getpid를 통해 프로세스 ID를 얻는다. getpid의 반환형은 pid_t. pid_t타입의 사이즈는 정확히 정해지진 않았지만 long타입에 들어맞는다는 것은 표준에 의해 강제된다.


프로세스 컨트롤(Process Control)

프로세스 컨트롤 함수는 기본적으로 fork, exec, waitpid가 존재.

(exec는 7가지 형태로 존재)

유닉스 시스템의 프로세스 컨트롤은 표준 입력에서 커맨드를 읽은 후 커맨드를 실행하는 프로그램을 통해 작동한다.

다음 코드는 쉘과 비슷한 역할을 하는 프로그램의 예시

#include "apue.h"
#include <sys/wait.h>

int main(){
	char	buf[MAXLINE];    /* from apue.h */
	pid_t	pid;
	int		status;

	printf("%% ");	/* print prompt */
	while (fgets(buf, MAXLINE, stdin) != NULL) {
		if (buf[strlen(buf) - 1] == '\n')
			buf[strlen(buf) - 1] = 0; /* replace newline with null */
		if ((pid = fork()) < 0) {
			err_sys("fork error");
		} else if (pid == 0) {		/* child */
			execlp(buf, buf, (char *)0);
			err_ret("couldn't execute: %s", buf);
			exit(127);
		}
		/* parent */
		if ((pid = waitpid(pid, &status, 0)) < 0)
			err_sys("waitpid error");
		printf("%% ");
	}
	exit(0);
}

작성한 프로그램의 특성들을 살펴보면 다음과 같다.

  • 표준입력으로부터 한 줄씩 읽기 위해 표준 입출력 함수 fgets를 사용. EOF를 입력하면 fgets는 null포인터를 반환하여 루프가 끝나고 프로세스가 종료된다.
  • fgets로 읽은 줄에서 널 문자 직전에 개행 문자가 있다면 널 문자로 교체. 그러기 위해 strlen함수를 사용하여 스트링의 길이를 계산한다. 이렇게 하는 것은 execlp함수가 널 종료되는 인자를 받기 때문.
  • fork를 통해 함수를 호출하는 프로세스(caller)의 복사본인 새 프로세스를 생성. caller를 부모(parent), 새로 생성된 프로세스를 자식(child)이라고 부른다. 부모 프로세스에게 fork는 음수가 아닌 자식 프로세스의 프로세스 ID를 반환하고, 자식 프로세스에게는 0을 반환한다. fork가 새 프로세스를 생성하므로 함수의 호출은 한 번 이루어지지만 반환은 두 번함을 알 수 있다.
  • 자식 프로세스에서 execlp를 호출하여 표준 입력으로부터 읽혀진 커맨드를 실행한다. 이를 통해 자식 프로세스를 새 프로그램 파일로 교체한다. 몇몇 운영체제에서는 fork를 호출하고 exec를 호출하는 것을 새 프로세스를 낳는 것으로 표현한다.
  • 자식 프로세스가 execlp를 호출하여 새 프로그램 파일을 실행하므로 부모 프로세스는 자식 프로세스가 종료할 때까지 대기한다. 이는 waitpid를 통해 이루어지며 이때 pid를 적어줌으로써 어떤 프로세스를 기다릴 것인지 명시해줘야 한다. waitpid함수는 자식 프로세스의 종료 상태(termination status)를 반환한다. 위의 프로그램에서는 이 값을 가지고 무언가를 하지는 않았다.
  • 위 프로그램은 커맨드에 인자를 넘길 수 없다는 한계가 있다. 이를 극복하려면 입력 라인을 파싱해서 execlp에 넘겨줘야 한다.

프로그램을 실행하면 다음과 같은 결과를 얻을 수 있다.

$ ./a.out
% date
Sat Jan 21 19:42:07 EST 2012
% who
sar console Jan 1 14:59
sar ttys000 Jan 1 14:59
sar ttys001 Jan 15 15:28
% pwd
/home/sar/bk/apue/3e
% ls
Makefile
a.out
shell1.c
% ˆD			type the end-of-file character
$				the regular shell prompt

 

'42 > pipex' 카테고리의 다른 글

pipex 허용 함수 정리(모르는 것, 필요한 부분 위주로)  (0) 2021.06.16
pipex 관련 찾아본 자료  (0) 2021.06.11