개발👩‍💻/시스템프로그래밍

프로세스 다루기 1(10-2-2)

gigibean 2021. 6. 5. 02:53
728x90
int main()
{
	int fd, n;
	char line[512];
	fd = open("data.txt", O_RDONLY);
	n = read(fd, line, 17); line[n] = '\0';
	fprintf(stderr, "before fork: %s", line);
	if (fork() > 0) { /* parent process */
		sleep(3);
		n = read(fd, line, 17); line[n] = '\0';
		fprintf(stderr, "parent process: %s", line);
	}
	else { /* child process */
		n = read(fd, line, 17); line[n] = '\0';
		fprintf(stderr, "child process: %s", line);
	}
	n = read(fd, line, 17); line[n] = '\0';
	fprintf(stderr, "after fork: %s", line);
}

포크된 부모프로세스와 자식프로세스가 오픈된 파일의 파일의 오프셋을 공유한다는 것을 확인해보기위한 프로그램을 짜보자.

data.txt라는 파일이 있다.

data.txt
Hello my world 1
Hello my world 2
Hello my world 3
Hello my world 4
Hello my world 5
Hello my world 6
Hello my world 7

위 파일을 리드모드로 연다 오픈이라는시스템을 이용해서

리트 시스템골을 이용해서 17바이트를 읽어들인다.

hello my world 1+\n 포함 17바이트이기 때문이다.

다시말해서 한 라인을 읽어들이는 것이 리드시스템콜에 의해 일어지는 일이다.

17바이트를 읽으라고 했으니 리턴되는 값도 17이 될 것이다.

 

그다음 

line[n] = '\0' 은

문자열은 항상 \0으로 끝나야하는데 위 데이터텍스트에서는 한줄에 \n으로 끝나지만 \0은 없다.

그래서 이것을 하지 않고 그냥 읽어들인대로 출력을하면 위에 값들에 쓰레기 값들이 나온다.

그래서 이부분을 막기위해 16번째 방인 \n뒤인 17번째 방에다가 \0을 넣어주기위해서

 

그래서 읽어들인 한라인의 내용을 before fork해서 집어넣었다.

그래서 'Hello my world 1" 이 출력된다.

 

그리고 if 문으로와서 fork() 리턴 값이 0 보다 크니까 부모 프로세스이고, else 부분은 자식프로세스가 실행할 부분이다.

 

부모와 자식이 포크에 의해 만들어졌다.

그런데 운영체제가 스케쥴링을 할 것이다. 이 때 부모, 자식 특수관계를 전혀 고려하지 않는다.

수많은프로세스들 중에 하나로 취급하기 때문에 전혀 고려하지 않고 스케줄링한다.

포크에 의해 자식이 만들어졌기 때문에 부모프로세스가 계속해서 이어나갈 가능성이 높다.하지만 포크를 하고 자식이 만들어지자 마자 컨텍스트 스위치가 일어나서 cpu를 뺏길 수도 있다. 뺏았긴 cpu를 자식프로세스가 가질 수도 있다.

자식이 만들어지고 자식이 먼저 실행될 수 있다.

부모, 자식이 어떤 순서로 실행되는지는 알수 없고 운영체제가 스케줄링하며 정하기 나름이다.

어떤 결정도 내릴 수 없다.

그런데 이제 자식프로세스가 먼저일어날수록 보장하기 위해서 부모프로세스를 3초동안 자게한다. 자는동안 아마 자식이 운영체제에 의해 cpu를 받을 가능성이 있을 것이라고 판단하여 먼저 실행될 수 있도록 한것이다.

여기서 sleep한 이유는 자식이 먼저 실행되게 하기 위해

 

그리고 자식이 먼저 한라인을 읽어들이고 이부분을 출력한다.

 

그리고 앞서 얘기했듯 오프셋을 자식과 부모가 공유하기 때문에 포크하기 전에 읽은 부분, 그 다음부터 자식이 읽기를 불러서 읽으면 그다음부터 자식에 의해 출력된다.

끝나고 계속 아랫부분을 실행할 수도, 아님 컨텍스트 스위치가 일어나서 부모가 가져갈 수도 있다.

그다음 또 자식이 부모보다 먼저 실행한다는 보장이 없지만 계속해서 실행해나아간다면 또 자식프로세스에서 after fork를 실행한다.  그리고 3초 있다 부모는 깨어나서 cpu를 받고 read불러 그다음 라인을 읽게된다.

hello ~~를 읽고 포크에 의해 자다가 일어나서 다시 부르는 것이지만 자식 프로세스가 그 사이에 오프셋을 공유하며 읽었기 때문에 부모프로세스는 자식이 읽은 뒤부터 읽게된다.

이어서 부모프로세스가 밑의 부분을 실행하기 때문에 한번더 hello~를 읽게되고 종료가 된다.

 

그래서 실행결과를 보게 되면

before fork: Hello my world 1
child process: Hello my world 2
after fork: Hello my world 3
parent process: Hello my world 4
after fork: Hello my world 5

포크하기전에 1이 출력이되고

포크한후 자식이 먼저 실행되고 마지막까지 실행된 후

3초후에 부모 프로세스가 실행된다.

 

그런데 비슷한 코드를 조금 다르게 짜보자.

시스템 콜이 아닌 라이브러리 함수를 가지고 파일을 열고 읽어보자

int main()
{
	FILE *fp;
	char line[512];
	fp = fopen("data.txt", "r");
	fgets(line, 512, fp);
	fprintf(stderr, "before fork: %s", line);
	if (fork() > 0) { /* parent process */
		sleep(3);
		fgets(line, 512, fp);
		fprintf(stderr, "parent process: %s", line);
	}
	else { /* child process */
		fgets(line, 512, fp);
		fprintf(stderr, "child process: %s", line);
	}
	fgets(line, 512, fp);
	fprintf(stderr, "after fork: %s", line);
}

그리고 나서 읽어들인 그 라인을 출력을 한다.

그리고 포크를한다. 부모는 3초 잔 후에 라인을 읽고 출력한다. .. 로직상 앞 코드와 똑같으나 

시스템콜이 아닌 라이브러리를 사용했다는 차이가 있다.

 

이렇게 했을 때 앞의 결과와 다르게 나온다.

before fork: Hello my world 1
child process: Hello my world 2
after fork: Hello my world 3
parent process: Hello my world 2
after fork: Hello my world 3

포크하기 전에 라인을 읽어들여 출력을 하고,

그다음에 부모 자식이 생기고, 부모가 자는동안 자식이 2라인을 읽어들인다 

그다음 자식이 마지막 부분을 실행하며 3을 읽어들인다.

그래서 자식프로세스가 여기가지 읽고 부모프로세스가 실행되는데 라인을 읽어들인때

다시 2를 읽는다. 그리고 3을 읽는다.

왜이렇게 될까?

로직상 동일한데 시스템콜, 라이브러리 함수 호출의 차이가 왜 이런 결과를 야기하는 것일까?

이것은 예전에 배웠다 싶이 라이브러리함수는 buffered I/O를 하는데 반해 시스템콜은 버퍼를 사용하지 않고 I/O를 하기 때문이다.

처음에 fgets해서 파일 읽어들이면, 버퍼를 가리키는 포인터가 있는데, 파일을 오픈했을 대는 이버퍼가 비어있다. 근데 이함수를 불러 읽어들이면 버퍼를 채운다. 

data.txt 내용을 다 읽어서 버퍼에 들어간다.

이상태에서 한 라인을 읽어서 포크하기전에 1을 읽은 상태로 표시가 되어있고 포크를했다.

그리고 자식프로세스를 만들었다.

자식프로세스를 만들면서 data.txt가 카피가된다. 왜냐면 이 내용은 앞에서 프로세스 실행시키기위해 메인메모리를 가져올 때 메이메모리에 txt부분과 data 부분이 있다고했는데, FILE struct는 다 data에 저장된다. 포크에서 자식이 만들어지면 data 부분을 복사해가기 때문에 이 FILE struct 부분도 복사가 된다.

그래서 포크하기 전 읽은 1 부분 표시가 되어 있는 것을 자식이 복사해 가는 것이다.

자식도 파일이라는 구조체에 버퍼를 나타내는 포인터가 있고 그 안에 data.txt를 일겅들인 내용이 복사가 되어 있고, 1까지 읽었다는 정보가 기록이 되어 있다.

그상태에서 라인을 읽어들이면 위와같은 결과가 나오게 되는 것이다.

 

그리고 부모프로세스도 fgets 하게되면 부모 data에 있는 file 구조체에서 읽어들이게 되어서 2, 3을 읽게되는 것이다.

 

그래서 내용적으로 두개가 앞에 시스템콜, 라이브러리 사용하는 것 같지만 위와 같은 이유로 다른 결과를 얻게 된다.

 

반응형