2015년 10월 4일 일요일

2015년10월4일 정민이 결혼.

정민이가 호텔수성에서 결혼했다.

아침 일찍 울산 전하관 기숙사에서 일어나서, 아침을 먹고 호텔수성으로 운전했다.
축의금을 받는 역할을 하기로 했기 때문에 서둘렀다.

결혼식이 12시 30분인데, 11시 이전에 도착했다.
일찍 도착하니 주차장에 자리가 많아서 좋았다.

준흠이형이랑 같이 축의금 받는 일을 했는데, 처음 경험했다.
갑자기 손님들의 축의금 봉투가 여기저기서 들어와서 번호매길 때, 헛갈렸다.

혹시나 봉투를 분실할까, 이상한 사람이 차비 달라고 하지 않을까...
뉴스에서 들어보았던 이야기들을 생각하며 조심하려고 했다.

손님들에게도 식권을 나눠줄 때도, 신속히 드리지 못하고, 한 장 두장 ... 열 장
천천히 세다 보니 시간이 오래 걸렸다.

여튼... 정민이 결혼식을 제대로 보지 못했고, 준흠이형이랑 이런 저런 이야기하다 보니
친지 사진 찍는 시간이었다. 하하하.

정흠이는 애봐야 해서, 길흠이도 회사 출근 때문에 식이 마치자 마자 갔다.
나와 심훈이와 준흠이 형... 이렇게만 남았다.

아뭏든 결혼식 끝나고 삼촌댁인 침산 명성푸르지오로 갔다.
큰아버지, 고모, 삼촌들이 돌아가신 할머니, 할아버지 말씀하시면서,
이런 저런 옛날 이야기 하시는 걸 보니, 보기 좋았다.

문득... 보름이는 ???
엄마 아빠가 죽고 나면, 엄마 아빠에 대해서 함께 이야기할 누군가가 없어서
너무 외로울 것 같다는 생각이 들었다.

엄마 아빠에 대해서 함께 이야기 할 수 있는 누군가가 있다는 것...

글쎄... 엄마 아빠가 죽어서도 그런 이야기를 할 수 있는 형제가 있다는 것...

보름이에게 미안한 생각이 들었다.





2015년 8월 1일 토요일

fork를 이용한 client 연결 처리 시, cleint 와 연결 종류 후, 부모 process 에 전달되는 SIGCHLD 신호 때문에 accept 함수가 -1 을 반환할 때, 처리.

linux 소켓 프로그래밍을 하면서, listening socket 을 만든 후,
client 가 연결을 요청을 하면, accept 함수를 이용하여,
client 와 연결되는 새로운 socket 을 동작시킨다.

이 accept 라는 함수는 기본적으로 blocking 함수이다.
만약 listening socket 에 연결된 연결 요청 대기 큐에
아무런 요청이 없다면,
이 accept 함수는 client 의 연결 요청이 있는 시점까지, 그 다음의 code 를 진행시키지 않고 대기하게 된다.

그런데 이 blocking 으로 동작하는 accept 함수가 새로운 client 의 요청을 기다리고 있는 동안에 이 프로세스가 signal(신호) 를 받아서, 기존 signal함수 또는 sigaction 함수로 등록해둔, signal handler 함수가 갑자기 수행해야 되는 상황이 발생하면 어떻게 될까?

( blocking system call 은 다음과 같은 상황에서 빠져나온다고 한다.
1. 기능수행성공
2. 기능수행실패
3. 시그널을 받음 )

바로 이점이 내가 몰랐던 부분이다.

그러면, signal handler 에 해당하는 함수를 다 처리하고, 해당 process 는
원래 blocking 상태에 있던 accept 를 수행하던 곳으로 돌아오게 된다.
원래 accept 가 있던 곳에서 새로운 client 의 요청이 있을 때까지 기다리는 상태를 지속하는 것이 아니라, accept 함수는 error 를 의미하는 -1 을 return 한다.

그리고 이 때, errno 를 확인하면,
4번, EINTR [ error : Interrupt ]이 발생한다.
accept 의 man 페이지를 열어서, 언제 EINTR이 발생하는지 보연 아래와 같이 적혀 있다.

The system call was interrupted by a signal that was caught before a valid connection arrived

즉 client 쪽에서 connect 함수를 호출하고, server 쪽에서 blocking 상태에 있던 accept 함수가 동작하는 connection 이 발생하기 이전에,  signal 이 발생하여, blocking 상태에 있던 accept 함수의 수행이 중단되었다는 의미이다.


그럼 좀 더 예제의 구체적인 상황을 설명해 보자.
fork 를 이용하여, 프로세스 기반의 다중 접속 서버를 구현할 경우,
parent 프로세스는 accept 를 통해서 client 연결이 인식되면, 해당 client 와의 작업을,
fork 를 이용하여, 별도의 프로세스로 만들어서 처리하게 된다.

그리고 parent 프로세스의 경우,  accept 함수를 만나서 또 다른 client 연결 요청을 기다리며, blocking 상태에 있게 된다.

그런데, 이전에 처리되었던 child process 가 작업을 끝내고 종료할 경우,
부모 프로세스는 자식의 프로세스가 종료되었음을 알리는 SIGCHLD 신호를 받게 된다.

SIGCHLD 신호를 받게 되면, accept 에서 block 되어 client 의 연결 요청을 기다리는 일 대신, sigaction 에 등록해 두었던 signal handler 함수가 실행된다.
그리고 이 실행이 끝나면, OS 는 accept 함수의 반환 값으로 -1 을 돌려주고,
errno 에 EINTR 을 적어주게 된다.

그렇기 때문에 parent 프로세스 코드를 짤 때, accept 함수가 -1 을 반환하고,
errno 이 EINTR 인 경우에는, 다시 accept 함수를 실행하여
client의 연결 요청을 기다릴 수 있도록 해주어야 한다.

그래서 code 에는 continue 가 있는 것이다!


원래 기본 코드는
윤성우 님의 "열혈 TCP/IP 소켓 프로그래밍" 의
Chapter10 멀티프로세스 기반의 서버구현 을 가지고 왔다.
윤성우님의 책을 보면서 공부하다가, 내가 실행하면서, 공부를 하다가
나처럼 잘 모르는 사람을 위해서 이런 부분의 설명이 있었으면 더 좋았을텐데...
하는 생각이 들어서 블로그에 정리하게 되었다.

윤성우님의 책에서 필요한 정보를 많이 얻어서 감사하고, 존경한다.
다음 판이 나올 때는 blocking system call 이라는 개념에 대해서도 설명이 함께
있으면 독자들이 더 쉽게 이해할 수 있지 않을까 한다.

원래 코드에서 getpeername, getsockname 등을 이용하여,
연결 발생 시, 서버와 클라이언트의 IP 및 port 정보를 출력하도록 하였다.

그리고 waitpid 의 man 페이지에 나와 있는 sample 코드를 이용하여,
kill -STOP
kill -CONT
등의 신호도 실습할 수 있도록 변경하였다.

그리고, 부모 프로세스에 sigaction 을 이용하여, signal handler 를 등록해 두면,
fork 에 의해 생성된 자식 프로세스에도 동일한 signal handler 가 동작된다는 것을
알게 되었다.

client 와 연결이 있을 때,
kill -CHLD  
명령을 이용하여 SIGCHLD 신호를 줄 때와




client 와 연결이 없을 때,
kill -CHLD  
명령을 이용하여 SIGCHLD 신호를 줄 때 약간 양상이 다르다.


이는 waitpid 의 기능때문이다.


echo_mpserv.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <wait.h>
#include <signal.h>
#define BUF_SIZE 1024
void error_handling( char * msg, int exit_on )
{
    fputs( msg , stderr);
    fputc( '\n', stderr);
    fprintf(stderr, "errno num : %d\n", errno  );
    fprintf(stderr, "errno msg : %s\n", strerror( errno ) );
    if( exit_on )
    {
        exit(1);
    }
}
void proc_child(int sig)
{
    pid_t pid;
    int status;
    pid = waitpid( -1, &status, WUNTRACED | WCONTINUED | WNOHANG );
    //pid = waitpid( -1, &status,  WNOHANG );
    if( pid == -1 )
    {
        error_handling("waitpid() error!"0 );
    }
    printf("=====================\n");
    if( WIFEXITED( status ) )
    {
        printf("child process : %d exit, ret value : %d\n", pid, WEXITSTATUS( status ) );
    }
    else if( WIFSIGNALED(status) )
    {
        printf("child process : %d killed by signal %d\n", pid, WTERMSIG(status) );
    }
    else if( WIFSTOPPED(status) ) 
    {
        printf("child process : %d stopped by signal %d\n", pid, WSTOPSIG(status) );
    }
    else if( WIFCONTINUED(status) )
    {
        printf("child process : %d continued\n",pid );
    }
    else
    {
        printf("Unexpected Flow!\n");
        printf("Unexpected Flow!\n");
        printf("Unexpected Flow!\n");
    }
    printf("=====================\n");
}
int main( int argc, char * argv[] )
{
    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    struct sockaddr_in SERV_addr_tmp;
    struct sockaddr_in clnt_addr_tmp;
    struct sockaddr_in serv_addr_tmp;
    int sock_serv;
    int sock_clnt;
    socklen_t sock_size;
    socklen_t sock_size_tmp;
    pid_t child_pid;
    char buffer[ BUF_SIZE ];
    struct sigaction act ;
    act.sa_handler = proc_child;
    sigemptyset( &act.sa_mask );
    act.sa_flags = 0;
    sigaction( SIGCHLD, &act, 0 );
    if( argc != 2 )
    {
        error_handling( "[usage] echo_mpserv "1 );
    }
    memset( &serv_addr, 0sizeof(struct sockaddr_in) );
    serv_addr.sin_family      = AF_INET;
    serv_addr.sin_addr.s_addr = htonl( INADDR_ANY );
    serv_addr.sin_port        = htons( atoi( argv[1] ) );
    sock_serv = socket( PF_INET, SOCK_STREAM ,0 );
    if( sock_serv  == -1 )
    {
        error_handling( "socket() fail!"1 );
    }
    if( bind( sock_serv, (struct sockaddr *) &serv_addr, sizeof(serv_addr) ) == -1 )
        error_handling( "bind() fail!"1 );
    if( listen( sock_serv, 5 ) == -1 )
    {
        error_handling( "listen() fail"1 );
    }
    sock_size_tmp = sizeof( SERV_addr_tmp );
    if( getsockname( sock_serv, (struct sockaddr *) &SERV_addr_tmp, &sock_size_tmp) == -1 )
    {
        error_handling( "getsockname() error!"1);
    }
    else
    {
        printf"SERVER listening IP : %s,  port %u\n"
                inet_ntoa( SERV_addr_tmp.sin_addr ),
                ntohs( SERV_addr_tmp.sin_port )
                );
    }
    while(1)
    {
        sock_size = sizeof( clnt_addr );
        sock_clnt = accept( sock_serv, (struct sockaddr *) &clnt_addr, &sock_size );
        if( sock_clnt == -1 )
        {
            //error_handling( "accept() fail", 1 );
            printf("=====================\n");
            error_handling( "accept() fail"0 );
            fprintf(stderr, "child process died?? why INTERRUPT??\n");
            printf("=====================\n");
            continue;
        }
        // client IP, port info.
                // Later, let's make this as a function!!! 
                // Later, let's make this as a function!!! 
        // int getpeername(int s, struct sockaddr *name, socklen_t *namelen);
        sock_size_tmp = sizeof( clnt_addr_tmp );
        if( getpeername( sock_clnt, (struct sockaddr * ) &clnt_addr_tmp, &sock_size_tmp ) == -1 )
        {
            error_handling( "getpeername() error!"1);
        }
        else
        {
            printf("=====================\n");
            printf"connected client IP : %s,  port %u\n"
                    inet_ntoa( clnt_addr_tmp.sin_addr ),
                    ntohs( clnt_addr_tmp.sin_port )
                    );
        }
        // Here get server ip, port!!!
        //int getsockname(int s, struct sockaddr *name, socklen_t *namelen);
        sock_size_tmp = sizeof( serv_addr_tmp );
        if( getsockname( sock_clnt, (struct sockaddr *) &serv_addr_tmp, &sock_size_tmp) == -1 )
        {
            error_handling( "getsockname() error!"1);
        }
        else
        {
            printf"          server IP : %s,  port %u\n"
                    inet_ntoa( serv_addr_tmp.sin_addr ),
                    ntohs( serv_addr_tmp.sin_port )
                    );
            printf("=====================\n");
        }
        if( ( child_pid = fork() ) == -1 )
        {
            error_handling( "fork() fail!"1 );
        }
        else if( child_pid == 0 ) // child
        {
            int read_num;
            pid_t child_pid;
            child_pid = getpid();
            printf("child PID : %u\n", child_pid );
            // if you don't close server-socket, check, /fd//fd
            close( sock_serv );
            while( ( read_num = read( sock_clnt, buffer, BUF_SIZE ) ) > 0 )
            {
                if( read_num == -1 )
                {
                    //error_handling( "child read() error!", 1 );
                    error_handling( "child read() error!"0 );
                    return 20;
                }
                write( sock_clnt, buffer, read_num );
            }
            
            close( sock_clnt );
            return 0;
        }
        else // parent
        {
            close( sock_clnt );
            continue;
        }
    }
    close( sock_serv );
    return 0;
}
cs


echo_clnt.c


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define BUF_SIZE 1024
void error_handling( char * msg, int exit_on )
{
    fputs( msg , stderr);
    fputc( '\n', stderr);
    fprintf(stderr, "errno num : %d", errno  );
    fprintf(stderr, "errno msg : %s", strerror( errno ) );
    if( exit_on )
    {
        exit(1);
    }
}
int main( int argc, char * argv[] )
{
    struct sockaddr_in serv_addr;
    int sock_serv;
    char buffer[ BUF_SIZE ];
    if( argc != 3 )
    {
        error_handling( "[usage] echo_clnt  "1 );
    }
    memset( &serv_addr, 0sizeof(struct sockaddr_in) );
    serv_addr.sin_family      = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr( argv[1]  );
    serv_addr.sin_port        = htons( atoi( argv[2] ) );
    sock_serv = socket( PF_INET, SOCK_STREAM ,0 );
    if( connect( sock_serv, (struct sockaddr *) &serv_addr, sizeof(serv_addr) ) ==  -1 )
    {
        error_handling("connect() fail!"1);
    }
    while(1)
    {
        int write_num;
        printf("insert sentence (q/Q to quit): ");
        fgets( buffer, BUF_SIZE , stdin );
        buffer[ strlen( buffer ) - 1 ] = 0;
        if!strcmp( buffer, "q" ) || !strcmp( buffer, "Q" ) )
        {
            break;
        }
        write_num = write( sock_serv, buffer, strlen(buffer) );
        read( sock_serv, buffer, strlen(buffer) );
        printf("'%s'\n", buffer );
    }
    close( sock_serv );
    return 0;
}
cs







아래는 확인을 위하여 도움을 얻은 사이트 및 발췌 내용.

https://kldp.org/node/22102


유닉스/리눅스는 blocking 시스템 호출이 있습니다.
전형적인 예가 select(), accept(), connect(), send(), recv()등인데요.
blocking system call들은 해당기능를 수행하기 까지는 멈춰있다는
의미로 해석해도 무방하겠습니다.
blocking system call은 다음의 상황에서 빠져나오게 됩니다.
1. 기능수행성공
2. 기능수행실패
3. 시그널을 받음





Blocking 의 의미.

https://www.justsoftwaresolutions.co.uk/threading/non_blocking_lock_free_and_wait_free.html
참조.

Definition of Blocking

A function is said to be blocking if it calls an operating system function that waits for an event to occur or a time period to elapse. Whilst a blocking call is waiting the operating system can often remove that thread from the scheduler, so it takes no CPU time until the event has occurred or the time has elapsed. Once the event has occurred then the thread is placed back in the scheduler and can run when allocated a time slice. A thread that is running a blocking call is said to be blocked.


A child created via fork(2) inherits a copy of its parent's signal dispositions. During an execve(2), the dispositions of handled signals are reset to the default; the dispositions of ignored signals are left unchanged.









EINTR
The system call was interrupted by a signal that was caught before a valid connection arrived; see signal(7).


Return Value

On success, these system calls return a nonnegative integer that is a descriptor for the accepted socket. On error, -1 is returned, and errno is set appropriately.

Error handling

Linux accept() (and accept4()) passes already-pending network errors on the new socket as an error code from accept(). This behavior differs from other BSD socket implementations. For reliable operation the application should detect the network errors defined for the protocol after accept() and treat them like EAGAIN by retrying. In the case of TCP/IP, these areENETDOWNEPROTOENOPROTOOPTEHOSTDOWNENONETEHOSTUNREACHEOPNOTSUPP, andENETUNREACH.




signal 의 man 페이지에서 발췌.
A child created via fork(2) inherits a copy of its parent's signal dispositions. During an execve(2), the dispositions of handled signals are reset to the default; the dispositions of ignored signals are left unchanged.

2015년 6월 27일 토요일

fork 를 사용해서 자식 프로세스 만든 후, 부모 프로세스가 먼저 죽으면, 자식 프로세스의 PPID(부포 프로세스 ID) 는 1 이 되는 시험.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
 
 
void error_handling( char *msg);
 
 
void read_childproc(int sig)
{
    pid_t pid;
    int status;
 
    pid = waitpid( -1, &status, WUNTRACED | WCONTINUED | WNOHANG );
    //pid = waitpid( -1, &status,  WNOHANG );
 
    if( WIFEXITED( status ) )
    {
        printf("child process : %d exit, ret value : %d\n", pid, WEXITSTATUS( status ) );
    }
    else if( WIFSIGNALED(status) )
    {
        printf("child process : %d killed by signal %d\n", pid, WTERMSIG(status) );
    }
    else if( WIFSTOPPED(status) ) 
    {
        printf("child process : %d stopped by signal %d\n", pid, WSTOPSIG(status) );
    }
    else if( WIFCONTINUED(status) )
    {
        printf("child process : %d continued\n",pid );
    }
    else
    {
        printf("Unexpected Flow!\n");
        printf("Unexpected Flow!\n");
        printf("Unexpected Flow!\n");
    }
}
 
 
 
int main(int argc, char * argv[] )
{
 
    pid_t pid_1;
    pid_t pid_2;
 
    struct sigaction act;
    act.sa_handler = read_childproc;
    sigemptyset( &act.sa_mask );
    act.sa_flags = 0;
 
    sigaction( SIGCHLD,  &act, 0 );
 
 
    printf("parent : proces ID : %3d\n",  getpid() );
 
    pid_1 = fork();
 
    if( pid_1 < 0 )
    {
        error_handling("fork() error");
    }
    else if( pid_1 == 0// child
    {
        int j;
 
        printf("child 1:  proces ID : %3d\n",  getpid() );
 
        for(j=0; j<30; j++)
        {
            sleep(10);
        }
        printf("child 1:  finish    : %3d\n",  getpid() );
        return 10;
    }
    else // parent
    {
        printf("parent : child 1 process ID : %d\n", pid_1 );
 
        pid_2 = fork();
        if( pid_2 < 0 )
        {
            error_handling("fork() error");
        }
        else if( pid_2 == 0 ) // child
        {
            int k;
            printf("child 2: proces ID : %3d\n",  getpid() );
            for(k=0; k<30; k++)
            {
                sleep(10);
            }
            printf("child 2: finish    : %3d\n",  getpid() );
            return 20;
        }
        else //parent
        {
            int i;
            printf("parent : child 2 process ID : %d\n", pid_2 );
 
 
            for(i=0; i< 10; i++ )
            {
                printf("wait...(%d)\n", i + 1);
                sleep(5);
            }
        }
    }
    
    return 0;
}
 
void error_handling( char *msg)
{
    fputs( msg , stderr );
    fputc( '\n', stderr );
 
    fprintf(stderr, "error number : %d\n", errno );
    fprintf(stderr, "error Msg    : %s\n", strerror( errno ) );
 
    exit(1);
}
 
cs


이 코드를 실행하면, 자식 프로세스가 먼저 죽는다.
(for 문의 반복횟수 및 시간 설정에 따라 조절 가능 )

그러면, 자식 프로세스의 PPID 번호가 원래 PPID 와는 달리 1 이 되어 있는 것을 확인할 수 있다.


먼저 프로그램을 실행하고
또 다른 terminal 에서 ps -ef | grep <부모 프로세스 PID>
를 입력하면,
부모, 자식 프로세스를 다 확인할 수 있다.

그리고 부모 프로세스가 죽고 나서
ps -ef 를 이용해
자식 프로세스를 확인하면, PPID 가 1 이 되어 있는 것을 확인할 수 있다.

팔로어