Process Control

▪ How processes are ‘created’ and terminated?

1. User가 command를 terminal/window에서 execute

2. 다른 'process'에 의하여: fork(), exec(), wait(), exit()

⇒ e.g., concurrent servers

▪ fork()

- An 'existing' process can create a 'new' process using fork().

⇒ fork() creates a “copy” of the executing process.

parent

process

child

process

fork()

⇒ 두 개의 process가 서로 "독립적으로" 수행됨.

ex) p.63 fig.2-7: init process (pid=1)

- fork()는 현재의 process image를 “복사”하여 child process를 만든다.

⇒ 각각의 process에 return 값이 주어짐: Return 값이 두 개임.

⇒ fork()가 성공적으로 수행되면, parent에게는 child의 PID가 return되고, child에게는 0가 return된다.

fork()가 실패하면 -1이 return되어 복귀한다.

ex) p.55, a multiprocess program.

if ((childpid=fork())==-1){

-- error 처리.

} else if (childpid==0){

PC of the child	/* child process */

-- child process가 해야할 일.

} else {

PC of the parent	/* parent process	*/

-- parent process가 해야할 일.

}

ex) test.c

#include <stdio.h>

#include <stdlib.h>

main(){

int childpid;

if((childpid=fork())==-1){

printf("can't fork");

exit(1);

}else if (childpid==0){

sleep(10);	/* child가 parent보다 나중에 끝남, ppid=1이 됨. */

printf("child: child pid=%d, parent pid=%d\n",getpid(),getppid());

exit(0);

}else{

printf("parent:child pid=%d, parent pid=%d\n", childpid, getpid());

exit(0);

} }

ex) test.c

fork() 후.....

parent	child

printf("1\n");

pid=fork();

printf("2\n");

printf("1\n");

pid=fork();

printf("2\n");

PC

PC

# test

1

2

2

fork() 수행 전

P

user

block

kernel area

user

image

user area

fork() 수행 후

Pparent

user

block

kernel area

user area

user

block

Pchild

user

image

user

image

⇒ 같은 user image를 가지지만 PC가 다르므로 수행하는 부분이 다름.

- Process table (PCB) values:

The values of the child that is the same as the values of the parents.

The values of the child that differs from the values of the parents.

⇒ p.56

⇒ process의 alarm() 시계는 exec()이 수행되더라도 계속 작동하나, fork() 수행 후 자식 process에게 alarm() 시계는 더 이상 작동하지 않는다.

- If the text segment can be shared, then both the parent and child can share the text segment after the fork().

ex) p.56 그림:	data segment는 copy

text segment는 공유.

- The open files in the parent before the fork() are shared by the child.

Why? p.71 fig2-12

⇒ fork()에 의하여 process image가 복사될 때는 user block도 함께 복사되므로 user block에 있는 file descriptor table도 모두 '복사'된다.

⇒ 따라서, fork()가 수행되기 전에 parent에서 열려졌던 모든 file은 child가 open()을 수행하지 않고 “공유”할 수 있다.

- Two uses for fork()

① A process wants  to make a copy of itself so that one copy can handle an operation while the other copy does another task.

⇒ child process가 독립적으로 수행됨.

ex)	Internet superserver: inetd - ftpd, telnetd, rlogind, tftpd 실행(p.336 fig.6-18).

DB server: DB engine - transaction 요구들 처리.

Web server: httpd - CGI process들 실행.

printer server

ex) p.65 fig2-9

② A process wants to "execute" another program.

⇒ child process가 끝나기를 기다린 후 제어를 돌려 받음.

⇒ fork(), exec() & wait()

ex) p.66. fig2-10; shell program - command processor

if (fork()==0){

execl( … );	/* child는 다른 program을 수행	*/

}

wait(&status);	/* parent는 child가 exit()하기를 기다림 */

⇒ fork(), exec()과 wait()을 사용하면 '수행 가능한 program을 실행'한 후에 원래의 process로 제어가 다시 돌아가는 효과를 얻을 수 있다.

⇒ synchronizaton

▪ exit()

- A process terminates itself by calling exit().

- When exit() is called, an integer exit status is passed by the process to the kernel.

ex) p.55

if ((childpid=fork()) == -1){

exit(1);	/* an error condition is returned	*/

} else if{

exit(0);	/* an normal condition is returned */

}

- The exit status is then available from the kernel to the 'parent process' through wait(&status).

⇒ Only the low-order 8 bits of the exit status is 'transfered' to the parent.

⇒ A child process terminates with an exit status in the range 0 ～ 255.	ex) p.60 fig2-6 참조.

▪ wait()

- A process can wait for one of its child processes to finish using

int wait(int *status)

the PID of the child     the value passed to exit()

that terminated	by the terminating child

-1 if no child

ex)

if (fork()==0){

execl( … );	/* child는 program을 수행	*/

}

wait(&status);	/* the calling process is 'suspended by the	kernel' until one of its child processes terminates */

⇔ unsigned int sleep(unsigned int sec)	/*pause for a specified no. of secs*/

- 3 conditions for which wait() returns a stutus.

① A child process called exit().

② A child process was terminated by a signal.

ex) # kill child-pid	/* child는 수행 중에 terminate됨. */

③ A child process was being traced and the process stoped.

ex) A debugger process is used to step through a process by tracing	the execution of another process.

debugger

process

child

process

SIGSTOP

- Each of above three cases returns different info. in the status var.

⇒ p.60 fig.2-6

⇒ status: parent가 대기 상태에서 벗어날 때, child로부터 전달되는 값으로서 하위(low) 8 bit와 상위(high) 8 bit로 구성됨.

Case ①: child가 exit()에 의해 종료.

→ exit()의 인수로서 주어진 값이 status의 상위 8 bit에 전달됨.

Case ②:	child가 signal에 의해 종료.

→ 그 signal 번호가 하위 7 bit에 전달됨.

Case ③: child가 signal에 의해 stop.

→ 그 signal 번호가 상위 8 bit에 전달됨.

- exit() and wait()

․process를 종료시키는 system call.

⇔ 다른 경우: ① main()의 끝에 도달.

② main()에서 return()을 수행시: function의 끝으로써 함수 값을 돌려줌.

․모든 개방된 file descriptor를 닫고, clean-up action (e.g. buffer cleaning)을 수행.

․exit status: exit()으로 kernel에게 보내지는 인수.

⇒ 이 인수의 하단 8 bit는 wait()를 수행하고 있던 parent에게 보내짐.

ex) /* status - how to get hold of a child's exit status */

#include <stdio.h>

#include <stdlib.h>

main()

{	int pid, status, exit_status;

if ((pid=fork()) < 0){ fprintf(stderr, "fork failed..."); }

if (pid==0){	/* child exits with non-zero value. */

sleep(10); exit(5);

}

if (wait(&status)<0){  /* parent waits for the child */

fprintf(stderr, "wait failed..."); exit(1);}

/* parent test lower-order bits using bitwize AND */

/* NOTE: 0xFF is a mask: 0000 0000 1111 1111*/

if ((status & 0xFF)!=0){

fprintf(stderr,"some low order bits non zero\n"); /*Case②,③*/

} else{	/* case ① */

exit_status = status>>8; /* shift high order bits rightward */

exit_status &= 0xFF;  /*mask off unwanted high order bits*/

printf("exit status from %d was %d\n", pid, exit_status);

}	/* exit_stauts = 5 */

exit(0);

}

- The following steps are done by the kernel when a child process exits.

init

parent

child

never terminates. (PID = 1)

fork()

receive the child's status.

exec()

exit(status)

fork() & wait(&status)

exec()

⇒ 정상적인 종료의 경우.

① parent의 wait()가 수행되고 있는 상태에서 child가 exit().

② parent보다 child가 먼저 종료.

- 불완전한 종료의 경우

Case 1:	parent가 아직 wait()를 수행하고 있지 않은 상태에서 child가 종료	할 때.

⇒	child는 zombie가 됨. (zombie: PCB만 갖고 있으면서, kernel의	memory를 사용하지 않는 process, 부두교 - 죽었다가 되살아	난 사람.)

⇒	parent의 wait()가 나중에 수행되면 zombie는 시스템에서 제거	되고, kernel은 parent에게 SIGCLD signal을 보내어 parent는	child의 종료 상태를 얻을 수 있다.

Case 2:	하나 이상의 child가 수행되고 있는 상태에서 parent가 "먼저" 종료할 때.

⇒	parent는 정상적으로 종료하며, child(zombie도 포함)는 init의	자식이 됨.

⇒	parent PID = 1

▪ exec()

- An existing process can execute a program by exec().

- exec() “replaces” the current process with the new program.

⇒ The PID does not change.

- exec() return to the caller only if an error occurs.

Otherwise, control passes to the start of the new program.

⇒ exec()은 현재의 process image 위에 수행가능한 file의 image를 덮어 씌운 다음 제어를 image의 처음으로 넘겨 주기 때문에 calling process로의 복귀가 없다.

ex) runls.c

printf("1\n");

execl("/bin/ls");

PC

exec() 후.....

/* first line of ls */

……

PC

ls

exec() 수행 전

P

user

block

kernel area

user

image

user area

exec() 수행 후

P

new

user

block

kernel area

user area

new

user

image

- six types of exec()

p.58 fig.2-5

arg[0] - blank or "date"

ex)	execl("/bin/date", " ", (char *)0);      /* /bin에 date가 없으면   */

execl("/usr/bin/date", " ", (char *)0);  /* /usr/bin의 date를 수행 */

fprintf(stderr, "date error...\n");	/* 그래도 없으면, error    */

ex)	#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

main(argc, argv)

int argc;	char *argv[];	32-bit null ptr (c.f. 0: 16-bit integer,

{	⇒ 32-bit computer의 경우 error.)

execlp("more", " ", argv[1], (char *)0);  /* PATH에서 탐색 */

execlp("date", " ", (char *)0);	/* not processed!  */

exit(0); }

- The values of the new program that is the same as the values of the	caller.

The values of the new program that differs from the values of the caller.

⇒ p.59

⇒ set-user-ID가 set되어 있는 경우, eff. user ID가 달라짐.

set-group-ID가 set되어 있는 경우, eff. group ID가 달라짐.

- signal handling

① Any signals that were set to "terminate" the calling process will "terminate" the new program.

② Any signals that were set to be "ignored" by the calling process will be "ignored" by the new program.

③ Any signals that were set to be "caught" by the calling process's signal handler will "terminate" the new program.

⇒ Why? No signal handler in the new program.

ex)	argument list: execl(path, arg, …, (char*)0) & execv(path, argv)

/*runls1 - use execl to run ls */

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

main()

{	execl("/bin/ls", "ls", "-l", (char*)0);

fprintf(stderr,"execl failed...\n");

exit(1);

}

/*runls2 - use execv to run ls */

main()

{    char *av[3];

av[0]="ls";	av[1]="-l";  av[2]=(char*)0;

execv("/bin/ls", av);

fprintf(stderr,"execv failed...\n");

exit(1); }

ex) /* runls3 - run "ls" in a child */

#include <stdio.h>

main()

{	int pid;

pid=fork();

/* if parent, use wait() to suspend execution until child finishes */

if(pid>0){

wait((int*)0);	/* not receive status	*/

printf("\n Parent: ls completed\n");

exit(0);

}

if(pid==0){	/* if child, exec "ls" */

execl("/bin/ls","ls","-l",(char*)0);

fprintf(stderr,"execl failed\n");

}

fprintf(stderr,"fork failed\n");

}

⇒ command processor(/bin/csh 등)를 만드는데 이용할 수 있다.

Process Relationships

▪ How Unix starts its operation for multiuser?  (file의 path는 system마다 다를수 있음.)

init

The kernel invokes the program /etc/init.

PID=1, user ID=0 (login name=root)

- Executes the shell script file /etc/rc (Checks memory and disk system and starts the normal daemon processes - lpd, inetd. cron 등)

- Reads the file /etc/ttydefs to determine the terminals to be activated.

init

/etc/getty sets the terminal speed, outputs a greeting message and waits for a user to enter login name.

login name:

getty

fork()

exec()

login

- /etc/login looks up the login name in the /etc/passwd file and prompts the user for a passwd, if required.

- After authorization, sets the c.w.d. to the login directory field from /etc/passwd using chdir().

- Sets the group ID and the user ID to the values specified in /etc/passwd using setuid() & setgid() to "lower" the privilege of the process.

- exec() the shell program specified in the /etc/passwd.

If not specified, /bin/sh is executed.

kernel

process

A group leader and a terminal control process.

- Prompts the user for a command.

exec()

exec()

Look for an executable file with the name "date' using PATH defined in .cshrc file in the user's home directory.

fork() & wait()

user

process

csh

csh

date

exec()

Job-Contol Shell

▪ A feature of 4.3 BSD that allows a user to generate multiple jobs (process groups) "from a terminal" and have some control over their execution.

▪ Bourn shell: a non job-control shell.

C-shell (under 4.3 BSD): a job-control shell.

ex) p.67 fig.2-11

⇒ Bourn shell: all processes belongs to the same process group.

ex) csh: a job-control shell

A child process can remove itself from the p.g. (process group) using setpgrp().

⇒ This makes the setpgrp()-calling process a p.g. leader in its own p.g., and any processes created by this child will, by default, belong to this new p.g.

csh

csh

vi

exec()

csh

make

make

cc

fork()

fork()

fork()

exec()

exec()

process group

new

process

group

new

process

group

new

process

group

# make > make.out 2>&1 &    2(stderr) 출력을 1(stdout→make.out)로 변경.

1(stdout)을 file(make.out)로 변경. (>>: stdout을 기존file에 append.)

/* compiler and linker를 background로 실행 */

# vi main.c

ex) p.68 ～ p.69

examples of

① non job-control shell: "pgrp="가 모두 같음.

② job-control shell: "pgrp="가 모두 다름.

⇒ makes each child the leader of its own process group.

▪ The sending of signals

ex) kill(0, sig);

⇒ sends the signal to all processes in the sender's process group.

- Non job-control shell: the signal is sent to any child processes that belong to the process group of the login shell.

- Job-control shell: a child process of a login shell is not affected.

Daemon(=Demon) Processes

▪ A process that executes in the "background" (i.e. without an associated terminal or login shell)

⇒ Either waiting for some event to occur, or waiting to perform some specified task on a periodic basis.

ex) lpd, inetd(xinetd), httpd, sendmail 등.

▪ When a daemon process starts?

① Most system daemons: During system start-up. (booting시)

⇒ Defined in /etc/rc (written in shell script, executed by /etc/init)

② Periodic daemons: Invoked on a periodic basis. - cron daemon

⇒ Defined in /usr/lib/crontab file executed by the cron program (clock daemon) in /etc/rc

⇒ User의 crontab file 제공: Only system V not 4.3 BSD.

ex) In the crontab file.

13 3 * * * <shell script>	/* at 03:13 am everyday */

15 3 * * 0 <shell script>	/* at 03:15 am on Sundays */

⇒ The cron command starts a process that executes commands at       specified dates and times.

Regularly scheduled commands can be specified in crontab files.

⇒  Programs executed only once may be submitted using "at".

③ Non-periodic daemons: Executed by the 'at' command. (at, batch - execute a command or shell script at a specified time)

⇒ 주기적이 아니고, 특정한 시간이 되면 작동.

ex) # at 8:15 am Jan 24 <shell file>

at now+1 day <shell file>

④ From a user terminal: By background job. (release a terminal)

⇒ When testing a daemon.

▪ "System" daemons have the following characteristics.

- They are started once, when the system is initialized.

- Their lifetime is the entire time that the OS is operating.

- They wait for some event to occur at which time they perform their service.

- They frequently spawn other processes to handle service requests.

ex) RAM 상주 programs in Windows

ex) p.334 fig.6-16, inetd(Internet superserver)

⇒ ftpd, telnetd, rlogind들을 invoke함.

(Linux의 경우: xinetd ⇒ telnetd, ftpd, sshd을 제어)

① Before 4.3 BSD:

- 'many daemons' exists.

- each process did nearly identical start-up task.

- to handle a request, only fork() is called.

ftpd

telnetd

rlogind

ftpd

fork()

② 4.3 BSD:

- a 'single' process (inetd) to be waiting.

⇒ reduces the total no. of processes in the system.

- many of the start-up details are handled by inetd "once" before the actual server is invoked.

- to handle request, fork() & exec() is called.

inetd

inetd

inetd

inetd

ftpd

telnetd

rlogind

fork()

exec()

fork()

fork()

exec()

exec()

ex) Programming a "server" process in Unix (= daemon process)

⇒ p.82 skeleton daemon: a set of rules for coding a robust daemon.

⇒ daemon_start(): a daemon should call when it is started.

① Close all open file descriptors.

② Change current work directory into "/".

③ Reset the file access creation mask.

④ Run in the background.

⑤ Disassociate from process group.

⑥ Ignore terminal I/O signals.

⑦ Disassociate from control terminal.

⑧ Don't reacquire a control terminal.

▪ "Server"의 두 가지 의미

① Server "computer"

ex) Unix server, Windows server, Linux server

② Server "process"

Mail server, Web server, DB server 등

(⇒ 각각의 server computer, 혹은 하나의 server computer에 있을 수 있음.)

ex) lpd - Printer daemon, printing service

ftpd - ftp daemon, file transfer service (FTP)

sendmail - Mailer daemon, mail service (SMTP)

telnetd - Telnet daemon, remote login service (TELNET)

httpd - Web server, WWW service (HTTP)