'Language/C'에 해당되는 글 66건

  1. 2011.11.02 소켓 정리
  2. 2011.11.02 getopt()와 optarg, optopt
  3. 2011.09.16 시리얼 통신 - 자료 수신을 위한 poll
  4. 2011.09.16 IPC - 파이프(PIPE)의 개념
  5. 2011.09.16 IPC - 리눅스 Named PIPE 사용법 (FIFO)
  6. 2011.09.15 named pipe / FIFO
  7. 2011.09.08 01. 링킹의 기본 이해
  8. 2011.09.08 03. gcc를 사용해 원하는 컴파일 하기
  9. 2011.09.08 02. 알아두면 정말 유용한 C소스 컴파일 과정(3)
  10. 2011.09.08 02. 알아두면 정말 유용한 C소스 컴파일 과정(2)

소켓 정리

Language/C 2011. 11. 2. 18:45
1. Byte Order
 
네트워크 상에서의 데이터 저장 방식은(Network Byte Order)는 Big Endian 이다.
네트워크 관련 데이터를 채우거나 패킷을 생성할 때 Network Byte Order로 형성을 해 주어야 될 때가 있는데,
이때에는 다음과 같은 함수를 이용해 Host Byte Order의 수를 Network Byte Order로 바꾸어 줄 수가 있다.


htons()

host to network short

htonl()

host to network long

ntohs()

network to host short

ntohl()

network to host long



만약 이미 Network Byte Order의 수를 htonl 을 써서 Network Byte Order을 만들고자 한다면?
함수는 실행되지 않는다.


2. 구조체

2-1. addrinfo

struct addrinfo {
    int              ai_flags;     // AI_PASSIVE, AI_CANONNAME, etc.
    int              ai_family;    // AF_INET, AF_INET6, AF_UNSPEC
    int              ai_socktype;  // SOCK_STREAM, SOCK_DGRAM
    int              ai_protocol;  // use 0 for "any"
    size_t           ai_addrlen;   // size of ai_addr in bytes
    struct sockaddr *ai_addr;      // struct sockaddr_in or _in6
    char            *ai_canonname; // full canonical hostname

    struct addrinfo *ai_next;      // linked list, next node
};

이 구조체는 소켓을 만들 때 필요한 정보들을 얻기위한 구조체이다.
몇몇 요소들을 채운 뒤 getaddrinfio()함수를 실행하면 함수가 필요한 정보를 가진 linked list를 반환해준다
 
ai_flags : AI_PASSIVE를 넣을 시 localhost의 관련 정보들을 얻을 수 있다
ai_family : IPv4를 이용할시 AF_INET, IPv6을 이용할 시 AF_INET6,  정해져있지 않을 시 AF_UNSPEC을 넣는다
ai_socktype : 스트림 소켓이면 SOCK_STREAM, 데이터그램 소켓이면 SOCK_DGRAM을 넣는다
ai_protocol : 0을 넣을 시 모든 프로토콜 관련 정보를 얻을 수 있고, 특정 프로토콜을 지정해 줄 수도 있다
ai_addrlen : ip주소의 길이를 가지고있다
ai_addr : sockaddr 구조체 포인터로, ip, port number 정보를 가지고 있다.
ai_cononname :  hostname을 가르키는 포인터이다.
ai_next : 다음 노드를 가리키고 있다 


2-2. sockaddr
struct sockaddr {
    unsigned short    sa_family;    // address family, AF_xxx
    char              sa_data[14];  // 14 bytes of protocol address
}; 

sockaddr구조체는 IPv4용인 sockaddr_in, IPv6용인 sockaddr_in6으로 캐스팅될 수 있고, 그 반대도 가능하다.

IPv4
// (IPv4 only--see struct sockaddr_in6 for IPv6)

struct sockaddr_in {
    short int          sin_family;  // Address family, AF_INET
    unsigned short int sin_port;    // Port number
    struct in_addr     sin_addr;    // Internet address
    unsigned char      sin_zero[8]; // Same size as struct sockaddr
};
// (IPv4 only--see struct in6_addr for IPv6)

// Internet address (a structure for historical reasons)
struct in_addr {
    uint32_t s_addr; // that's a 32-bit int (4 bytes)
};

IPv6
// (IPv6 only--see struct sockaddr_in and struct in_addr for IPv4)

struct sockaddr_in6 {
    u_int16_t       sin6_family;   // address family, AF_INET6
    u_int16_t       sin6_port;     // port number, Network Byte Order
    u_int32_t       sin6_flowinfo; // IPv6 flow information
    struct in6_addr sin6_addr;     // IPv6 address
    u_int32_t       sin6_scope_id; // Scope ID
};

struct in6_addr {
    unsigned char   s6_addr[16];   // IPv6 address
};


2-3. ip주소의 입력과 출력

inet_pton()를 이용하여 구조체에 IP주소를 넣을 수 있으며
inet_ntop()를 이용하여 구조체에 있는 IP주소를 문자열로 얻을 수 있다.


struct sockaddr_in sa; // IPv4
struct sockaddr_in6 sa6; // IPv6

inet_pton(AF_INET, "192.0.2.1", &(sa.sin_addr)); // IPv4
inet_pton(AF_INET6, "2001:db8:63b3:1::3490", &(sa6.sin6_addr)); // IPv6

// IPv4:

char ip4[INET_ADDRSTRLEN];  // space to hold the IPv4 string
struct sockaddr_in sa;      // pretend this is loaded with something

inet_ntop(AF_INET, &(sa.sin_addr), ip4, INET_ADDRSTRLEN);

printf("The IPv4 address is: %s\n", ip4);


// IPv6:

char ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string
struct sockaddr_in6 sa6;    // pretend this is loaded with something

inet_ntop(AF_INET6, &(sa6.sin6_addr), ip6, INET6_ADDRSTRLEN);

printf("The address is: %s\n", ip6);



3. 함수

3-1. 연결 - 클라이언트, 서버 공통


3-1-1.getaddrinfo()

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *node,     // e.g. "www.example.com" or IP
                const char *service,  // e.g. "http" or port number
                const struct addrinfo *hints,
                struct addrinfo **res);

addrinfo를 채워주는 함수이다
*hints에 몇몇 요소들을 기입한 addrinfo 구조체를 넣어주면
**res를 통해 linked list가 반환된다
함수 성공시 0이 반환된다.

예제1) 나의 localhost의 3490포트 정보 얻기
int status;
struct addrinfo hints;
struct addrinfo *servinfo;  // will point to the results

memset(&hints, 0, sizeof hints); // make sure the struct is empty
hints.ai_family = AF_UNSPEC;     // don't care IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
hints.ai_flags = AI_PASSIVE;     // fill in my IP for me

if ((status = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) {
    fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
    exit(1);
}

// servinfo now points to a linked list of 1 or more struct addrinfos

// ... do everything until you don't need servinfo anymore ....

freeaddrinfo(servinfo); // free the linked-list

예제2)www.example.com의 3490포트 정보 얻기
int status;
struct addrinfo hints;
struct addrinfo *servinfo;  // will point to the results

memset(&hints, 0, sizeof hints); // make sure the struct is empty
hints.ai_family = AF_UNSPEC;     // don't care IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets

// get ready to connect
status = getaddrinfo("www.example.net", "3490", &hints, &servinfo);

// servinfo now points to a linked list of 1 or more struct addrinfos

// etc.


3-1-2. socket()

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol); 

소켓 파일 기술자를 생성한다
domain에는 PF_INET 또는 PF_INET6 이 들어가고, type에는 SOCK_STREAM 또는 SOCK_DGRAM, protocol에는 addrinfo 구조체의 protocol과 같다.
또 PF_INET과 PF_INET6은 각각 AF_INET과 AF_INET6으로 대신할 수 있다
함수 성공시 소켓 파일 기술자를 반환하고, 에러시 -1을 반환한다.
int s;
struct addrinfo hints, *res;

// do the lookup
// [pretend we already filled out the "hints" struct]
getaddrinfo("www.example.com", "http", &hints, &res);

// [again, you should do error-checking on getaddrinfo(), and walk
// the "res" linked list looking for valid entries instead of just
// assuming the first one is good (like many of these examples do.)
// See the section on client/server for real examples.]

s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);


3-2. 연결 - 서버측

3-2-1. bind()

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

프로세스가 몇번 포트를 이용해 통신을 할 것인지 정해주는 함수이다
sockfd에는 소켓 파일 기술자, my_addr에는 bind할 곳의 주소 정보를 가지고 있는 sockaddr구조체, addrlen에는 주소의 길이를 넣어준다.
에러시 -1을 반환한다.

struct addrinfo hints, *res;
int sockfd;

// first, load up address structs with getaddrinfo():

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;     // fill in my IP for me

getaddrinfo(NULL, "3490", &hints, &res);

// make a socket:

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

// bind it to the port we passed in to getaddrinfo():

bind(sockfd, res->ai_addr, res->ai_addrlen)


3-2-2. listen()


int listen(int sockfd, int backlog); 

접속을 받아들일 준비를 하는 함수이다. backlog는 몇개의 연결까지 대기상태에 둘 것인지를 정해준다.
에러시 -1을 반환한다


3-2-3. accept()

#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 

대기중인 접속 하나를 받아드려 데이터를 주고받기 위한 함수이다.
성공시 새로운 소켓 파일 기술자를 반환하며, 이를 통해 데이터를 주고받을 수 있다
addr은 연결된 클라이언트의 주소가 들어갈 sockaddr_storage 구조체 포인터 변수를 넣어주고, 
addrlen은 sockaddr_storage의 크기를 넣어준다


#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MYPORT "3490"  // the port users will be connecting to
#define BACKLOG 10     // how many pending connections queue will hold

int main(void)
{
    struct sockaddr_storage their_addr;
    socklen_t addr_size;
    struct addrinfo hints, *res;
    int sockfd, new_fd;

    // !! don't forget your error checking for these calls !!

    // first, load up address structs with getaddrinfo():

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;     // fill in my IP for me

    getaddrinfo(NULL, MYPORT, &hints, &res);

    // make a socket, bind it, and listen on it:

    sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    bind(sockfd, res->ai_addr, res->ai_addrlen);
    listen(sockfd, BACKLOG);

    // now accept an incoming connection:

    addr_size = sizeof their_addr;
    new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size);

    // ready to communicate on socket descriptor new_fd!
    .
    .



3-3. 연결 - 클라이언트측


3-3-1. connect()

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); 

서버에 접속을 하기위한 함수이다. serv_addr에는 접속할 곳의 주소가 들어가며, addrlen에는 주소의 길이를 넣어준다.
에러시 -1을 반환한다.


예제)www.example.com 의 3490포트에 접속
struct addrinfo hints, *res;
int sockfd;

// first, load up address structs with getaddrinfo():

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

getaddrinfo("www.example.com", "3490", &hints, &res);

// make a socket:

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

// connect!

connect(sockfd, res->ai_addr, res->ai_addrlen);




3-4. 데이터 송수신


3-4-1. 스트림 소켓 : send() recv()

int send(int sockfd, const void *msg, int len, int flags); 
int recv(int sockfd, void *buf, int len, int flags);

accept()된 소켓 혹은 connect된 소켓을 이용해 데이터를 주고받기 위한 함수이다

send - msg에는 보낼 테이터, len에는 보낼 길이를 넣어준다
recv - buf에는 받을 데이터를 저장할 공간, len에는 받을 길이를 넣어준다
함수 종료시 실제 송,수신한 데이터의 길이를 반환하고, 에러시 -1을 반환한다



3-4-2. 데이터그램 소켓 : sendto() recvfrom()

int sendto(int sockfd, const void *msg, int len, unsigned int flags,
           const struct sockaddr *to, socklen_t tolen);
int recvfrom(int sockfd, void *buf, int len, unsigned int flags,
             struct sockaddr *from, int *fromlen); 

send,recv함수와 비슷하지만 송신지or 수신지의 주소를 넣어주고, 구조체 변수의 크기를 넣어주어야 한다.

'Language > C' 카테고리의 다른 글

현재 날짜와 시간 출력  (0) 2011.11.04
gettimeofday  (0) 2011.11.04
getopt()와 optarg, optopt  (0) 2011.11.02
시리얼 통신 - 자료 수신을 위한 poll  (0) 2011.09.16
IPC - 파이프(PIPE)의 개념  (0) 2011.09.16
:

getopt()와 optarg, optopt

Language/C 2011. 11. 2. 13:10

1 히스토리

  • 2010/3/25 wiki 스타일로 변환
  • getopt_long함수도 다루자.

2 getopt를 이용한 프로그램 인자 처리

모든 C(C++)로 된 쏘쓰는 반드시 하나의 main() 함수를 포함하며, main 함수는 2개의 인자를 가지게 된다. 일반적으로 main() 함수는 아래와 같이 선언된다.
void main (int argc, char **argv)  
 

위와 같이 main 함수를 선언해 놓으면 프로그램이 시작할때마다, 프로그램의 인자를 argc 와 argv 로 받아들이게 된다. argc 는 프로그램이 실행될때 받아들인 인자의 갯수이며, argv 는 받아들인 인자의 문자열 집합이다. 아래의 예를 실행해보면 쉽게 이해가 가능할것이다.
#include <stdlib.h> 
 
int main(int argc, char **argv) 
{ 
    int i; 
    printf("아규먼트의 수는 %d 개입니다", argc); 
 
    for (i = 0; i < argc; i ++) 
    {  
        printf("%d 번째 아규먼트 : %s", i, argv[i]); 
    }    
 
    return 0; 
}  
 
위의 프로그램을 컴파일 해서 실행시키면, 아규먼트의 수와 각각의 아규먼트를 돌려주는걸 볼수 있을것이다.
[yundream@localhost test]# ./argc hello myname  
아규먼트의 수는 3 개입니다  
0 번째 아규먼트 : ./argc  
1 번째 아규먼트 : hello  
1 번째 아규먼트 : myname 
 

분명히 main() 함수의 아규먼트로 넘어오는 argc 와 argv 를 활용해서 기본적인 아규먼트 충분히 할수 있기는 하지만, 복잡한 실행 인자를 처리하기에는 적합하지 않다.

우리가 언제나 사용하는 가장 간단한 ls 조차도 "ls -al" 과 같은 옵션을 지원한다. ls -al 에서 보듯이 이것은 ls -a -l 로도 사용될수도 있고, ls -l -a 로도 사용될수 있다. 이것을 프로그램내에서 사용자 정의 함수를 이용해서 처리하려고 하면 보통 일이 아닐것이다. 그래서 유닉스 에서는 이러한 옵션을 쉽게 처리할수 이또록 도와주는 getopt(3) 함수를 지원한다. 이번에는 getopt 를 이용해서 다양한 옵션을 처리하는 testopt 라는 프로그램을 만들어 보도록 하겠다. 프로그램 이름은 testopt.c로 하겠다.

#include <unistd.h> 
#include <stdio.h> 
#include <string.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
 
void help(); 
void version(); 
int main(int argc, char **argv) 
{  
    int opt; 
    int port_num; 
    int fp; 
    int opt_ok; 
    char file_name[16]; 
    char buf[256]; 
 
    while((opt = getopt(argc, argv, "hvf:")) != -1)  
    { 
        switch(opt)  
        {  
            case 'h': 
                help();  
                break;  
            case 'v': 
                version();   
                break; 
            case 'f': 
                memcpy(file_name, optarg, 16); 
                opt_ok = 1; 
                break; 
        } 
    }  
    if (opt_ok != 1) 
    {  
        help(); 
        exit(0); 
    }  
 
    if (access(file_name, R_OK) != 0) 
    { 
        printf("파일이 존재하지 않습니다"); 
        exit(0); 
    } 
    if((fp = open(file_name, O_RDONLY)) == -1) 
    { 
        printf("file open error"); 
        exit(0); 
    } 
 
    memset(buf, '0', 256); 
    while(read(fp, buf, 256) > 0) 
    { 
        printf("%s", buf); 
        memset(buf, '0', 256); 
    } 
} 
 
void help() 
{ 
    printf("Usage: ./testopt [OPTION] [FILE]" 
           "  -h                도움말" 
           "  -f [FILE]         파일출력" 
           "  -v                버전출력"); 
    exit(0); 
} 
 
void version() 
{ 
    printf("Version : 1.01"); 
    exit(0); 
} 
 

매우 간단하고 이해가 쉬운 예제이다. getopt 를 이용해서 프로그램의 인자를 검사 해서 '-' 가 앞에 붙어 있는 인자는 옵션으로 간주해서 "hvf" 중 어느 옵션인지를 건사해서 해당 루틴을 실행시켜준다. 단 "f" 의 경우에는 ":" 가 붙어 있는데, 이는 "f" 옵션은 반드시 뒤에 값이 따라야 함을 명시하고 있다. 여기에서 값이란 화면에 출력하고픈 파일이름이다. 
[yundream@localhost test]# ./testopt -h  
Usage: ./testopt [OPTION] [FILE] 
  -h                            도움말 
  -f [FILE]                     파일출력 
  -v                            버전출력 
 [yundream@localhost test]# ./testopt -f 
./testopt: option requires an argument -- f 
Usage: ./testopt [OPTION] [FILE] 
  -h                            도움말 
  -f [FILE]                     파일출력 
  -v                            버전출력 
 

3 getopt_long

'Language > C' 카테고리의 다른 글

gettimeofday  (0) 2011.11.04
소켓 정리  (0) 2011.11.02
시리얼 통신 - 자료 수신을 위한 poll  (0) 2011.09.16
IPC - 파이프(PIPE)의 개념  (0) 2011.09.16
IPC - 리눅스 Named PIPE 사용법 (FIFO)  (0) 2011.09.16
:

시리얼 통신 - 자료 수신을 위한 poll

Language/C 2011. 9. 16. 10:36

http://forum.falinux.com/zbxe/?document_srl=405838


이번 시간에는 시리얼 통신에서 자료를 수신하는 부분을 구현하려 합니다. 그러나 송신 보다 수신하는 부분은 생각할 점이 있습니다. 전송이야 이쪽에서 필요할 때 보내기만 하면 되기 때문에 “언제라는” 시간적인 문제가 없습니다. 그러나 수신은 자료가 언제 올지를 모르죠. 기다려야 한다는 것인데, 자료가 올 때까지 마냥 시리얼 포트만 쳐다 보고 있을 수 없습니다. 다른 일도 처리 해야죠. 해야할 일이 산더미처럼 쌓였는데, 마냥 포트만 쳐다 볼 수 없습니다. 

 

이럴 때 쉽게 생각할 수 있는 것이 일을 처리하는 중에 잠시잠시 포트를 확인하는 방법입니다. 가령 예를 들어서 아래와 같이 하는 것이죠.

while( 1)
{
  // 다른 업무를 실행
  if 0 < read( fd, buf, BUF_MAX_SIZE)
  {
    // 수신 자료를 처리
  }
}

물론 이와 같은 방법도 좋습니다만 ?자료 수신 이외의 이벤트 처리, 예로 통신에 에러가 발생하지 않았는지 등을 확인을 위해서는 또 다른 확인 루틴을 작성하고 if 절을 추가해야 합니다. 그러나 무엇보다도 read()함수가 block되 버리면 루틴 자체가 block되어 버리는 매우 큰 문제를 가지고 있습니다.

 

이럴 때 사용하는 것이 POLL입니다.

 

poll()

POLL은 확인하고 싶은 여러 가지 사건( 이하 event)를 미리 등록해 놓고 그 사건들이 발생했는지 확인할 수 있는 편리한 방법을 제공해 줍니다. POLL을 이용한 작업 진행 과정을 정리해 보면,

  1. 체크하고 싶은 여러 event를 등록
  2. poll() 함수를 호출하면
  3. event가 발생하면 poll() 함수 호출 후에 바로 복귀하지만,
  4. 발생된 event 가 없으면 하나 이상이 발생할 때 까지 time-out 시간 만큼 대기하게 됩니다.
  5. event가 발생면 해당 event의 배열 아이템의 값이 변동이 되는데, 
    이 변동을 확인하여 어떤 event 가 발생했는지를 알 수 있습니다.

장황한 설명 보다는 먼저 간단한 예를 보여 드리고 그 후에 자세한 설명을 드리도록 하겠습니다. 예제는 이해를 돕기 위해 상수를 사용하지 않았고, 함수로 분리하지 않았습니다.

예제

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/poll.h>
#include <termios.h>                   // B115200, CS8 등 상수 정의
#include <fcntl.h>                     // O_RDWR , O_NOCTTY 등의 상수 정의

int main( void)
{        
   int    fd;
   int    ndx;
   int    cnt;
   char   buf[1024];
   struct termios    newtio;
   struct pollfd     poll_events;      // 체크할 event 정보를 갖는 struct
   int    poll_state;

   // 시리얼 포트를 open

   fd = open( "/dev/ttyS0", O_RDWR | O_NOCTTY | O_NONBLOCK );        // 디바이스를 open 한다.
   if ( 0 > fd)
   {        
      printf("open error\n");
      return -1;
   }

   // 시리얼 포트 통신 환경 설정

   memset( &newtio, 0, sizeof(newtio) );
   newtio.c_cflag       = B115200 | CS8 | CLOCAL | CREAD;
   newtio.c_oflag       = 0;
   newtio.c_lflag       = 0;
   newtio.c_cc[VTIME]   = 0;
   newtio.c_cc[VMIN]    = 1;
   
   tcflush  (fd, TCIFLUSH );
   tcsetattr(fd, TCSANOW, &newtio );
   fcntl(fd, F_SETFL, FNDELAY); 


   // poll 사용을 위한 준비
   
   poll_events.fd        = fd;
   poll_events.events    = POLLIN | POLLERR;          // 수신된 자료가 있는지, 에러가 있는지
   poll_events.revents   = 0;


   // 자료 송수신

   while ( 1)
   {
      poll_state = poll(                               // poll()을 호출하여 event 발생 여부 확인     
                         (struct pollfd*)&poll_events, // event 등록 변수
                                                   1,  // 체크할 pollfd 개수
                                                1000   // time out 시간
                       );

      if ( 0 < poll_state)                             // 발생한 event 가 있음
      {     
         if ( poll_events.revents & POLLIN)            // event 가 자료 수신?
         {
            cnt = read( fd, buf, 1024);
            write( fd, buf, cnt);
            printf( "data received - %d %s\n", cnt, buf);
         }
         if ( poll_events.revents & POLLERR)      // event 가 에러?
         {
            printf( "통신 라인에 에러가 발생, 프로그램 종료");
            break;
         }
      }
   }
   close( fd);
   return 0;
}

  예제를 보시면 struct pollfd를 사용했습니다. struct pollfd의 내용을 보면 아래와 같습니다.

struct pollfd 
{
  int fd;             // 대상 파일 디스크립터 
  short events;   // 발생된 이벤트 
  short revents;   // 돌려받은 이벤트 
};

  1. fd는 감시 대상인 디스크립터, 즉 핸들이 되겠습니다.
  2. events는 체크하고 싶은 event의 모음입니다. 여기서 체크가 가능한 것은 아래와 같습니다.
  • #define POLLIN 0x0001 // 읽을 데이터가 있다.
  • #define POLLPRI 0x0002 // 긴급한 읽을 데이타가 있다.
  • #define POLLOUT 0x0004 // 쓰기가 봉쇄(block)가 아니다.
  • #define POLLERR 0x0008 // 에러발생
  • #define POLLHUP 0x0010 // 연결이 끊겼음
  • #define POLLNVAL 0x0020 // 파일지시자가 열리지 않은 것 같은, Invalid request (잘못된 요청)
  1. revents 는 event 발생 여부를 bit 별로 갖는 값입니다.

 

  자, 이제 예제를 보겠습니다.

struct pollfd poll_events; // 체크할 event 정보를 갖는 struct

poll()을 사용하기 위한 변수를 선언했습니다. poll_events 에는 감시 대상인 디스크립터와 어떤 event를 감시할지 결정해서 bit 값으로 지정해 줄 것입니다. 

int poll_state;

poll()이 수행한 결과값을 갖습니다. 이 값은 반드시 체크해야 하는데, 아래와 같은 반환값을 갖습니다.

 

poll() 수행 후 반환 값
반환 값 설명
음수 반환 값이 음수라면 치명적인 에러가 발생한 것입니다. 한번 이렇게 음수로 에러가 발생하면 이후 계속 음수값이 날라 옵니다. 거의 대부분 프로그램을 다시 실행해야 됩니다. 반드시 체크해야 겠지요.
0 지정한 대기시간, time-out이 지나도록 발생한 event가 없습니다.
양수 event 가 발생했습니다.

poll_events.fd = fd;

감시 대상인 디스크립터를 지정합니다.

poll_events.events = POLLIN | POLLERR; // 수신된 자료가 있는지, 에러가 있는지

체크하고 싶은 event에 대해 비트값으로 설정하여 지정해 줍니다.

poll_events.revents = 0;

revents를 0 으로 청소해 주고요.

poll_state = poll(                               // poll()을 호출하여 event 발생 여부 확인     
                  (struct pollfd*)&poll_events, // event 등록 변수
                                             1,  // 체크할 pollfd 개수
                                          1000   // time out 시간
                );

체크할 event 정보를 넘겨 줍니다. 1은 체크할 pollfd 개수입니다. 이 예제를 가지고는 pollfd의 개수가 왜 필요한지 이해가 안되시죠. 예제에는 감시하는 디스크립터가 한 개이지만 프로그램에 따라서는 한번에 여러 개의 디스크립터를 관리할 경우가 많습니다.

 

관리할 디스크립터가 많을 때, 각각을 변수로 처리하는 것 보다 배열로 처리하는 것이 편하겠죠. 이래서 poll() 이 편리합니다. poll()은 변수 하나 외에도 배열을 받을 수 있으며, 한번의 호출로 모든 event 발생 여부를 확인할 수 있어 매우 편리합니다.

 

 참고

앞으로 강좌를 진행하면서 말씀 드리겠습니다만 poll()을 사용하는 또 다른 장점은 서로 다른 특징의 디스크립터를 사용한다 해도 프로그램 코드를 통일할 수 있습니다.

예로 rs232c 포트 10개와 tcp/ip 소켓 10개를 한번에 제어하는 프로그램을 작성했을 경우 역시 하나의 배열로 구성해서 poll()을 사용할 수 있기 때문에 rs232c 처리와 tcp/ip 처리하는 프로그램 코드를 통일할 수 있습니다.

 

1000은 time-out 시간으로 발생한 event 가 없을 경우 poll()은 event가 발생할 때 까지 time-out 시간 동안 대기하게 됩니다.

이 시간 값은 msec로 1000은 1초를 의미합니다. 0 이면? 바로 복귀합니다. -1 이면? evevnt 가 발생할 때 까지 기다리게 됩니다. 즉, block되 버리죠.

if ( 0 < poll_state)

poll() 함수 결과가 양수라면 event 가 발생한 것입니다.

if ( poll_events.revents & POLLIN)

발생한 event 중에 POLLIN 이 있는 지를 확인합니다. POLLIN에 해당되는 bit 가 1로 세트되어 있다면 POLLIN으로 AND한 값은 0 이 아닐 것입니다. 이렇게 비트값을 확인하여 event 발생 여부를 확인할 수 있습니다.

cnt = read( fd, buf, 1024);
write( fd, buf, cnt);
printf( "data received - %d %s\n", cnt, buf);

자료 수신 event 가 발생했으므로 fd로부터 자료 값을 읽어 들이고, 예제 테스트를 위해 다시 그 자료를 전송합니다. 또한 화면에도 수신한 자료를 뿌리고 있죠.

'Language > C' 카테고리의 다른 글

소켓 정리  (0) 2011.11.02
getopt()와 optarg, optopt  (0) 2011.11.02
IPC - 파이프(PIPE)의 개념  (0) 2011.09.16
IPC - 리눅스 Named PIPE 사용법 (FIFO)  (0) 2011.09.16
named pipe / FIFO  (0) 2011.09.15
:

IPC - 파이프(PIPE)의 개념

Language/C 2011. 9. 16. 09:30

이번에는 IPC 기법 중 PIPE 에 대하여 알아보도록 하겠습니다.

파이프의 느낌은 슈퍼마리오의 게임에서 파이프를 타고 입구와 출구를 오가는 그런 느낌이죠.


IPC 에서 PIPE 를 간단하게 설명한다면, 

여러개의 프로세스가 공통으로 사용하는 임시공간이라고 할 수 있습니다.


그리고 그 임시 공간은 실제로 파일 시스템에 생성되는 임시 파일입니다.

하나의 프로세스가 파이프에 쓰게 되면 다른 프로세스는 그 파이프에서 읽는 방식으로 쓰게 됩니다.




위에 그림처럼 파이프는 시스템 내부에서 관리하는 임시 파일을 이용하므로, 

다른 IPC 기법 중 하나인 SIGNAL 과는 다르게 대용량의 메시지도 전송이 가능합니다.


이런 파이프는 보통 익명의 파이프와 이름을 가진 Named Pipe 의 두가지 방법으로

사용할 수 있도록 OS 가 제공 합니다.


파이프는 소켓 등의 비하면 꽤 단순한 IPC 기법으로 여러가지 특징을 가지고 있습니다.


그중 첫번째 특성은, 파이프는 입구와 출구의 개념이 없다는 것입니다.

파이프는 단순한 통로 입니다. 

방향성이 없기 때문에 자신이 쓴 메시지를 자신이 읽을 수 있다는 것을 항상 유의해야 합니다.


그렇기 때문에 두개의 프로세스가 통신할 때는 

읽기전용 파이프와 쓰기 전용 파이프의 두 개의 파이프를 쓰게 됩니다.

두개의 파이프를 생성하고, 한쪽 파이프를 일부러 닫아 버리는 것이지요.


그래서 결국은 아래 그림처럼 파이프를 두개 생성하여 하나의 방향으로만 갈 수 있도록 합니다.






또 파이프의 특성 중 유의할 것은 파이프는 fork() 함수에 의해 복사 되지 않는다는 것입니다.


fork() 함수에 의해서 프로세스가 생성되면, 자식 프로세스는 부모가 사용하던 변수를 복사하게 됩니다.

하지만 파이프의 경우 복사되는것이 아니라 File Descriptor 를 공유하게 됩니다.


즉 자식 프로세스와 부모 프로세스가 같은 파이프를 가리키게 됩니다.


그외에도 익명의 파이프는 또 하나의 주요한 특성이 있습니다.

부모 자식프로세스 사이에서만 파이프를 사용 가능하다는 것입니다.


파이프는 운영체제에서 임시로 생성되는 파일이고, 접근 가능한 방법은

File Descriptor(파일 디스크립터) 를 공유하는 방법만이 존재합니다.


익명의 파이프는 부모와 자식 프로세스만이 파일 디스크립터를 공유하므로

다른 프로세스는 파이프를 사용하여 통신이 불가능 합니다.




반면 Named PIPE(네임드 파이프) 의 경우 다른 프로세스도,

지정한 이름으로 파일 디스크립터를 열수 있기 때문에,

부모 자식 프로세스 간이 아니더라도 사용할수 가 있는 것이죠.

출처:
 
http://akj61300.blog.me/80133137262 

'Language > C' 카테고리의 다른 글

getopt()와 optarg, optopt  (0) 2011.11.02
시리얼 통신 - 자료 수신을 위한 poll  (0) 2011.09.16
IPC - 리눅스 Named PIPE 사용법 (FIFO)  (0) 2011.09.16
named pipe / FIFO  (0) 2011.09.15
01. 링킹의 기본 이해  (0) 2011.09.08
:

IPC - 리눅스 Named PIPE 사용법 (FIFO)

Language/C 2011. 9. 16. 09:21

리눅스에서는 Named PIPE 기법을 위해 FIFO 라는 특수 파일을 제공합니다.

특수 파일로 생성되는 FIFO 는 생성 방법에 차이는 있지만 PIPE 와 거의 유사합니다.


익명의 파이프는 따로 이름이 없는 통신 채널인데 비해서 

FIFO 는 mkfifo 함수에 의해 실제로 생성되는 특수 파일입니다.


따라서 어떤 프로세스라도 생성된 경로로 파일에 접근하여

다른 파일을 사용하듯 읽거나 쓸 수 있습니다.


FIFO 를 사용하기 위해서는 mkfifo() 함수를 사용하는데

함수의 설명은 표로 정리해 보겠습니다.


함수 원형

int mkfifo(const char *pathname, mode_t mode)

인자

const char *pathname

fifo 파일이 생성될 경로를 지정한다.

일반 파일의 경로를 지정하는 것과 동일

mode_t mode

생성 될 fifo 파일의 접근 권한을 지정한다.

반환 값

성공하는 경우 0 반환

실패하는 경우 -1 반환


mkfifo() 함수가 정상적으로 실행되면 

인자로 넘긴 경로에 특수 파일로 FIFO 가 등록됩니다.


다른 프로세스는 일반 파일에서 사용하는 것처럼 FIFO를 사용 가능하지만 

특정 프로세스가 FIFO 를 쓰기용으로 열기 전까지는 다른 읽기용 개방은 블럭(Block) 됩니다.


예제를 통해 실제 사용법을 알아보겠습니다.


만들어보고자 하는 예제는 두개의 프로세스가 FIFO 를 이용해 통신하며

Sender 프로세스가 보낸 메시지를 Receiver 프로세스가 메시지를 읽어서 화면에 보여주는 프로그램입니다.


Sender 프로세스가 메시지를 3번 보내고 

Receiver 프로세스도 세번 메시지를 읽도록 하겠습니다.


먼저 Receiver 프로세스 코드입니다.

Receiver 프로세스는 Named PIPE 인 FIFO 를 생성하고 메시지를 기다립니다.



 #include <fcntl.h>

#include <sys/stat.h>

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>


#define MSG_SIZE 80


int main()

{

    char msg[MSG_SIZE];

    int filedes;

    int nread, cnt;


    if(mkfifo("./fifo",0666) == -1){

        printf("fail to call fifo()\n");

        exit(1);

    }


    if((filedes = open("./fifo", O_RDWR)) < 0){

        printf("fail to call fifo()\n");

        exit(1);

    }


    for(cnt=0; cnt<3; cnt++){

        if((nread = read(filedes, msg, MSG_SIZE)) < 0 ){

            printf("fail to call read()\n");

            exit(1);

        }

        printf("recv: %s\n", msg);

    }

    unlink("./fifo");


    return 0;

}



소스를 부분 별로 살펴보겠습니다.




파일 디스크립터(File Descriptor) 를 얻을 변수와 

메시지를 저장할 변수를 선언하였습니다.





Named PIPE 특수 파일인 FIFO 를 생성하는 부분입니다.

mkfifo() 함수에 현재 경로는 프로그램이 실행되는 폴더인 "./fifo" 로 지정하였고,

파일의 권한은 0666 으로 owner, group, user 모두 읽기 쓰기 권한을 주었습니다.




fifo 를 개방하는 부분입니다. 

이때 주의할 부분은 최초 개방할 때는 반드시 쓰기 권한이 있어야 합니다.

위에서도 한번 설명 했듯이 fifo 는 최초 쓰기 개방전에는 모든 읽기 개방을 Block 해 버립니다.


여기서는 Receiver 프로세스가 최초 개방이므로 

O_RDWR 읽기 쓰기 모두 가능 모드로 개방하였습니다.




fifo 파일에서 정해진 사이즈만큼 읽어 들입니다. 

pipe 의 특성 상 정해진 바이트를 읽을 때까지 대기를 하게 됩니다.

정해진 사이즈의 데이터를 읽으면 화면에 메시지를 출력합니다.



unlink() 함수로 fifo 와 연결을 끊고 프로그램을 종료하는 부분입니다.


이제 Sender 의 소스를 보겠습니다.


 #include <fcntl.h>

#include <sys/stat.h>

#include <unistd.h>

#include <stdlib.h>

#include <stdio.h>


#define MSG_SIZE 80


int main()

{

    char msg[MSG_SIZE];

    int filedes;

    int cnt;


    // open fifo file

    if((filedes = open("./fifo", O_WRONLY)) < 0){

        printf("fail to call open()\n");

        exit(1);

    }


    for(cnt=0; cnt<3; cnt++){

        printf("input a message : ");

        gets(msg);

        msg[MSG_SIZE-1] = '\0';


        if(write(filedes, msg, MSG_SIZE)==-1){

            printf("fail to call write()\n");

            exit(1);

        }

        sleep(1);

    }

}




Sender 쪽 소스도 부분별로 살펴보겠습니다.




fifo 파일을 엽니다. fifo 파일의 패스(path) 를 안다면 

어떤 프로세스도 일반 파일을 여는 것처럼 fifo 파일을 열 수 있습니다.




일반 파일에 쓰는 것과 똑같이 fifo 에 문자열을 쓰고 있습니다.

다만 fifo 는 파이프(pipe) 로 작동 하므로 상대 프로세스로 메시지가 전달되게 됩니다.


실행해서 결과를 확인해 보겠습니다.

실행할 때의 주의점은 Receiver 프로세스와 

Sender 프로세스가 같은 폴더에 있어야 한다는 것입니다.


fifo 파일의 경로를 상대 경로 "./" 으로 하였기 때문에

같은 폴더에 있어야 파일에 접근할 수 있는 것이죠.


물론 절대경로로 fifo 를 설정하면 실행파일이 같이 있을 필요는 없지만

소스를 그렇게 코딩 했으므로 같은 폴더에 넣고 실행하도록 하겠습니다.





터미널을 두개 열고 각 프로세스를 실행시킨 결과 화면입니다.





그림에서 보는 것처럼 Sender 프로세스가 메시지를 쓰고 엔터키를 누를 때마다

Receiver 프로세스는 전달 받은 메시지를 그대로 화면에 출력합니다.

같은 동작이 세번 반복 되면 프로그램이 끝나게 됩니다.


Named PIPE 역시 사용하기에 따라 여러가지 응용이 가능하다고 생각합니다.

출처 :  
http://akj61300.blog.me/80133137262 

'Language > C' 카테고리의 다른 글

시리얼 통신 - 자료 수신을 위한 poll  (0) 2011.09.16
IPC - 파이프(PIPE)의 개념  (0) 2011.09.16
named pipe / FIFO  (0) 2011.09.15
01. 링킹의 기본 이해  (0) 2011.09.08
03. gcc를 사용해 원하는 컴파일 하기  (0) 2011.09.08
:

named pipe / FIFO

Language/C 2011. 9. 15. 17:22

FIFO

pipe 에 대한 내용은 Pipe의 사용 System V IPC 에 대해서를 참조하기 바란다.

FIFO는 PIPE와달리 명명된(이름이 있는) 파일을 통하여 통신이 이루어지게 되므로, 서로다른 세션에 있는 프로세스라도 관계없이 통신을 할수가 있으며, 다중의 클라이언트를 받아들이기 위한 서버모델을 만들수있다. 그러나 Unix Domain 소켓과는 달리, 단일의 파일지시자를 통해서 연결이 되므로, select 등을 통하여 다중의 클라이언트중 원하는 클라이언트에 선별적으로 통신할수 있는 진정한 다중 서버/클라이언트 모델을 구성하는건 쉽지가 않다. 이럴경우는 Unix Domain 소켓과 같은 다른 방법을 찾아봐야 할듯하다.

어쨋든 단지 다중의 클라이언트로 부터 메시지를 받아서 처리하는 것으로 끝나는 경우에는 (READ ONLY), 매우 간단하게 이러한 모델을 구현할수 있음으로 사용하기에 매우 편할것이다. 하나의 서버와 하나의 클라이언트로 구성되어 있고, 이들이 서로 메시지를 주고 받는 서비스를 만들경우에도, 비록 FIFO 가 읽기/쓰기를 지원하지만, 제대로된 작동을 보장받기 위해서는 읽기전용의 FIFO와 쓰기전용의 FIFO를 열어야 할것이다.

FIFO 파일의 생성은 mkfifo(3)를 사용하는데, 다음은 실제 FIFO 파일을 만들어내는 간단한 예제이다.

예제 : fifo.c
#include <sys/types.h> 
#include <sys/stat.h> 

int main(int argc, char **argv)
{
    if (mkfifo(argv[1], 0666) != 0)
    {
        perror("mkfifo failure : ");
    }

    return 0;
}
위의 프로그램을 컴파일한후 실행하면 fifo 파일이 만들어지는데, 이를 ls -al 록 확인해보면 아래와 같이 파일의 특성을 확인해볼수 있다. 
[root@localhost test]# ./fifo /tmp/testfifo2
[root@localhost test]# ls -al /tmp/testfifo2
prw-r--r--    1 root     root            0  2월  7 16:26 /tmp/testfifo2
만들어진 fifo 파일의 크기는 해당 파일에 대한 입력이 있더라 하더라도 그 크기는 언제나 0인데 이유는 입력이 파일로 쌓이지 않고, 커널에서 이를 처리하기 때문 이다. 만들어진 fifo 파일은 PIPE 와 달리, 프로세스가 종료하거나, 시스템을 리부팅 시키더라도 사라지지 않으며(파일이니 당연하다), 직접 지워줄때까지 살아있게 된다. 리눅스에서는 FIFO를 만들도록 도와주는 mkfifo(2)라는 프로그램도 있으므로, FIFO 파일을 만들기 위해서 굳이 위의 예제처럼 fifo.c 파일을 만들필요는 없을 것이다. 

만들어진 FIFO 파일로의 읽기및 쓰기는 보통의 파일을 위해서 사용하는 open(2)과 fopen(3) 등 일반적인 파일 열기/엑세스 함수군을 사용하면 된다.

예제 : testfifo.c
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <stdio.h> 
#include <unistd.h> 

int main()
{
    FILE *fp;

    char buf[255];
    if((fp = fopen("/tmp/testfifo", "rw")) == NULL)
    {
        perror("open error : ");
        exit(0);
    }


    memset(buf, 0x00, 255);
    while(1)
    {
        while(fgets(buf, 255, fp) != NULL)
        {
            printf("%s", buf);
            memset(buf, 0x00, 255);
        }
    }
}
FIFO 를 위한 명명된 파일이름은 "/tmp/testfifo" 로 정했다. 
위의 프로그램을 컴파일한다음에 실행시키면, read 에서 블록되는데, 이때 /tmp/testfifo 로 표준출력 시키면 해당 메시지를 read 해서 화면에 출력시켜 줄것이다. echo 를 통해서 간단하게 테스트를 해보자
[yundream@localhost /tmp]# echo "hello world" > testfifo


이번에는 좀더 복잡한 프로그램을 만들어 보도록 하자. 
위의 예제는 단지 서버측에서 받아서 뿌리기만 했었는데, 이번에는 클라이언트측 에서 서버측으로 메시지를 보내면 서버측에서 메시지를 받은다음 다시 클라이언트 측에 보내고 클라이언트측에서 이 값을 출력하는 프로그램을 만들도록 하겠다

읽기/쓰기를 위해서 2개의 FIFO 파일을 만들어서 통신을 하게 될것이며, 각각의 이름은 fifo_c, fifo_s 로 하도록 하겠다.
프로그램이 하는 일은 fifo_c.c 에서 fifo_s.c 로 숫자를 보내면 fifo_s.c 에서는 받은 숫자를 "거듭제곱" 해서 fifo_c.c 로 다시 보내주고 fifo_c 에서는 이를 출력시켜주는 간단 프로그램이다. 
쉘에서 mkfifo 명령을 이용해서 /tmp/fifo_c, /tmp/fifo_s 두개의 FIFO 파일을 생성하도록 하자. 

fifo_s.c
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <stdio.h> 
#include <unistd.h> 

#define chop(x) x[strlen(x) - 1] = 0x00 

int main()
{
    int fp_w;
    int fp_r;

    int n;
    char buf_r[11];
    char buf_w[11];
    if((fp_r = open("/tmp/fifo_c", O_RDONLY)) < 0)
    {
        perror("open error : ");
        exit(0);
    }
    if((fp_w = open("/tmp/fifo_s", O_WRONLY)) < 0)
    {
        perror("open error : ");
        exit(0);
    }


    memset(buf_r, 0x00, 11);
    memset(buf_w, 0x00, 11);

    while((n = read(fp_r, buf_r, 11)) > 0)
    {
        printf("%s", buf_r);
        chop(buf_r);
        sprintf(buf_w, "%d\n", atoi(buf_r) * atoi(buf_r));

        write(fp_w, buf_w, 11);

        memset(buf_r, 0x00, 11);
        memset(buf_w, 0x00, 11);
    }
    exit(1);
}
프로그램이 하는일은 간단하다.
/tmp/fifo_c 에서 메시지를 읽어서 이걸 제곱해서 /tmp/fifo_s 로 쓴다. 

fifo_c.c
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <stdio.h> 
#include <unistd.h> 

#define chop(x) x[strlen(x) - 1] = 0x00 

int main()
{
    int fp_r;
    int fp_w;
    int n;
    int i;
    char buf_r[11];
    char buf_w[11];

    if((fp_w = open("/tmp/fifo_c", O_WRONLY)) < 0)
    {
        perror("open error : ");
        exit(0);
    }
    if((fp_r = open("/tmp/fifo_s", O_RDONLY)) < 0)
    {
        perror("open error : ");
        exit(0);
    }


    i = 1;

    memset(buf_r, 0x00, 11);
    memset(buf_w, 0x00, 11);
    sprintf(buf_w, "%d\n", i);

    while((n = write(fp_w, buf_w, 11)) > 0)
    {
        read(fp_r, buf_r, 11);

        printf("%6d^2 = %s", atoi(buf_w), buf_r);
        memset(buf_r, 0x00, 11);
        memset(buf_w, 0x00, 11);
        i++;
        sprintf(buf_w, "%d\n", i);
        sleep(1);
    }
}

이 프로그램은 1부터 프로그램을 종료시킬때까지 순차적으로 (1씩증가) /tmp/fifo_c에 쓰고, 그 결과값(제곱)을 /tmp/fifo_s 에서 읽어오고, 그 값을 화면에 출력한다. fifo_s 를 먼저 실행하고 그다음 fifo_c 를 실행하면 된다. 다음은 실행화면이다.
[yundream@localhost test]#./fifo_s
1
2
3
4
5
6
7
8
9
10
11

[yundream@localhost test]#./fifo_c
     1^2 = 1
     2^2 = 4
     3^2 = 9
     4^2 = 16
     5^2 = 25
     6^2 = 36
     7^2 = 49
     8^2 = 64
     9^2 = 81
    10^2 = 100
    11^2 = 121
...


출처 : http://joinc.co.kr/modules/moniwiki/wiki.php/Site/system_programing/IPC/FIFO
:

01. 링킹의 기본 이해

Language/C 2011. 9. 8. 12:16

링킹 과정은 컴파일과 링킹에서 마지막 과정으로 조각난 오브젝트 파일들을 하나의 바이너리 이미지로 합치는 과정

 

|| 링킹과정 절차

main.c

funcs.c

#include <stdio.h>

void func1();
void func2();

int var1 = 0x11111111;
int var2;
int var3 = 0;

int main(){
        static int var4 = 0x22222222;
        static int var5;
        int var6;

        printf("This is main() function()!\n");
        func1();
        func2();

        return 0;
}

#include <stdio.h>

extern int var1, var2;
int var8 = 0x33333333;
const int var9 = 0x12345678;
int var10;

void func1(){
        printf("func1() call\n");
        printf("var1 = 0x%X, var2 = 0x%X\n", var1, var2);
}
void func2(){
        printf("func2 call\n");
}

 

$ gcc -o test main.c funcs.c -v -save-temps

출처:

http://www.oneone.kr/?mid=UnixPUtil&page=1&document_srl=5700

 

:

03. gcc를 사용해 원하는 컴파일 하기

Language/C 2011. 9. 8. 12:16

|| 가장 단순한 컴파일 명령

gcc -W -Wall -O2 -o like like.c


|| 반드시 알아야 할 gcc 필수 옵션 및 중요 옵션


1) gcc C컴파일러 드라이버 옵션


-E : 전처리 과저의 결과를 화면에 보이는 옵션, 추가적으로 -save-temps 옵션을 사용해 like.i 파일을 읽어보는 것이 더 좋은 방법


-S : 어셈블리 파일만 생성하고 컴파일 과정을 멈춘다.


-c : 오브젝트 파일까지만 생성하고 컴파일 과정을 멈춘다.


-v : 과정 보여주기


-save-temps : 컴파일과정에서 생성되는 *.i, *.s, *.o 지우지 않고 저장한다.



2) cc1 -E 또는 cpp0 전처리기 옵션


- I : 헤더파일을 탐색하는 기본디렉토리를 추가할 때


-D : 매크로를 외부에서 define할 때 사용, 반대는 -U[매크로]


3) cc1 C 컴파일러의 옵션



* 경고 옵션


-W과 -Wall : Wall옵션은 모든 모호한 문법에 대한 경고메시지 출력, -W는 합법적이지만 모호한 코딩에 대한 부가적인 정보제공


-w : 모든 경고메시지 제거



* 최적화 옵션


-O0

-O1

-O2

-O3

-Os : 사이즈 최적화, 임베디드 시스템 같은데 사용



* 디버깅 옵션


-g : 디버거에 제공하는 디버깅 정보를 바이너리에 삽입한다.

 최적화옵션처럼 레벨이 있는데 -g0 ~g3, 기본은 g2와 동일


4) as의 옵션

생략


5) collect2 또는 ld의 옵션


-L[라이브버리 디렉토리] : 라이브러리를 찾을 디렉토리를 지정한다.


-[라이브버리 이름] : 라이브러리의 이름은 앞에 오는 lib를 떼어버리고 확장자 이전까지의 라이브러리명이다.


-shared : 공유 라이브러리를 우선 링크, 디폴트는 공유 라이브러리


-static : 정적 라이브러리 우선링크

:

02. 알아두면 정말 유용한 C소스 컴파일 과정(3)

Language/C 2011. 9. 8. 12:15

2) cc1 중단부에서 하는 일

 중단부에서 하는 일은 GIMPLE트리를 입력받아 SSA형태로 변환한 후 SSA에 기초한 아키텍쳐 비종속적인 최적화를 수행하고, 최종적으로 cc1후단부에서 사용할 RTL을 생성하는 일이다.

 -아키텍쳐 비종속적 : 아키텍쳐에 구애 받지 않는 최적화


cc1 중단부 과정 

 중간표현(GIMPLE트리) → 중간표현(SSA) → [Tree SSA Optimizer] → 중간표현(RTL) 


* SSA 변환

i = 10

j = i + 10

k = j + 20 

이를 SSA로 변경하면

i_1 = 10

j_2 = i_1 + 10

k_3 = j_2 + 20

이처럼 된다.


컴파일시 -fdump-tree-all -O3 옵션을 지정하고 컴파일하면 ssa파일이 생성된다.

 [testone@oneone study]$ gcc -O3 -o CTest CTest.c -fdump-tree-all

[testone@oneone study]$ gcc -O3 -o CTest CTest.c -fdump-tree-all

[testone@oneone study]$ ls

CTest                             CTest.c.056t.ccp2         CTest.c.093t.copyprop4

CTest.c                           CTest.c.057t.phiprop      CTest.c.094t.dceloop1

CTest.c.001t.tu                   CTest.c.058t.fre          CTest.c.095t.lim

CTest.c.003t.original             CTest.c.059t.dce2         CTest.c.096t.pcom

CTest.c.004t.gimple               CTest.c.060t.forwprop2    CTest.c.097t.unswitch

CTest.c.006t.vcg                  CTest.c.061t.copyprop2    CTest.c.098t.sccp

CTest.c.007t.useless              CTest.c.062t.mergephi2    CTest.c.099t.empty

CTest.c.010t.lower                CTest.c.063t.vrp1         CTest.c.102t.ivcanon

CTest.c.011t.ehopt                CTest.c.064t.dce3         CTest.c.103t.ifcvt

CTest.c.012t.eh                   CTest.c.065t.cselim       CTest.c.104t.vect

CTest.c.013t.cfg                  CTest.c.066t.dom1         CTest.c.105t.veclower2

CTest.c.014t.cplxlower0           CTest.c.067t.phicprop1    CTest.c.106t.dceloop2

CTest.c.015t.veclower             CTest.c.068t.ifcombine    CTest.c.107t.cunroll

CTest.c.021t.cleanup_cfg1         CTest.c.069t.phiopt1      CTest.c.110t.ivopts

CTest.c.023t.early_optimizations  CTest.c.070t.tailr2       CTest.c.111t.loopdone

CTest.c.024t.ssa                  CTest.c.071t.ch           CTest.c.114t.reassoc2

CTest.c.026t.einline2             CTest.c.073t.cplxlower    CTest.c.115t.vrp2

CTest.c.027t.cleanup_cfg2         CTest.c.074t.sra          CTest.c.116t.dom3

CTest.c.028t.copyrename1          CTest.c.075t.copyrename3  CTest.c.117t.phicprop3

CTest.c.029t.ccp1                 CTest.c.076t.dom2         CTest.c.118t.cddce

CTest.c.030t.forwprop1            CTest.c.077t.phicprop2    CTest.c.120t.dse2

CTest.c.031t.addressables1        CTest.c.078t.reassoc1     CTest.c.121t.forwprop4

CTest.c.032t.sdse1                CTest.c.079t.dce4         CTest.c.122t.phiopt3

CTest.c.033t.esra                 CTest.c.080t.dse1         CTest.c.123t.tailc

CTest.c.034t.copyprop1            CTest.c.081t.forwprop3    CTest.c.124t.copyrename4

CTest.c.035t.mergephi1            CTest.c.082t.phiopt2      CTest.c.125t.uncprop

CTest.c.036t.dce1                 CTest.c.083t.objsz        CTest.c.126t.optimized

CTest.c.037t.addressables2        CTest.c.084t.store_ccp    CTest.c.127t.nrv

CTest.c.038t.sdse2                CTest.c.085t.copyprop3    CTest.c.128t.blocks

CTest.c.039t.tailr1               CTest.c.086t.fab          CTest.c.129t.final_cleanup

CTest.c.040t.profile              CTest.c.087t.sincos       like

CTest.c.041t.release_ssa          CTest.c.088t.crited       like.c

CTest.c.051t.apply_inline         CTest.c.089t.pre          love.c

CTest.c.052t.salias               CTest.c.090t.sink         Makefile

CTest.c.054t.retslot              CTest.c.091t.loop

CTest.c.055t.copyrename2          CTest.c.092t.loopinit

 


* RTL 생성

RTL은 GCC에서 기계 독립적인 특성을 제공하기 위해 사용하는 중간표현의 한 형태다.

[testone@oneone study]$ gcc -o CTest CTest.c -da

옵션 -da를 붙여주면 *.expand파일이 생성되는데 RTL표현을 볼 수 있다.

3) cc1 후반부에서 하는 일
중단부에서 RTL이 전달되면 후단부에서는 RTL로 다음과 같은 일을 수행한다.

 후단부에서 하는 일

 RTL → RTL Optimizer → Code Generator → 목적 코드(like.s)



* RTL최적화

 지금까지 아키텍쳐 비종속적인 최적화만 수행하였으나 후단부에서는 비종속+종속적인 최적화가 함께 수행한다.


* 목적코드 생성

목적 코드는 타겟 아키텍처의 어셈블리 코드이다.

 [testone@oneone study]$ gcc -v -save-temps -o CTest CTest.c

[testone@oneone study]$ vi CTest.s

        .file   "CTest.c"

        .section        .rodata

.LC0:

        .string "j = %d, k = %d\n"

        .text

.globl main

        .type   main, @function

main:

        leal    4(%esp), %ecx

        andl    $-16, %esp

        pushl   -4(%ecx)

        pushl   %ebp

        movl    %esp, %ebp

        pushl   %ecx

        subl    $36, %esp

        movl    $0, -12(%ebp)

        movl    $0, -8(%ebp)

        movl    $0, -16(%ebp)

        jmp     .L2

.L5:

        movl    -16(%ebp), %eax

        andl    $1, %eax

        testb   %al, %al

        je      .L3

        addl    $1, -12(%ebp)

        jmp     .L4

.L3:

        addl    $1, -8(%ebp)

.L4:

        movl    -8(%ebp), %eax

        movl    %eax, 8(%esp)

        movl    -12(%ebp), %eax

        movl    %eax, 4(%esp)

        movl    $.LC0, (%esp)

        call    printf

        addl    $1, -16(%ebp)

.L2:

.. 생략 


|| as에 의한 기계어 코드 생성

gcc -o like like.c -v -save-temps 명령은 다음과 같다.

as -V -Qy -o like.o like.s


as에 의해 생성되는 like.o파일은 어셈블된 like.s의 어셈블리어명령어과 데이터가 들어있는 ELF 바이너리 포맷 구조를 가진다. 바이너리 포맷이란 바이너리 파일의 구조를 결정짓는 규약을 말한다.


readelf 명령으로 ELF헤더 정보다.

 

[testone@oneone study]$ readelf -a like.o

ELF Header:

  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00

  Class:                             ELF32

  Data:                              2's complement, little endian

  Version:                           1 (current)

  OS/ABI:                            UNIX - System V

  ABI Version:                       0

  Type:                              REL (Relocatable file)

  Machine:                           Intel 80386

  Version:                           0x1

  Entry point address:               0x0

  Start of program headers:          0 (bytes into file)

  Start of section headers:          320 (bytes into file)

  Flags:                             0x0

  Size of this header:               52 (bytes)

  Size of program headers:           0 (bytes)

  Number of program headers:         0

  Size of section headers:           40 (bytes)

  Number of section headers:         11

  Section header string table index: 8

 


|| collect2에 의한 링킹 과정

  생략.


출처:

http://www.oneone.kr/?mid=UnixPUtil&page=1&document_srl=5669
 
 

:

02. 알아두면 정말 유용한 C소스 컴파일 과정(2)

Language/C 2011. 9. 8. 12:14

|| cc1에 의한 어셈블리 소스 파일로 컴파일


 전처리 과정이 끝났으면 이제 진짜 C컴파일인 cc1이 나설 차례다.

cc1의 컴파일 과정은 크게 세부분으로 나뉠 수 있는데, 전단부 - 중단부 - 후단부 


GCC 컴파일러(cc1, cc1plus, jc1)의 컴파일 과정 

 전처리파일(like.i) → 전단부 → 중간 표현(GIMPLE트리) → 중단부 → 중간표현(RTL) → 후반부 → 

→ 목적코드(like.s)



전단부에서는 소스 코드가 올바르게 작성되었는지 분석


중단부에서는 전단부에서 넘겨받은 GIMPLE 트리를 이용해 아키텍쳐 비종속적인 최적화를 수행


후단부에서는 아케텍쳐 비종속적인 최적화와 함께 아키텍쳐 종속적인 최적화가 수행


비로소 목적 코드를 생성




1) cc1 전단부에서 하는 일

cc1 전단부 처리 과정 

 전처리파일(like.i) → 전단부(어휘/구문/의미 분석) → 중간표현(GIMPLE 트리)

cc1의 전단부는 소스코드의 문법상, 의미상의 오류를 파악하고 최종적인 GIMPLE트리를 생성하는 일이다.


* 중간 표현 생성

GIMPLE트리는 언어 독립적인 특성을 제공하기 위해 사용하는 트리 형태의 중간표현, 여러프로그래밍 언어 소스를 공통된 언어 표현으로 바꾼 후 소스 코드를 트리 형태로 표현한 것으로 GIMPLE 트리가 생성되는 과정은 다음과 같다. 

중간 표현 생성 과정 

 C(AST) → C(genericize)            ↘   

 C++(AST) → C++(genericize)       →  GENERIC 트리 →  Gimplify → GIMPlE 트리

 Java(AST) → JAVA(genericize)  ↗ 


GENERIC 트리는 소스를 트리 구조로 표현하는 데 있어 언어 독립적인 특징을 제공하기 위해 만들어졌다. 그래서 GCC에서 지원하는 많은 언어의 소스 코드는 GENERIC 트리로 표현 가능하다.

이렇게 컴파일러가 언어 종속적이게 되는 문제를 해결하기 위해 GENERIC틀 같은 공통된 중간 표현으로 바꾼다.



GIMPLE 트리 확인

CTest.c 내용 

#include<stdio.h>


int main()

{

        int i, j, k;


        j = 0;

        k = 0;


        for(i=0; i<10; i++)

        {

                if((i%2) != 0) {

                        j += 1;

                }

                else {

                        k += 1;

                }

                printf("j = %d, k = %d\n", j, k);

        }


        return 0;

}

 


다음 명령으로 GIMPLE트리를 확인 할 수 있다.

[testone@oneone study]$ gcc -o CTest Ctest.c -fdump-tree-all

[testone@oneone study]$ gcc -o CTest Ctest.c -fdump-tree-all 

[testone@oneone study]$ ls

CTest                  CTest.c.006t.vcg      CTest.c.013t.cfg           like

CTest.c                CTest.c.007t.useless  CTest.c.014t.cplxlower0    like.c

CTest.c.001t.tu        CTest.c.010t.lower    CTest.c.015t.veclower      love.c

CTest.c.003t.original  CTest.c.011t.ehopt    CTest.c.021t.cleanup_cfg1  Makefile

CTest.c.004t.gimple    CTest.c.012t.eh       CTest.c.051t.apply_inline

 



 CTest.c.004t.gimple 파일 내용

 main ()

{

  int D.1593;

  _Bool D.1594;

  int D.1595;

  int i;

  int j;

  int k;


  j = 0;

  k = 0;

  i = 0;

  goto <D.1591>;

  <D.1590>:;

  D.1593 = i & 1;

  D.1594 = (_Bool) D.1593;

  if (D.1594)

    {

      j = j + 1;

    }

  else

    {

      k = k + 1;

    }

  printf (&"j = %d, k = %d\n"[0], j, k);

  i = i + 1;

  <D.1591>:;

  if (i <= 9)

    {

      goto <D.1590>;

    }

  else

    {

      goto <D.1592>;

    }

  <D.1592>:;

  D.1595 = 0;

  return D.1595;




출처:

http://www.oneone.kr/?mid=UnixPUtil&page=1&document_srl=5657 
: