Unix/Linux 프로그래밍 기말시험 (2005년12월19일 18:00)
1. 문장의 빈곳을 적절한 말로 채우시오. [각1점]
(1) 유닉스 시스템이 시동될 때 가장 먼저 하는 일은 모든 프로세스들의 조상이 되는 프로세스인 ( init ) 프로세스를 만드는 일이다. 이 조상 프로세스는 프로세스 번호(PID)가 ( 1 ) 번이며 ( fork() )라는 시스템 호출을 이용하여 자식 프로세스들을 만드는데 이 때 자식 프로세스는 ( 프로세스번호(PID) )를 제외하고는 모든 면에서 부모 프로세스와 동일하다. 만들어진 자식 프로세스들은 ( exec() )라는 시스템 호출에 의해 실행 가능한 다른 프로그램의 코드로 치환될 수 있다.
(2) 시그널은 유닉스 프로세스에게 소프트웨어적인 인터럽트를 보내는 방법을 제공하는데 자료의 직선적인 전송보다는 비정상적인 상황을 취급하는데 사용된다. 시그널을 전면(foreground) 프로세스에게 전달하는 한 예로 키보드에서 Control-C나 Control-X를 누르는 것이다. 터미널 드라이버는 Control-C를 인식하는 즉시 ( SIGINT ) 시그널, Control-Z를 인식하면 ( SIGTSTP ) 시그널을 모든 전면 프로세스에게 보낸다.
한 프로세스 내부에서 다른 프로세스로 시그널을 보내려면 ( signal() )이라는 시스템 호출을 이용하면 된다.한 프로세스는 시그널을 받으면 취할 행동을 ( signal() ) 시스템 호출로 다시 프로그램 할 수 있는데 이 때 ( SIGKILL ) 시그널과 SIGSTOP 시그널은 절대로 다시 프로그램 되는 것을 허용하지 않는다.
[#M_ more.. | less.. |
2. 문장의 빈곳을 적절한 말로 채우시오. [각1점]
(1) 자식 프로세스는 종료할 때 부모 프로세스에게 ( SIGCHLD ) 시그널을 보낸다. 부모 프로세스는 그 자식이 좀비화 되는 것을막도록 ( wait() )을 실행하여 이 시그널을 처리하는 처리기를 설치한다.처리기를 설치하는 방법은( signal() ) 시스템 호출을 사용하는데 이때 첫 번 매개변수는 ( SIGCHLD )로 설정하고 두 번째 매개변수는 ( SIGCHLD 처리기의 이름 )라는 값을 설정하면 된다. 또는 부모 프로세스는 (SIGCHLD )을 무시할 수도 있다. 이 경우 자식 프로세스는 자동적으로 좀비화 되지 않는다. ( SIGCHLD )을 무시하려면 ( signal() )시스템 호출을 사용하는데 이때 첫 번 매개변수는 ( SIGCHLD )로 설정하고 두 번째 매개변수는 ( SIG_IGN )라는 값을 설정하면 된다.
(2) Internet이란 수 천 개의 작은 네트워크들로 이루어진 네트워크로 백만 개의 컴퓨터들이 연결되어 있다. 연결된 컴퓨터들은 모두 소프트웨어적으로는 통신 규약 TCP/IP를 준수하며 하드웨어적으로는 이더넷(ethernet) 버스, FDDI, ATM, Token Ring, 직렬선, 통신 모뎀 등의 통신 네트워크에 연결되어 있다. Internet의 소프트웨어 계층은 응용(application), 전송(transport), 네트워크(network), 링크(link) 계층이며 패킷을 목적지까지 전달하기 위하여 이웃하는 네트워크로의 홉핑(hopping)이 일어날 때에는 링크 계층과 네트워크 계층에서만 상호작용이 일어나므로 비교적 빠른속도의 전송이 가능하다. Unix의 소켓 프로그래밍은 연결지향 규약(connection-oriented protocol)인 TCP/IP 또는 비연결지향 규약(connectionless protocol)인 UDP/IP에 맞추어 소켓 프로그래밍을 할 수 있는데 연결지향 소켓 프로그래밍의 경우, server 측에서는 socket() -> ( bind() ) ->listen() -> ( accept() ) -> read() 또는 write()의 순서대로 소켓 시스템 호출을 하면 되고 클라이언트 측에서는 socket()-> ( connect() ) -> write() 또는 read()의 순서대로 소켓 시스템 호출을 하면 된다. 소켓을 이용하는 프로세스 통신은 클라이어트-서버 모델에 기초하고 있다.서버 프로세스는 이름이 클라이언트 프로세스에게 알려진 소켓을 생성한다. 클라이언트 프로세스는 이름이 없는 소켓을 생성하고 이를 서버의 명명된 소켓과 연결해 줄 것을 요청한다. 연결이 성공하면 클라이언트와 서버에게 각각 ( 파일기술자(File Descriptor) )를 하나씩 반환하는데, 두 파일 기술자는 모두 읽기와 쓰기용으로 사용될 수 있다. 이것이 소켓연결이 단방향 통신인 파이프와 달리 ( 양방향 ) 통신일 수 있는 이유이다.
3.문장의 빈곳을 적절한 말로 채우시오.[각1점]
(1) 유닉스 시스템의 프로세스는 ( fork() )라는 독특한 시스템 호출을 이용하여 자신을 복제할 수 있다. 하나의 프로세스가 ( fork() )를 호출하지만 2개의 프로세스(부모 프로세스와 자식프로세스)가 그 호출의 반환 값을 받는다.( fork() )가 성공하면 부모 프로세스에게는 ( 자식프로세스의 PID )을 반환하며 자식 프로세스에게는 ( 0 )을 반환한다. 만들어진 자식 프로세스들은 ( exec() )라는 시스템 호출에 의해 실행 가능한 다른 프로그램의 코드로 치환될 수 있다. 두 프로세스는 동일한 코드를 동시에 계속 실행하고 있지만 그들은 완전히 분리된 스택과 자료 공간을 갖고 수행된 후 종료된다. 종료한 프로세스라도 자신의 부모 프로세스가 반환 코드를 받을 때까지는 시스템을 떠날 수 없다.
만일 부모 프로세스가 이미 사멸되었다면,자식 프로세스는( init )프로세스의 양자가 된다.그러나 만일 어떤 프로세스의 부모가 살아 있으면서 wait()가 실행되지 않는다면 자기의 반환 코드는 결코 받아들여지지 않을 것이며 그 프로세스는 ( 좀비 ) 프로세스로 남아 있게 된다.자식 프로세스는 종료할때 부모 프로세스에게 ( SIGCHLD ) 시그널을 보낸다. 부모 프로세스는 그 자식이 ( 좀비 )화 되는 것을 막도록 wait()을 실행하여 이 시그널을 처리하는 처리기를 설치할 수 있다. 처리기를 설치하는 방법은 ( signal() ) 시스템 호출을 사용하는데 이때 첫번 매개변수는 ( SIGCHLD )로 설정하고 두 번째 매개 변수는 ( SIGCHLD )처리기의 이름을 설정하면 된다.또는 부모 프로세스는 ( SIGCHLD )시그널을 무시할 수도 있다.이 경우 자식 프로세스는 자동적으로 ( 좀비 )화되지 않는다. ( SIGCHLD )를 무시하려면 ( signal() )시스템 호출을 사용하는데 이때 첫 번 매개변수는( SIGCHLD )로 설정하고 두 번째 매개 변수는 ( SIG_IGN )라는 값을 설정하면 된다. ( 좀비 ) 프로세스는 코드, 자료, 스택 어느 하나도 갖고 있지 않게 때문에 많은 시스템 자원들을 낭비하지는 않지만 시스템의 고정된 크기의 프로세스 테이블에 계속 상주한다.
너무 많은 ( 좀비 ) 프로세스가 존재한다면 시스템 관리자는 이들을 처리해야 한다. 예를 들면 주기적인 작업을 스케줄하는( CRON ) 유틸리티를 이용하여 자동으로 제거할 수 있다.
4. Solaris 9의 Bash 셸 환경에서 아래의 질문에 답하라. [각2점]
(1) IP 패킷이 목적지에 도착하기 위해 방문하는 게이트웨이 순서를 추적하는 명령은?
traceroute
(2) /usr 디렉토리 내에 존재하는 접근권한이 755이고 set-user-id가 설정되어 있는 모든 파일을 찾아서 그 상세한 목록을 파일 result에 보관하는 Bash 셸 명령은?
find /usr -perm 1755 -exec ls -al >> result {} \;
(3) 매주 월요일 아침 오전 7시에 파일 시스템에 존재하는 모든 core 파일들을 제거하도록 crontab을 이용하여 스케줄하는 셸 스트립트를 작성하라.
crontab -e
0 7 * * 1 root /usr/bin/find / -name core -exec rm -f {} \;
(4) 호스트 이름이 multi.inehcon.ac.kr인 컴퓨터에서 수퍼유저가 아래 명령을 수행하여 네트워크 파일 시스템으로 공유시킨 후 다른 호스트에서 multi.incheon.ac.kr 컴퓨터의 /share 파일을 재부팅하지 않고 직접 처리하여 네트워크 파일로 사용 중인 호스트의 /mnt에 마운트해서 이용할 수 있게 하는 수퍼 유저 명령은?
$ share -F nfs /share
mount -F nfs multi.incheon.ac.kr:/share /mnt
(5) 호스트 이름이 xxx.incheon.ac.kr(IP 주소는 210.119.245.68)인 컴퓨터에 anonymous ftp로 접속해서 /pub/tracert.tar 파일을 내 컴퓨터로 복사해 온 다음 tar archive 파일에서 각 파일들을 추출하는 명령어 들은?
ftp 210.119.245.68
cd /pub
get tracert.tar
exit
tar -xvf tracert.tar
5. 아래의 fork()와 exec() 결합 사용에 대하여 답하시오.
단, fork() 실행 후 자식 프로세스가 CPU를 먼저 받았고 프로세스 종료까지 CPU를 넘겨주지 않았다고 가정한다.
(1) 다음 프로그램 실행 결과는 무엇인가? [5점]
[CODE]main()
{
int pid, status;
printf(“One\n”);
if ((pid = fork()) > 0) {
printf(“Two\n”);
wait(&status);
execl(“/bin/echo”, “echo”, “Three\n”, (char *)0);
} else
execl(“/bin/echo”, “echo”, “Four\n”, (char *)0);
printf(“Five\n”);
}[/CODE]
One
Four
Two
Three
(2) (1)과 같은 결과를 출력하도록 아래 프로그램의 빈칸을 채우시오. [각1점]
[CODE]main()
{
int pid, status;
char *av[3];
av[0]= “/bin/echo”;
av[1]= “Four\n”;
av[2]= (char *)0;
printf(“One\n”);
if((pid == fork() == 0))
{
execvp( av[0], av );
}
else
{
printf(“Two\n”);
wait(&status);
execl(“/bin/echo”, “echo” “Three\n”, (char *)0);
}
}[/CODE]
6. 유닉스 시스템의 시그널 처리에 대한 아래 물음에 답하시오. [각2점]
아래 프로그램은 자식 프로세스로 하여금 명령 줄에서 입력된 내용을 수행하도록 하는 프로그램이다. 이 프로그램 실행 중 유닉스 커널에서 제공하는 원래의 SIGCHLD 처리기는 자식의 반환코드를 받는 처리를 하지 않아 좀비 프로세스가 발생하게 된다. 100초 동안 sleep하는 작업을 하는 동안 SIGCHLD 시그널을 받을 경우 SIGCHLD에 대한 커널의 디폴트 처리기가 동작하는 대신 자신이 정의한 SIGCHLD 시그널 처리기인 sigchildhandler()에서 자식의 종료코드를
받아들인 후 종료된 자식의 프로세스 번호와 종료코드(exit 코드)를 출력하고 100초가 지난 후에는 다시 원래의 SIGCHLD 처리기를 회복하는 C 언어 프로그램 nozombie.c의 일부이다.
[CODE]#include <signal.h>
void sigchildHandler();
main(int argc, char *argv[])
{
int pid;
void (*oldHandler)();
oldHandler=signal( SIGCHLD, sigchildHandler );
pid=fork();
if(pid==0)
{
execvp( argv[1], &argv[1] );
perror(“limit”);
}
else
{
sleep(100);
signal( SIGCHLD, SIG_DFL );
}
}
void sigchildHandler()
{
int childPid, childStatus, childStatusLow;
childPid = wait(&childStatus);
childStatusLow = childStatus & 0xFF;
printf(“A Child with PID %d terminated with exit
code low8: %d, high8:%d\n”, childPid, childStatusLow, childStatus & 0xFF00);
exit(0);
}[/CODE]
7. 위6번과 연계된 아래 물음에 답하시오.[각5점]
(1) 시그널 처리기 sigchildHandler()를 별도의 프로그램 파일인 handler.c로 따로 작성하되 sigchildHandler() 함수에 대한 헤더파일도 새로 작성하고, 이때의 컴파일 명령을 적
으시오.
handler.h :
[CODE]void sigchildHandler();[/CODE]
handler.c :
[CODE]void sigchildHandler()
{
int childPid, childStatus, childStatusLow;
childPid = wait(&childStatus);
childStatusLow = childStatus & 0xFF;
printf(“A Child with PID %d terminated with exit
code low8: %d, high8:%d\n”, childPid, childStatusLow, childStatus & 0xFF00);
exit(0);
}[/CODE]
main.c :
[CODE]#include <signal.h>
#include “handler.h”
main(int argc, char *argv[])
{
int pid;
void (*oldHandler)();
oldHandler=signal( SIGCHLD, sigchildHandler );
pid=fork();
if(pid==0)
{
execvp( argv[1], &argv[1] );
perror(“limit”);
}
else
{
sleep(100);
signal( SIGCHLD, SIG_DFL );
}
}[/CODE]
Compile :
[CODE]gcc -c handler.h handler.c main.c
gcc -o nozombie handler.o main.o[/CODE]
(2) (2)번 프로그램에서 handler.c 파일을 컴파일 하여 process.a라는 보관 파일에 보관하였다. process.a에 보관되어 있는 handler.o을 링크하여 효율적으로 컴파일 할 수 있는 nozombie.make 파일을 작성시오.
[CODE]nozombie : process.a(handler.o) main.o
gcc -o nozombie process.a main.o
main.o : main.c
gcc -c main.c
process.a(handler.o) : handler.h handler.c[/CODE]
8. 아래 프로그램은 파이프를 이용하여 단방향 통신을 하는 파이프 2개를 이용하여 부모 프로세스와 자식 프로세스가 양 방향으로 통신을 하여 서로 읽고 쓰는 프로그램 bitalk.c의 일부이다.반 부분을 채워 완성하라.
[CODE]#include<stdio.h>
#define READ 0
#define WRITE 1
char* phrase1 = “Stuff this in your pipe and smoke it ? 1 from the Child”;
char* phrase2 = “Stuff this in your pipe and smoke it ? 2 from the Parent”;
main()
{
int fd1[2], fd2[2], bytesRead1, bytesRead2;
char message[100];
pipe(fd1);
pipe(fd2);
if(fork() == 0)
{
close(fd2[WRITE]);
bytesRead2 = read( fd2[READ] );
printf(“Child: Read %d bytes: %s\n”, bytesRead2, message);
close(fd2[READ]);
close(fd1[READ]);
write( fd1[WRITE] );
close(fd1[WRITE]);
}
else
{
write( fd1[WRITE] );
close(fd2[WRITE]);
close(fd1[WRITE]);
bytesRead1 = read( fd1[READ] );
printf(“Parent: Read %d bytes: %s\n”, bytesRead1, message);
close(fd1[READ]);
}
}[/CODE]
9. 셸에서 $ redirect_in xx cat 라고 치면 redirect_in 명령이 xx라는 파일의 내용을 표준 입력으로 연결하여 cat의 입력이 되게 하는 프로그램을 작성하시오. [10점]
[CODE]#include <stdio.h>
#include <fcntl.h>
main(int argc, char *argv[])
{
int fd;
fd = open(argv[1], O_RDONLY, 0600);
dup2(fd, 0); // 0은 표준입력
close(fd)
execvp(argv[2], &argv[2]);
}
[/CODE]
10. 명령 줄에서 입력한 호스트 이름과 포트(port) 번호로 소켓 연결을 요청하여 서버로부터 메시지를 하나 읽고 그 메시지(Are you READY?)에 대해 답으로 READY를 전송하고 나서 서버로부터의 전송되는 새로운 문자열을 읽어 화면에 출력하는 readygoc.c의 일부분이다. 빈 부분을 채워라. [10점]
[CODE]#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
main(int argc, char *argv[])
{
int clientFd;
char str[20];
char* hostaddress;
struct sockaddr_in serv_addr;
struct hostent* hostStruct;
struct in_addr* hostNode;
if(argv[1] == NULL) {
printf(“Usage: inetclient hostname(or server IP)?n”);
printf(” (Ex) inetclient multi.inchon.ac.kr(or 211.119.245.149) portnumber?n”);
exit(1);
}
hostStruct = gethostbyname( argv[1] );
if(hostStruct == NULL) return(0);
hostNode = (struct in_addr*) hostStruct->h_addr;
hostaddress = inet_ntoa(*hostNode);
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr( hostaddress );
serv_addr.sin_port = htons( argv[2] );
if((clientFd = socket(AF_INET,SOCK_STREAM,0)) < 0)
printf(“client: can’t open stream socket”);
if(connect(clientFd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
printf(“client: can’t connect to server”);
read( clientFd , str, 20);
printf(“%s “, str);
fflush(stdout);
read( clientFd , str, 20);
write(clientFd, str, 20);
read(clientFd, str, 20);
printf(“%s\n”, str);
close(clientFd);
exit(0);
}
[/CODE]
<끝> 수고하셨습니다.
_M#]
1267741260.doc