'Computer_language/C'에 해당되는 글 8건

  1. 2009.02.04 Thread 내의 usleep 의 문제
  2. 2009.01.12 Select를 이용한 채팅 서버
  3. 2009.01.12 소켓 주소 구조
  4. 2009.01.12 포인터 far near huge
  5. 2009.01.12 바이트 오더(Byte Order)에 대해서...
  6. 2009.01.12 [Transformation] htons [출처] [Transformation] htons|작성자 임종배
  7. 2009.01.12 [Transformation] htonl [출처] [Transformation] htonl|작성자 임종배
  8. 2009.01.12 max, min 함수 C / C++

Thread 내의 usleep 의 문제

|

이번에 OpenSUSE 11.0 으로 업그레이드하면서 이상한 현상이 발생하였습니다.

10.3에서 잘돌던 프로그램이 11.0으로 오면서 CPU 가 150% 이상 점유하는 문제였습니다.

그래서 결국에 문제를 찾기위해서 프로그램을 디버그해보았습니다.

 

문제가 생긴 부분은 thread에서 cpu점유를 막기위해서 사용한 usleep의 문제였습니다.

 

usleep(10)이라 해서 10us 만 쉬도록 되어있던 코드가 문제였습니다.

 

일단 thread내에서 cpu 점유율을 낮추기 위해서 10us를 쉬도록 한것 자체가 성립되지 않는 문제입니다.

그 이유는 man usleep 을 해보면 자세히 나와있고 sleep에 얽힌 이야기들 이란 글에 잘 나와있습니다.

 

 대부분의 UNIX OS(특히 Linux) kernel timer는 1/HZ정도로 맞추어져있으며, 
   
한 thread내에서 sleep등으로 context switching이 일어날 경우 다른 thread를 

수행하고 다시 현재의 thread로 돌아오는 시간은 최소 > 10ms가 

되며, 이것은 Intel Platform에서 지원하는 timer 주기와 맞물린다고 한다.

(이에 대해선 linux의 jiffies설정과 기타 문서 참조.)

따라서 실제로 thread내에서 usleep이든, nanosleep이든 sleep할 수 있는 최소의

시간은 >1/HZ(10ms intel)가 되어야하며, 이것은 1/100초 즉, 10msec라고 할 수 있다.

실제 sleep할 수 있는 시간은 10msec가 아닌 60msec정도가 한계인 경우(irix,solaris 

혹은 linux)도 있다.  이것은 또한 사용자 레벨에서 줄 수 있는 최소의 sleep time이라 

볼 수 있다.

 

usleep의 숫자를 늘린 뒤에는 (10ms이상으로) CPU 점유율이 정상적으로 돌아왔습니다.

 

glibc 소스를 받아서 10.3과 11.0의 usleep의 구현을 봐도 동일합니다.

(usleep 구현이 내부적으로 nanosleep을 호출하도록 되어있더군요. 구현이 틀리다는 내용도 있었는데 실제 보니.같았습니다.)

KLDP의 글에서 살짝이나마 예상을 할수 있었습니다.

 

usleep는 deprecated된 함수이고,

일반적으로 nanosleep을 사용하라고 권고하고 있습니다.

내부적으로 usleep은 select()함수를 이용하여 구현되었었다고

알고 있고, select를 직접 쓰시는 것과 큰 차이가 없습니다.

그리고, 한 쓰레드가 usleep을 한다고 해서 다른 쓰레드가 모두

정지하는 것이 아니고, 쓰레드의 스케쥴링 스코프가 SYSTEM

(BOUND Thread)일 경우에는 전혀 상관이 없습니다.

물론 PROCESS 스코프일 경우에도 SUN의 경우는 LWP Level을

자동으로 올려 그런 경우가 없도록 구현해 놓았습니다.

그리고, usleep이 Thread unsafe하다는 이야기는 처음 듣는

것이고, Thread unsafe한 함수의 경우 해당 플랫폼의

매뉴얼이나 Thread 프로그래밍 문서에 잘 나와있습니다.

(Signal Unsafe한 경우도 고려해야 합니다.)

nanosleep의 경우 사실상 CPU를 적게쓰기 위해 호출하는

것은 적절하지 않다는 생각이 듭니다.

문맥교환의 기본 시간단위가 mili second인데 nano second

단위로 Sleep을 하려면 CPU를 소모하면서 busy wait을

한 이후에 깨어나는 수 밖에는 없어서 그렇습니다.

개인적으로 테스트해 본 결과, select()보다 nanosleep()의 경우

CPU를 때때로 더 많이 사용하는 경우를 맞닥드린 경우도

있습니다. (물론 개인적인 테스트이기 때문에 편차가 있을 것으로

생각합니다만)

만일 CPU 점유율을 낮추기 위해 sleep을 하시려면

제가 보기에는 select() 관련 함수가 가장 적합해 보입니다.

좋은 결과 있기 바랍니다.

 

좀 오래된 글이라 (2003년) 안 맞는 이야기가있긴합니다.(usleep은 deprecated되었다 라던지. usleep구현이 틀리다. 현재 glibc 소스를 보면 usleep내부적으로 nanosleep로 되어있습니다.)

위의 내용에서 u sec의 수준으로 sleep을 하는 경우에 CPU를 소모하면서 busy wait를 한다고 되어있습니다.

아마 이게 주 원인이 아닐까 싶습니다.

 

결론적으로 thread내의 usleep의 10ms 이하의 sleep은 의미가 없고 OpenSUSE 11.0에서는 더 많은 cpu를 사용하는

문제가 생깁니다.

 

ps: 아직까지 11.0의 무엇이 이 원인을 생기게 했는지는 모르겠습니다. 누구 아시는 분은 좀 알려주세요.

 

 

추가의 글..

 

nanosleep의 manual을 보면 최소의 resolution은 1HZ라고 되어있습니다.

 BUGS
       The current implementation of nanosleep() is based on the normal kernel timer mechanism, which has a resolution of 1/HZ s (see time(7)).  Therefore, nanosleep() pauses always for  at  least  the
       specified  time, however it can take up to 10 ms longer than specified until the process becomes runnable again.
  For the same reason, the value returned in case of a delivered signal in *rem is
       usually rounded to the next larger multiple of 1/HZ s.

   Old behavior
       In order to support applications requiring much more precise pauses (e.g., in order to control some time-critical hardware), nanosleep() would handle pauses of up to 2 ms by  busy  waiting  with
       microsecond  precision  when called from a process scheduled under a real-time policy like SCHED_FIFO or SCHED_RR.  This special extension was removed in kernel 2.5.39, hence is still present in
       current 2.4 kernels, but not in 2.6 kernels.

       In Linux 2.4, if nanosleep() is stopped by a signal (e.g., SIGTSTP), then the call fails with the error EINTR after the process is resumed by a SIGCONT signal.  If  the  system  call  is  subse-
       quently restarted, then the time that the process spent in the stopped state is not counted against the sleep interval.

 

 

man 7 time을 해보면 다음과 같은 글이 있습니다.

  The Software Clock, HZ, and Jiffies
       The accuracy of many system calls and timestamps is limited by the resolution of the software clock, a clock maintained by the kernel which measures time in jiffies.  The  size  of  a  jiffy  is
       determined  by  the  value  of the kernel constant HZ.  The value of HZ varies across kernel versions and hardware platforms.  On i386 the situation is as follows: on kernels up to and including
       2.4.x, HZ was 100, giving a jiffy value of 0.01 seconds; starting with 2.6.0, HZ was raised to 1000, giving a jiffy of 0.001 seconds; since kernel 2.6.13, the HZ value is a kernel  configuration
       parameter and can be 100, 250 (the default) or 1000, yielding a jiffies value of, respectively, 0.01, 0.004, or 0.001 seconds.  Since kernel 2.6.20, a further frequency is available: 300, a num-
       ber that divides evenly for the common video frame rates (PAL, 25 HZ; NTSC, 30 HZ).

 

위의 글들이 좀 오래된것이라 2001년 2003년.. 현재 상황과는 안 맞는 부분이 있습니다.

 

 

테스트 하나)

OpenSUSE 11.0과 OenSUSE 10.3에서 usleep(1)을 했을때 얼마 정도 지나야 깨어나는지 테스트해보았습니다.

 #include <unistd.h>
#include <sys/time.h>
#include <stdio.h>

int main()
{
        struct timeval tv;

        gettimeofday(&tv,NULL);
        printf("%d %d\n", tv.tv_sec, tv.tv_usec);
        for(int i=0; i<10; i++)
        {
                usleep(1);
        }
        gettimeofday(&tv,NULL);
        printf("%d %d\n", tv.tv_sec, tv.tv_usec);
}

 

OpenSUSE 10.3 (평균 7382us)
1215153330 337156
1215153330 415476

 

OpenSUSE 11.0(평균 9.4us)

1215153341 235090
1215153341 235184

보면 usleep의 resolution이 11.0이 상당히 높음을 알수 있습니다.

결국 OpenSUSE 11.0에서 usleep 을 사용하여 thread를 쉬게 한뒤에 넘겼을 경우에 빠른 시간내에 제어권을 가지고 오기 위해

CPU를 사용했다는것을 알수 있습니다.

 


And

Select를 이용한 채팅 서버

|
select function
 
NAME
                            select
SYNOPSIS
                            #include <sys/socket.h>
                            #include <sys/time.h>
                            int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout)
RETURN
                            Return : descriptor count, 0 on timeout, -1 on error


기능 : process가 kernel에게 명령하여 다수의 event 중 하나를 기다리다 지정된 시간이 지나가거나 사건이 발생하면 process를 깨우도록 함
maxfd - 감시할 소켓의 총 갯수 +1
fd_set - 조사 대상의 descripter를 저징할 변수
struct  timeval
{
     int tv_sec;           /* second */
     int tv_usec;         /* microsecond */
};
      하나라도 준비 될 때까지 얼마나 기다려야 하는지 명시
      NULL - 영원히 기다린다.
      0 - 기다리지 않는다.

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <netinet/in.h>
#include <string.h>

#define MAXLINE 512
#define MAX_SOCK 64
char *escapechar = "exit";
int maxfdp1;
int num_chat = 0;                                                                               /* 채팅 참가자 수 */
int client_s[MAX_SOCK];
/* 채팅 탈퇴 처리 */
void removeClient(int i)                                                                        /* i번째 참가자 탈퇴 */
{
 close(client_s[i]);                                                                         /* 해당 소켓 닫기 */
 if(i !=num_chat -1)
  client_s[i] = client_s[num_chat -1];                                                    /*  i번째 참가자와 맨 마지말 참가장의 위치 switch*/
 num_chat --;                                                                               /* 채팅 참가자 수 1명 줄이기 */
 printf("채팅 참가자 1명 탈퇴. 현재 참가자 수 = %d\n",num_chat);
}
/* client_s[]내의 최대 소켓번호 얻기 (k는 초기치)*/
int getmax(int k)
{
 int max = k;
 int r;
 for(r = 0; r < num_chat ; r++)                                                              /* 채팅 참가자 수만큼 소켓 배열 탐색 */
  if(client_s[r] > max)
   max = client_s[r];
 return max;
}
int main(int argc, char *argv[])
{
 char rline[MAXLINE], my_msg[MAXLINE];                                                       /* buffer 생성 */
 char *start = "Connetctd to chat_server\n";                                                 /* 최초 연결시 보내는 메세지 */
 int i,j,n;
 int s, client_fd, clilen;
 fd_set read_fds;
 struct sockaddr_in client_addr, server_addr;                                                /* 소켓주소 구조체 선언 */
 if(argc != 2)                                                                               /* 포트번호를 입력 안했을 경우 */
 {
  printf("사용법 : %s port\n", argv[0]);
  exit(0);
 }
 /* 초기소켓 생성 */
 if((s = socket(PF_INET, SOCK_STREAM, 0)) < 0)
 {
  printf("Server: Can't open stream socket.");
  exit(0);
 }
 /* server_addr 구조체의 내용 세팅 */
 bzero((char *)&server_addr,sizeof(server_addr));                                           /*초기화(0으로 채운다) */
 server_addr.sin_family = AF_INET;
 server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 server_addr.sin_port = htons(atoi(argv[1]));
 if(bind(s,(struct sockaddr *)&server_addr,sizeof(server_addr)) < 0 )                        /* bind() */
 {
  printf("Server: Can't bind local address.\n");
  exit(0);
 }
 /*클라이언트로부터 연결요청을 기다림 */
 listen(s,5);                                                                                /* listen 접속 대기 (큐 크기 5) */
 maxfdp1 = s + 1;                                                                           /* 최대 소켓번호 +1 */
 while(1)
 {
  FD_ZERO(&read_fds);                                                                     /* fd_set  초기화(0으로 세팅)  */
  FD_SET(s,&read_fds);                                                                    /* 소켓에 해당하는 file discripter를 1로 세팅*/
  for(i=0; i<num_chat; i++)                                                               /* "" */
   FD_SET(client_s[i], &read_fds);
  maxfdp1 = getmax(s) +1;                                                                 /* maxfdp1 재 계산 */

  if(select(maxfdp1,&read_fds, (fd_set  *)0, (fd_set *) 0, (struct timeval *) 0) < 0)     /* select setting */
  {
   printf("select error =< 0 \n");
   exit(0);
  }
  if(FD_ISSET(s, &read_fds))                                                              /* read_fds 중 s에 해당하는 비트가 세팅되있다면 */
  {                                                                                       /* == 연결요청이 있다면 */
   clilen = sizeof(client_addr);
   client_fd = accept(s, (struct sockaddr *)&client_addr, &clilen);                    /*  accept() */
   if(client_fd == -1)
   {
    printf("accept error \n");
    exit(0);
   }
   /* 채팅 클라이언트 목록에 추가 */
   client_s[num_chat] = client_fd;
   num_chat++;                                                                         /* 채팅 참가자수 1 증가 */
  send(client_fd,start,strlen(start),0);                                              /* "Connetctd to chat_server" 를 접속한 클라이언트에 보냄  */
   printf("%d번째 사용자 추가,\n",num_chat);
  }
  /*클라이언트가 보낸 메시지를 모든 클라이언트에게 방송 */
  for(i = 0; i<num_chat; i++)                                                             /* 모든 클라이언트 검색 */
  {
  memset(rline,'\0',MAXLINE);                                                         /* buffer 초기화 */
   if(FD_ISSET(client_s[i],&read_fds))                                                 /* read 해당 소켓에서 read 할 것이 있는지 확인 */
  {
    if((n = recv(client_s[i],rline,MAXLINE, 0 )) <= 0)
    {
    removeClient(i);
     continue;
    }
    /* 종료문자 처리 */
    if(strstr(rline,escapechar) != NULL)                                            /*"exit"가 입력되면 종료시키기 */
    {
     removeClient(i);
     continue;
    }
    /* 모든 채팅 참가자에게 메시지 방송 */
    rline[n] = '\0';
    for(j = 0; j < num_chat; j++)
     send(client_s[j],rline,n,0);
    printf("%s\n",rline);
   }
  }
 }
 return 0;
}

And

소켓 주소 구조

|

소켓 주소 구조

<netinet/in.h>
 
 struct socketaddr_in
{
    uint8_t               sin_len;                    /* length of structure(16) */
    sa_family_t        sin_family;                 /* AF_INET */
    in_port_t            sin_port;                   /* 16 bit TCP or UDP port number */
                                                          /* network byte ordered */
    struct in_addr    sin_addr;                  /* 32-bit IPv4 address */
                                                          /* network byte ordered */
    char                 sin_zero[8];              /* unused */
};

struct in_addr
{
    uint32_t             s_addr;                    
}  



 * 4.3BSD-RENO 에서 필요한 데이터형, POSIX에서는 꼭 필요한 것이 아니다.
 * sin_zero 원소는 사용되지 않지만 항상 0으로 설정
 * 이 구조체 전체가 교환되지는 않는다.

일반적인 소켓 주소 구조체
<sys/socker.h>

struct sockaddr
{
    unit_8_t     sa_len;
    sa_family_t    sa_family;
    char    sa_data[14];
}

 * 소켓 함수의 인자로 전달할 때 참조하는 구조로써 공통된 모양이다.
 * 응용 프로그램 관점에서 보면 유일한 용도는 프로토콜에 따른 구조로 변형하는 것.

And

포인터 far near huge

|
결국 16비트때 사용하던 포인터 인데..
현재의 32 환경에서는 그냥 일반 포인터와 같은 의미 입니다.
자세히 아실 필요 없을 거에요.
컴퓨터 구조섭을 좀 들으셨으면 이해에 도움이 될라나...
쉽게 말해.. 구닥다리 입니다. 16비트 시절용.


[far 포인터 변수와 near 포인터 변수, 그리고 huge 포인터 변수]

IBM-PC의 주소는 2가지가 존재하는데, 우선 가장 많이 사용되는 near 포인터의 경우에는 오프셋 값만 주소로 갖게 된다. 이 경우 세그먼트 주소는 고정되어 있음을 의미한다. 따라서 near 포인터 변수의 크기는 16비트가 되며 현재 고정된 세그먼트 주소로부터 64K 바이트 이내의 값만 주소로 갖게 된다. 반면에 far 포인터 변수는 세그먼트 주소와 오프셋을 각각 16비트씩 값으로 갖는다. 따라서 far 포인터 변수는 32비트의 크기를 갖게 되며 주기억 장치의 어느 곳이나 주소를 값으로 가질 수 있다. huge 포인터는 far 포인터와 유사하나 오프셋의 값이 항상 0에서 15사이의 값을 갖도록 만든 것이 다르다. 이 주소를 사용하면 주기억 장치의 한 장소 중에서 주소가 딱 하나로 결정되는 장점이 있다.
포인터를 far 또는 near, huge로 선언하고자 할 때에는 다음과 같이 변수 이름과 데이터 유형 사이에 키워드를 집어 넣으면 된다.

int near *ip; /* 포인터 앞에 아무것도 없을 경우에는 사용하는 */
char far *cp; /* 메모리 모델에 의해 near, far, huge 등이 결정된다 */
float huge *fp;
And

바이트 오더(Byte Order)에 대해서...

|

걸리버 여행기에서 소인국 이야기가 나옵니다. 소인국은 그곳에서 2개의 나라로 나누어서 서로 시기하고 싸우고 있다. 소인국 사람들이 계란을 깰 때 큰 쪽(둥근 쪽)을 깨느냐 작은 쪽(뾰족한 쪽)을 깨느냐를 가지고 패가 갈려서 싸웠다죠. 큰 쪽이 big end, 작은 쪽이 little end, 큰 쪽을 깨는 사람들을 big endian, 그 반대쪽을 깨는 사람들을 little endian이라고 부른 것이 엔디안의 기원이라고 합니다.

 컴퓨터에 있는 바이트 오더에 little endian & big endian을 정리해 볼려고 합니다.

(솔직히 저는 little endian만 고려하고 프로그램을 개발했습니다. 즉, Intel CPU만 고려했지요ㅠ,.ㅜ)


  1. Intel 시스템의 경우

  a = 0x1000
   +----------+----------+----------+----------+
   | 00000001 | 00000000 | 00000000 | 00000000 |  a
   +----------+----------+----------+----------+

  *(char *)&a 의 값은 0x1000 번지의 1byte를 읽어들이게 되어서 1의 값이 됩니다.


  2. IBM PC 계열(Power PC)

  a = 0x1000
   +----------+----------+----------+----------+
   | 00000000 | 00000000 | 00000000 | 00000001 |  a
   +----------+----------+----------+----------+

  *(char *)&a 의 값은 0x1000 번지의 1byte를 읽어들이게 되어서 0의 값이 됩니다.


즉, Low level 프로그램을 개발하거나 또는 이 기종 컴퓨터로 네트워크를 통해서 데이타를 보낼때는 미리 알고 있어야 합니다. 미리 알아낸 바이트 오더를 이용해서 다른 기종간의 통신을 할 때에 바이트 오더의 순서를 바꾸어서 보내주어야 합니다.


아래 코드는 컴퓨터에서 바이트 오더를 간단하게 알아내는 방법입니다.


BOOL IsLittleEndian()

{

    BOOL bRet = TRUE;
    int a = 1;

    if( *(char *)&a == 1 )

    {

        bRet = TRUE;

        printf("Little endian.\n");

     }
    else

    {

        bRet = FALSE;

        printf("Big endian.\n");

     }

    return bRet;
}


마지막으로 바이트 오더 순서를 바꾸어 주는 함수를 정리해 보았습니다.

포인터에 관한 내용은 아니지만 바이트 오더의 순서를 바꾸어 주는 함수들이 있습니다.

  • htons(), htonl()

    htons는 Host to network short 의 줄임말입니다. Host 시스템에서 Network로 short 형 데이터를 보낼 때 바이트 오더를 바꾸어주는 함수입니다.

    htonl은 Host to network long 의 줄임말로 long 형 데이터의 바이트 오더를 바꾸어주는 함수입니다.

  • ntohs(), ntohl()

    ntohs는 Network to host short 의 줄임말로 Network에서 Host로 short형 데이터의 바이트 오더를 바꾸어주는 함수입니다.

    ntohl은 Network to host long의 줄임말로 long 형 데이터의 바이트 오더를 바꾸어주는 함수입니다.

위의 함수들은 소켓을 통해 다른 기종간에 데이터를 전송하거나 또는 받아들여서 자신의 바이트 오더에 맞게 변환해 줄 때 사용하는 함수입니다.


결론 : 이기종 컴퓨터와 네트워크 통신하기 전에 Little endian 인지 Big endian 인지를 알아내서 서로의 바이트 오더 정보를 교환한 다음에 그 바이트 오더에 맞추어서 데이터를 통신하게 되면 정확한 데이터를 송수신해야 한다.


출처 : 다년간의 프로그램밍 삽질(경험) 및 구글 사마

 from : http://blog.naver.com/process3?Redirect=Log&logNo=20029237790


And

[Transformation] htons [출처] [Transformation] htons|작성자 임종배

| 2009. 1. 12. 00:43
보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

[Transformation] htonl [출처] [Transformation] htonl|작성자 임종배

| 2009. 1. 12. 00:42
보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

max, min 함수 C / C++

| 2009. 1. 12. 00:42
보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.
prev | 1 | next