2012년 3월 28일 수요일

fd,파일디스크립터,socket 참고


1.시작하기
이 문서에서는 fd(file descriptor),소켓에 대해 조금 삐딱한 시선으로 설명해 볼 참이다.
많은 초보 플밍 입문서나 네트워크 입문서에 이 용어들에 대해 설명을 시도하고 있긴 하지만 아직 리어카 한대분의 공부도 마치지 못한,더구나 게임 중독자인 내가 볼 때 그 설명들이 바이올린처럼 높고 날카롭거나 아니면 첼로처럼 깊고 공허해서 원초적 감동을 안겨주는 그런내용,즉 가끔은 모든 삶의 부조리와 비애에도 불구하고 '살아서 좋았다!' 는 정도까지 엔지니어의 심장을 겨눈다고 할 만한 그런 내용들은 아직 본 적이 없다.
물론 미천한 내가 이 글에서 그러한 지고의 의식을 갖고 도전한다는 뜻은 결코 아니다.기대조차 말라.
하지만 어설픈 풀피리 정도라도,휘파람 정도라도 나는 내 방식안에서 설명해 볼 참이다.

여기에 당신의 너그러움을 구하는 바이다.
2.fd,파일디스크립터,파일지시자,파일기술자,,,도대체 이게 뭐냐?
한글번역에서(이 번역은 누가 어떻게 했는지 모르지만 참 적절한 사용인 것 같다.)지시자/기술자 등이 컴퓨터 프로그래밍에서 쓰이면 이는 거의 포인터의 개념으로 보면 된다.
포인터는 일정 메모리블럭의 시작주소를 값으로 갖기 때문에 fd 와 연계하여 생각하면 사용자영역에서 커널영역으로 접근할 수 있는 통로이며 그 통로는 커널의 내부 작용에 의해 커널영역안에 커널자신이 생성한 적절한 구조의 메모리블락을(버퍼를) 포인터하고 있는 변수의 이름으로 해석하면 된다.

2.1.커널영역,사용자영역
부팅시 CPU 가 자신이 설계된 방식대로 메모리를 블록/초기화하여 커널에 리턴하면 커널은 이 전체 메모리를 커널영역과 사용자영역이라는 2개의 커다란 블럭으로 구분하여 초기화한다.
  • 두 블록사이의 통신은,즉 커널 <------> 사용자 프로세서간 통신은 사용자프로세서가 커널에게 하는 특정 함수사용에 의한 특정 호출작동에 대해 커널이 그 권한을 검사한 후 적절한 요청에 대해서 커널은 내부의 일정영역에 I/O 를 위한 버퍼블럭을 할당하고 이 버퍼에 링크된 포인터를 fd 번호로 이름지어서 사용자 프로세서에 리턴하게 된다.
  • 사용자 프로세서는 이 번호를 통해서만 커널영역에 접근할 수 있는데 이 연결점,이 포인터,이 레퍼런스,이 인스턴스(용어들 제각기가 다르게 설명될 수 있지만 이 용어들의 바닥에는 '블럭화된 메모리를 포인터하고 있는 주소' 라는 공통의 뉘앙스가 있다.이미지 확장을 도우려 이런 나열을 택했다.)가 리눅스에서는,그리고 아마도 대부분의 유닉스스타일에서는 그 종류의 요청에 무관하게 공통된 일관성을 유지하고 있는데
  • 일관성의 예를 들면 사용자 프로세서의 파일을 여는 요청/소켓을 여는 요청/장치를 여는 요청 등의 경우처럼 서로 다른 목적의 요청에 대해 그것을 처리하는 커널의 내부 기능들 (파일시스템 모듈/네트워크 모듈/디바이스 드라이버 모듈 같은 것들) 역시 다른 영역들이지만 이 것들이 행동하는 스타일은 다음과 같다.


    "해당하는 커널 내부의 블럭안에 I/O 버퍼를 할당하고 포인터에 양의정수로 이름을 붙여 리턴한다."

  • I/O 버퍼의 크기 및 구조는 커널내부의 어떤 모듈이 요청을 처리하느냐에 따라 다른데 커널소스에 각 과정에 대한 추적을 할 수 있으며,어떤 요청에 대해서는 해당모듈 안에서만,어떤 요청에 대해서는 그 버퍼가 여러 모듈에 걸쳐서 참조되거나 전역으로 복사되기도 하지만 이는 커널내부의 동작일 뿐 사용자프로세서는 원래의 목적에 의해 승인받은 그 fd 로 1차적인 접근만 할 뿐이다.
  • 앞에서도 말했듯 포인터에 양의 정수로 이름을 붙여 리턴하며 이는 고유의 커널자원으로 2가지 형태로 커널내부에서 관리되는데

    Per-Process file table (하나의 프로세서와 관계된 : 열린 fd 테이블)
    ->아래 커널소스에서 구조체 fdtable
    Open file table (시스템 전체 : 열린 fd 테이블)
    ->아래 커널소스에서 구조체 files_struct

    로 이 2가지 자료구조는 긴밀히 연관되어 있고 커널의 여러곳에서 서로 교차하여 참조되고 있는 것을 볼 수 있으며 리눅스커널에서는
    include/linux/file.h 에
    구조체 fdtable와 구조체 files_struct 로 정의되어 있다.(PC방에서 윈도우상에서 리눅스 커널을 검색해 본 결과 커널 버전에 따라 없거나 위치가 다른 버전도 있기는 하다.)
  • 이 2가지 구조체 멤버에 따라 하나의 프로세서가 열 수 있는 fd 의 갯수라든가 시스템 전체에서 열 수 있는 fd 의 갯수가 제한되는데 NR_OPEN,NR_FILE 과 같은 것들이 여기에 적용된다.
2.2.간단히 알아보는 커널의 fd 관련 자료구조의 위치
fd 의 처리를 추적해 위에서 말한 2가지의 fd 테이블을 시작으로 아래처럼 계속 트리구조를 이루게 되는데 미리 말하지만 난 C 플머도 아니고 뭣도 아니고 PC 도 없으므로 오랜 시간에 걸쳐 이 소스를 분석하거나 모든 함수와 구조체에 대해 트리를 파 볼 수는 없다.
플밍에 대한 것을 내게 묻지말라.내가 언젠가 플밍을 하게 되기라도 한다면 아마도 "헬로우 월드"부터 다시 시작하게 될 것이니까 말이다!
앞부분은 커널 자료구조이며 뒷부분은 커널소스에서의 위치이다.

task_struct 에서 시작하여 <---------------  include/linux/sched.h
946 /* open file information */
947         struct files_struct *files;

files_struct안의 fdtable과 files_struct <---------------  include/linux/file.h  
 29 struct fdtable {
 30         unsigned int max_fds;
 31         struct file ** fd;      /* current fd array */
 32         fd_set *close_on_exec;
 33         fd_set *open_fds;
 34         struct rcu_head rcu;
 35         struct fdtable *next;
 36 };
 37
 38 /*
 39  * Open file table structure
 40  */

 41 struct files_struct {
 42   /*
 43    * read mostly part
 44    */
 45         atomic_t count;
 46         struct fdtable *fdt;
 47         struct fdtable fdtab;
 48   /*
 49    * written part on a separate cache line in SMP
 50    */
 51         spinlock_t file_lock ____cacheline_aligned_in_smp;
 52         int next_fd;
 53         struct embedded_fd_set close_on_exec_init;
 54         struct embedded_fd_set open_fds_init;
 55         struct file * fd_array[NR_OPEN_DEFAULT];
 56 };

files_struct 안의 file,inode    <---------------  include/linux/fs.h
747 struct file {
748         /*
749          * fu_list becomes invalid after file_free is called and queued via
750          * fu_rcuhead for RCU freeing
751          */
752         union {
753                 struct list_head        fu_list;
754                 struct rcu_head         fu_rcuhead;
755         } f_u;
756         struct path             f_path;
757 #define f_dentry        f_path.dentry
758 #define f_vfsmnt        f_path.mnt
759         const struct file_operations    *f_op;
760         atomic_t                f_count;
761         unsigned int            f_flags;
762         mode_t                  f_mode;
763         loff_t                  f_pos;
764         struct fown_struct      f_owner;
765         unsigned int            f_uid, f_gid;
766         struct file_ra_state    f_ra;
767
768         unsigned long           f_version;
769 #ifdef CONFIG_SECURITY
770         void                    *f_security;
771 #endif
772         /* needed for tty driver, and maybe others */
773         void                    *private_data;
774
775 #ifdef CONFIG_EPOLL
776         /* Used by fs/eventpoll.c to link all the hooks to this file */
777         struct list_head        f_ep_links;
778         spinlock_t              f_ep_lock;
779 #endif /* #ifdef CONFIG_EPOLL */
780         struct address_space    *f_mapping;
781 };


526 struct inode {
527         struct hlist_node       i_hash;
528         struct list_head        i_list;
529         struct list_head        i_sb_list;
530         struct list_head        i_dentry;
531         unsigned long           i_ino;
532         atomic_t                i_count;
533         unsigned int            i_nlink;
534         uid_t                   i_uid;
535         gid_t                   i_gid;
536         dev_t                   i_rdev;
537         unsigned long           i_version;
538         loff_t                  i_size;
539 #ifdef __NEED_I_SIZE_ORDERED
540         seqcount_t              i_size_seqcount;
541 #endif
542         struct timespec         i_atime;
543         struct timespec         i_mtime;
544         struct timespec         i_ctime;
545         unsigned int            i_blkbits;
546         blkcnt_t                i_blocks;
547         unsigned short          i_bytes;
548         umode_t                 i_mode;
549         spinlock_t              i_lock; /* i_blocks, i_bytes, maybe i_size */
550         struct mutex            i_mutex;
551         struct rw_semaphore     i_alloc_sem;
552         struct inode_operations *i_op;
553         const struct file_operations    *i_fop; /* former ->i_op->default_file_ops */
554         struct super_block      *i_sb;
555         struct file_lock        *i_flock;
556         struct address_space    *i_mapping;
557         struct address_space    i_data;
558 #ifdef CONFIG_QUOTA
559         struct dquot            *i_dquot[MAXQUOTAS];
560 #endif
561         struct list_head        i_devices;
562         union {
563                 struct pipe_inode_info  *i_pipe;
564                 struct block_device     *i_bdev;
565                 struct cdev             *i_cdev;
566         };
567         int                     i_cindex;
568
569         __u32                   i_generation;
570
571 #ifdef CONFIG_DNOTIFY
572         unsigned long           i_dnotify_mask; /* Directory notify events */
573         struct dnotify_struct   *i_dnotify; /* for directory notifications */
574 #endif
575
576 #ifdef CONFIG_INOTIFY
577         struct list_head        inotify_watches; /* watches on this inode */
578         struct mutex            inotify_mutex;  /* protects the watches list */
579 #endif
580
581         unsigned long           i_state;
582         unsigned long           dirtied_when;   /* jiffies of first dirtying */
583
584         unsigned int            i_flags;
585
586         atomic_t                i_writecount;
587 #ifdef CONFIG_SECURITY
588         void                    *i_security;
589 #endif
590         void                    *i_private; /* fs or device private pointer */
591 };


이상은 커널 2.6.20 의 내용이고 2.2와 2.4,2.6 에서의 inode 취급이 바뀌고 있는데 아래 2.4 의 inode 멤버를 보면 공용체로 각 파일시스템 독단적인 것이나 소켓이나 선언하고 있지만 위의 2.6 에서는 이 부분을 찾을 수 없다.
440 struct inode {
441         struct list_head        i_hash;
442         struct list_head        i_list;
443         struct list_head        i_dentry;
444        
445         struct list_head        i_dirty_buffers;
446         struct list_head        i_dirty_data_buffers;
447
448         unsigned long           i_ino;
449         atomic_t                i_count;
450         kdev_t                  i_dev;
451         umode_t                 i_mode;
452         unsigned int            i_nlink;
453         uid_t                   i_uid;
454         gid_t                   i_gid;
455         kdev_t                  i_rdev;
456         loff_t                  i_size;
457         time_t                  i_atime;
458         time_t                  i_mtime;
459         time_t                  i_ctime;
460         unsigned int            i_blkbits;
461         unsigned long           i_blksize;
462         unsigned long           i_blocks;
463         unsigned long           i_version;
464         unsigned short          i_bytes;
465         struct semaphore        i_sem;
466         struct rw_semaphore     i_alloc_sem;
467         struct semaphore        i_zombie;
468         struct inode_operations *i_op;
469         struct file_operations  *i_fop; /* former ->i_op->default_file_ops */
470         struct super_block      *i_sb;
471         wait_queue_head_t       i_wait;
472         struct file_lock        *i_flock;
473         struct address_space    *i_mapping;
474         struct address_space    i_data;
475         struct dquot            *i_dquot[MAXQUOTAS];
476         /* These three should probably be a union */
477         struct list_head        i_devices;
478         struct pipe_inode_info  *i_pipe;
479         struct block_device     *i_bdev;
480         struct char_device      *i_cdev;
481
482         unsigned long           i_dnotify_mask; /* Directory notify events */
483         struct dnotify_struct   *i_dnotify; /* for directory notifications */
484
485         unsigned long           i_state;
486
487         unsigned int            i_flags;
488         unsigned char           i_sock;
489
490         atomic_t                i_writecount;
491         unsigned int            i_attr_flags;
492         __u32                   i_generation;
493         union {
494                 struct minix_inode_info         minix_i;
495                 struct ext2_inode_info          ext2_i;
496                 struct ext3_inode_info          ext3_i;
497                 struct hpfs_inode_info          hpfs_i;
498                 struct ntfs_inode_info          ntfs_i;
499                 struct msdos_inode_info         msdos_i;
500                 struct umsdos_inode_info        umsdos_i;
501                 struct iso_inode_info           isofs_i;
502                 struct nfs_inode_info           nfs_i;
503                 struct sysv_inode_info          sysv_i;
504                 struct affs_inode_info          affs_i;
505                 struct ufs_inode_info           ufs_i;
506                 struct efs_inode_info           efs_i;
507                 struct romfs_inode_info         romfs_i;
508                 struct shmem_inode_info         shmem_i;
509                 struct coda_inode_info          coda_i;
510                 struct smb_inode_info           smbfs_i;
511                 struct hfs_inode_info           hfs_i;
512                 struct adfs_inode_info          adfs_i;
513                 struct qnx4_inode_info          qnx4_i;
514                 struct reiserfs_inode_info      reiserfs_i;
515                 struct bfs_inode_info           bfs_i;
516                 struct udf_inode_info           udf_i;
517                 struct ncp_inode_info           ncpfs_i;
518                 struct proc_inode_info          proc_i;
519                 struct socket                   socket_i;
520                 struct usbdev_inode_info        usbdev_i;
521                 struct jffs2_inode_info         jffs2_i;
522                 void                            *generic_ip;
523         } u;
524 };
결국 file,inode 2가지 주요 자료구조에서 prefix_operations 구조체내의 각 함수가 리턴한 멤버값에 따라 어떤 사용자 프로세서가 관계하는 어떤 fd 에 대해 처리를 하는 것 같은데....이 하부는 예를들어
struct file_operations {

안에 20개가 넘는 함수의 포인터가 있다.
file_operations와 inode_operations 에 대한 세세한 분석은 할 수 없다.짐작컨데 전자는 fd 가 링크하는 메모리버퍼의 취급을,후자는 그 메모리에 관계된 inode 정보의 취급을 하는 것 같다.어디까지나 짐작이다.이게 맞다면 전자는 필요해도 후자는 필요없는 경우도 있지 않을까.......
2.3.실제 전체 시스템 fd 자원 중 할당가능한 번호를 찾아 FD_SET하는 함수의 예
다음은 fs/open.c 중 get_unused_fd() 함수이다.
856 int get_unused_fd(void)
857 {
858         struct files_struct * files = current->files;
859         int fd, error;
860         struct fdtable *fdt;
861
862         error = -EMFILE;
863         spin_lock(&files->file_lock);
864
865 repeat:
866         fdt = files_fdtable(files);
867         fd = find_next_zero_bit(fdt->open_fds->fds_bits, fdt->max_fds,
868                                 files->next_fd);
869
870         /*
871          * N.B. For clone tasks sharing a files structure, this test
872          * will limit the total number of files that can be opened.
873          */
874         if (fd >= current->signal->rlim[RLIMIT_NOFILE].rlim_cur)
875                 goto out;
876
877         /* Do we need to expand the fd array or fd set?  */
878         error = expand_files(files, fd);
879         if (error < 0)
880                 goto out;
881
882         if (error) {
883                 /*
884                  * If we needed to expand the fs array we
885                  * might have blocked - try again.
886                  */
887                 error = -EMFILE;
888                 goto repeat;
889         }
890
891         FD_SET(fd, fdt->open_fds);
892         FD_CLR(fd, fdt->close_on_exec);
893         files->next_fd = fd + 1;
894 #if 1
895         /* Sanity check */
896         if (fdt->fd[fd] != NULL) {
897                 printk(KERN_WARNING "get_unused_fd: slot %d not NULL! ", fd);
898                 fdt->fd[fd] = NULL;
899         }
900 #endif
901         error = fd;
902
903 out:
904         spin_unlock(&files->file_lock);
905         return error;
906 }
907
908 EXPORT_SYMBOL(get_unused_fd);
3.socket,소켓, 너는 또 뭐냐?
소켓에 대한 일반 정의는 다음 RFC 147 에 이미 있다.

내 나름대로 자유롭게 더하자면 소켓은 TCP/IP 식으로 표현한 응용의 주소이다.
이것이 네트워크 계층모델의 이미지를 포함하면서도 응용의 유일성,즉 주소라는 개념으로 정의될 수 있는 이유는 이 socket 이라는 다소 컴퓨터와 거리가 있을 것 같은 미국넘들의 단어로 표현한 같은 이름의 함수가 리턴하는 것이 바로 앞에서 얘기했던 fd 이기 때문이다.
바꿔 말하면
네트워크 용어로서의 소켓은 TCP/IP 의 계층추적을 하면서 각 계층별로 내포된 주소의 의미로
맥주소 -> IP주소 -> 포트주소(번호) -> 소켓(fd = 응용의 주소)
의 연장선에 있는 뉘앙스가 강한 그런 개념이고
플밍용어로서의 소켓은 socket() 함수가 리턴하는 fd 가 하나의 시스템내에서 해당 사용자 프로세서와 커널 내부 모듈로의 최초의 연결점으로서의 주소 역할을 하기 때문에 플밍에서 소켓하면 이 커널에 fd 요청을 하는 함수 자체를 얘기하는 뉘앙스가 강한 개념이다.
네트워크 용어로서의 소켓은 결국 사용자 프로세서가 사용하는
fd  + <네트워크주소 및 전송주소> 하는 작용(bind)을 통해 인터넷상에 존재하는 유일 주소를 추상적으로 일컫는 것이므로 소켓의 기술적 의미는 어디까지나 플밍에서의 소켓,소켓함수가 커널에 요청하는 유형,커널의 그 요청 유형에 맞는 버퍼할당과 이 포인터 리턴 - 까지의 작동중에 거의 다 있다고 할 수 있다.
디스크에 있는 파일의 open 에도 fd, 소켓의 연결에도 fd,,,,라면 두 fd 는 차이가 있는가? 차이는 뭔가? 서로 혼용하여,예를들어 open 으로 할당받은 fd 에 주소를 bind 하는 것이 가능한가?.........
이런 물음에 대한 답은 앞에 거의 다 있다.
둘의 fd 는 같다.
함수자체도 커널 인터페이스에서 하나의 식별자 역할을 하므로 open 시스템콜이 오면 커널은 파일시스템루틴아래에,socket 시스템콜이 오면 네트워크루틴아래로 점프하여 버퍼를 할당하고 포인터변수를 양의정수로 이름지어 리턴한다.
따라서 포인터하고 있는 커널내의 버퍼 위치와 버퍼의 자료구조가 다르며 당연히 혼용하여 사용할 수 없다.
파일시스템처리루틴아래 할당된 버퍼의 크기와 모양은 네트워크를 위한 것과는 다르며,조금이라도 다를 경우 엄격하게 이는 에러를 리턴하기 때문이다.
3.1.플밍에서 소켓일반
이는 사용자 프로세서가 커널의 네트워크모듈내의 I/O 버퍼로 통하는 포인터로 fd 를 할당받기 위한 요청에 광범위하게 쓰이는 함수로서의 socket() 으로 구현되는 과정을 대략적으로 살펴 봄으로써 이해가능하다.
음.....소켓 함수에 대해 몇 문서를 보았지만 리눅스 맨페이지가 가장 맘에 들었다.
int socket(int domain, int type, int protocol); 
  • 첫 인자는 AF_ 또는 PF_ 접두사가 붙어 있는 상수로(AF 는 address family / PF 는 protocol family 의 약자)예를들어 IPv4 는 AF_INET 또는 PF_INET 이고 IPv6 는 AF_INET6 또는 PF_INET6 와 같이 큰 범위의 주소 세그먼트이고 이는
    include/linux/socket.h 
    에 상수로 정의되어 있다.
    AF_ 이든 PF_ 이든 아무거나 사용해도 매크로로 정의되어 있으므로 관계없다.
  • 세번째 인자는 첫 인자의 하부 개념으로 domain 세그먼트의 protocol 옵셋 으로 생각할 수 있다.
    첫 인자가 PF_INET 처럼 평범한 경우에는 'IP 버전 4 에 해당하는 커널 처리루틴 안에 메모리블럭을 만들어 주세요~' 라는 식의 요청의미가 있고
    이어 두번째 인자로 SOCK_STREAM/SOCK_DGRAM 을 주면 커널은 TCP/IP 의 전송영역의 구현에 맞는 처리를 선택하게 되는데 TCP/IP 전체 표준에서 전송영역은 TCP 또는 UDP 가 있으며 이는 전송의 전부이므로 커널은 사용자 프로세서가 요청하는 fd 의 유형을 충분히 알 수 있으므로 세번째 인자는 0 으로 전달하며 '정의되지않은' 또는 '모든 유형의' 프로토콜을 의미한다.
    즉 이 2가지 타입의 경우는 ICMP 와 같이 전송영역의 표준이 아닌 값을 줄 수 없다.
  • 반면 아래 생소켓부분에서처럼 이 2번째 인자가 SOCK_RAW 같은 경우 세번째 인자로 0을 지정하거나 아니면 커널이 알 수 있는 ICMP,IGMP 를 지정하거나 아니면 예약되지 않은 값을 지정하여 서버와 클라이언트간 사설의 독자적인 프로토콜을 구현한다. 
  • 두 번째 인자는 커널이 생성할 버퍼의 유형에 대한 추가정보를 기술한다.이 버퍼의 유형은 TCP/IP 계층의 전송영역의 처리에 해당하며 다음과 같은 아주 일반적인 사용자 프로세서의 요청은
    socket(AF_INET,SOCK_STREAM,0)
    "커널 IPv4 처리루틴안의 전송루틴안에 그에 맞는 I/O 블럭을 확보하고 fd 를 리턴해 달라" 라는 식의 문장이 된다.(사실상 넷트웍장비안의 특수 내부기능이거나 ICMP,IGMP가 아니면 IP 루틴이(버전4 및 6) 네트워크루틴의 유일한 것이다.) 
3.2.생소켓(RAW SOCKET)
생소켓은 위에서 말한 아주 평범한 소켓과 다르게 커널의 네트워크루틴안에,즉 전송영역이 아니라 네트워크영역에 I/O 버퍼를 확보하고 사용자 프로세서에 fd 를 리턴한다.따라서 이 요청은 루트 또는 루트가 설정한 권한의 사용자만 가능하다.

일반적인 유닉스스타일로는
socket(AF_INET, SOCK_RAW, protocol);
리눅스에서는 위 방법도 되고 좀 더 세부적으로
socket(PF_PACKET,SOCK_RAW, int protocol);
과 같은 형태를 지원한다. 이는 리눅스 단독의 것으로 보이는데 확실치는 않다.(2번째 인자에 SOCK_DGRAM 을 사용할 수도 있으나 그 차이는 링크계층-커널이 네트워크 장치드라이버로부터 리턴받는,내가 다른문서에서 언급했던 시스코의 양다리 그림을 연상하라-의 헤더를 포함하는가 제거하는가의 차이이므로 생소켓의 본질적인 개념 설명에는 중요하지 않아서 SOCK_RAW 로만 했다.)
  • 첫 인자가 크게 주소군을 지정하고 세번째 인자로 그 주소군 안에 세부 주소를 지정한다는 의미를 세그먼트와 옵셋으로 연결하여 말했는데
  • 생소켓은 3번째 인자를 지정하지 않으면 일반소켓과 같이 앞의 도메인에 속하는 모든 하부 프로토콜을 의미하니깐 중요하지 않고 지정을 하게되면 이 값이 일치해야 하는데 예를들어 내가 다른 문서에서 언급했던 AoE(ATA over Ethernet) 같은 경우를 구현하는 SAN 스토리지와 여기 액세스하는 시스템간의 통신의 경우 세번째 인자에 include/linux/if_ether.h 의 다음 매크로 값을 주어서 할 수 있다.

    #define ETH_P_AOE       0x88A2          /* ATA over Ethernet            */
  • 다시 말하지만 생소켓은 커널의 네트워크루틴에서 전송이 아닌 네트워크계층에 fd 를 위치시키므로 특별한 소켓이다.그리고 이것은 주로 시스템 점검 및 모니터링을 위한 root 용 프로그램에 있고 대표적으로 ping,traceroute,tcpdump 와 같은 프로그램이 내부적으로 이런 생소켓 호출을 한다.
  • ICMP,IGMP 와 같은 프로토콜을 지정한 통신에서 반드시 생소켓 호출이 일어나는데 지금에 와서는 아닌 부분도 있지만 이런 개념이 태어날 초기에는 ICMP 가 시스템의 네트워크 모듈이 이해하는 프로토콜로 기술되는 측면이 강했고,IGMP 는 라우터의 네트워크 모듈이 이해하는 프로토콜로 기술되는 측면이 강했기에 전송을 제외하고 네트워크계층에서 해석하고 포맷할 수 있는 간략하고 빠른 방법을 설계하기 위해 특별히 만들어진 것이라 보면 된다.
  • 현 시점에서는 일반인들은 ping 정도면 족하고 시스템 엔지니어라 할 만한 사람들은 각 시스템 독자적인 프로토콜의 경우도 눈여겨 볼 필요가 있다.내가 다른 문서에서 언급했던 스토리지 장비와 IEEE 의 헤더타입이 0x8100 일 경우 등과 혼합해서도 살펴보라.
    리눅스의 몇 문서에서,시스코나 인터넷의 떠도는 문서에서 차츰차츰 고속/대량의 데이타 통신에 이 네트워크계층에서 직접 처리하는 방식이나 링크계층에서 직접 처리하는 방식,즉 TCP/IP 의 계층 중에서 중간과정을 생략하여 시스템 부하를 줄이고 응용에 빠르게 접근할 수 있게끔하는 개념이 언급되는 문서를 자주 보곤한다.
    프로그래머라면
    tcpdump 개발자들이 배포하여 아주 유명하며 생소켓을 통한 읽기 기능이 필요한 곳에 많이 쓰이는 pcap 라이브러리와 시스코의 프로그래머가 개발한 읽기 및 쓰기 기능이 필요한 곳에 쓰일 수 있는,pcap 보다는 알려지지 않았지만 내가 보기에 깔끔할 것 같은 libnet 을 참고해 보라.다음 사이트를 따르라.

    http://www.tcpdump.org/
    http://www.packetfactory.net/projects/libnet/
  • ICMP 및 IGMP 는 커널내에 오래전부터 이식되었다고 말했다.
    따라서 이 두가지 프로토콜이 지정되어 호출되면 이 프로그램을 사용할 때 커널은 ICMP 헤더포맷에 따라 type,code,checksum 등을 구분하여 또 다른 처리를 하지만(예를들어 커널은 ICMP 메세지가 올 경우 헤더를 분석해서 Type 값이  8/13/18 일 경우 에코응답 등 자신이 직접 처리하고 type 값이 내장 값이 아닐 경우 생소켓으로 가거나 드랍한다.)  커널에 기본 포함되지 않은 AoE 같은 경우 외부 AoE 모듈을 호출하여 리턴된 것을 생소켓을 통해 사용한 프로그램의 fd 에 전달하게 된다.
    즉 TCP/IP 또는 OSI 측면에서 커널의 링크계층에서 AoE 드라이버를 호출하여 리턴된 것이 네트워크계층으로 올라오고 네트워크계층은 이를 생소켓으로 바로 응용의 fd로 전달한다는 말이다.
  • IEEE 이더넷 프로토콜에 대해 이전에 간단히 다룬 바 있으나 사실상 나 자신 해골이 아파 더 이상 정리된상태가 아니다.따라서 위의 설명에서 네트워크 계층간/내부 응용간 관계설명은 약간의 이미지를 가지는 정도로 그치기 바란다. 오류가 많을테니 말이다.
4.FILE 에 대해
여러 질문들이 있었기 때문에 이건 추가정보로 여기 쓴다.
stdio.h 에서 제공하는 FILE 자료형은
glibc-2.7/libio/libio.h (C 라이브러리로)에 _IO_FILE 로 정의되어 있는 구조체의 다른이름으로
glibc-2.7/libio/stdio.h 에 다음처럼 다른이름으로 정의되어 있다.
typedef struct _IO_FILE FILE;
원래의 libio.h 의 _IO_FILE 은
struct _IO_FILE {
  int _flags;  /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr; /* Current read pointer */
  char* _IO_read_end; /* End of get area. */
  char* _IO_read_base; /* Start of putback+get area. */
  char* _IO_write_base; /* Start of put area. */
  char* _IO_write_ptr; /* Current put pointer. */
  char* _IO_write_end; /* End of put area. */
  char* _IO_buf_base; /* Start of reserve area. */
  char* _IO_buf_end; /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;
  struct _IO_FILE *_chain;
  int _fileno;
#if 0
  int _blksize;
#else
  int _flags2;
#endif
  _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */

#define __HAVE_COLUMN /* temporary */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  /*  char* _save_gptr;  char* _save_egptr; */
  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};




출처 URL : http://www.linux.co.kr/home2/board/subbs/board.php?bo_table=linuxetc&wr_id=609&page=2 

2012년 3월 27일 화요일

RPM 설치하기

rpm은 기본적으로 확장자가 .rpm으로 끝나는 패키지를 설치하고 제거하는 기능을 합니다. 
물론 이외에도 수없이 많은 기능과 옵션이 존재합니다만 그리 자주 사용되지는 않습니다.

X Winodw용 rpm 패키지 관리자가 있기는 하지만 모두 콘솔용 rpm을 응용하고 있기 때문에
rpm 관리자는 꼭 알아두셔야 합니다. 

가장 기본적으로 버전과 도움말을 보겠습니다.

> rpm --version
RPM 버전 - 4.4.2
...

> rpm --help
사용법: rpm [옵션...]
...

빼기(-) 기호가 2개입니다.

다음은 rpm의 기본 옵션입니다.

설치 옵션: -i 또는 -U   (install, upgrade)
제거 옵션: -e             (erase)
질의 옵션: -q             (query)
기타: -v              (정보 표시)
       -h              (진행상태 표시)
       --force        (강제설치)
       --nodeps     (의존성 무시)

자 그럼 패키지를 설치해 보겠습니다. 

패키지 install과 upgrade가 있는데 upgrade를 사용하면 먼저 install되어 있는지
확인하므로 upgrade만 사용하셔도 됩니다.

> rpm -Uvh kaffeine-0.8.2-2.i386.rpm

그러면 설치정보를 함께 표시하면서 진행 상황을 "#####..."게 표시해 줍니다.
항상 이렇게 많이 쓰니 외워 두시면 편리합니다.

내 컴퓨터에 설치된 rpm이 ㅤㅁㅕㅈ 개일까요?
질의 옵션을 사용하여 설치된 패키지를 볼 수 있습니다.

> rpm -qa

실로 엄청나게 많습니다. (중간에 멈추려면 Ctrl-C입니다.) 
수세는 설치를 전부 rpm으로 하기 때문에 이것을 다 보기란 불가능합니다.

내가 원하는 패키지가 rpm으로 설치되어 있는지 알아보고 싶다면 다음과 같이 표현합니다.

> rpm -qa | grep kaffeine
kaffeine-0.8.2-2

kaffeine이 설치되어 있다면 위와 같이 버전도 함께 출력됩니다.
(파이프라인은 자주 사용되므로 익숙해지셔야 합니다.)

kaffeine이 설치되어 있지 않다면 아무것도 표시하지 않습니다. 

이제 kaffeine을 삭제해 보겠습니다. (정말 지우시면 YaST로 다시 깔아야 합니다.)

삭제는 다음과 같이 -e 옵션을 사용합니다.  

> rpm -e kaffeine-0.8.2-2

아무 출력이 없다면 성공적으로 삭제된 것입니다.
GUI처럼 보기 좋지는 않지만 기능은 더 막강합니다.

다음에 프로그램 설치할 때 꼭 이용해 보세요. 


출처 URL : http://www.suse.or.kr/bbs/board.php?bo_table=tip&wr_id=1654&page=4

epoll


Contents

1 소개
2 epoll
2.1 epoll에 대해서
2.2 Edge Trigger 과 Level Trigger
2.3 최대 등록 파일 개수
3 epoll API
3.1 epoll_create
3.2 epoll_wait
3.3 epoll_ctl
4 epoll의 장점/단점/해결방법
4.1 장점
4.2 단점
5 예제 프로그램
6 프로젝트 진행
7 참고 문서



1 소개

epoll 사용에 대한 문서입니다. 예전 2.4.x 기준으로 작성한 문서를 계속 유지했었는데요. 2011년인 지금까지 2.4.x를 기준으로 한 문서를 유지할 필요는 없을 것 같아서 2.6.x 버전으로 다시 만들었습니다. 쓰지도 않는 2.4에 대한 내용이 있으니 오히려 헛갈리더라구요.

현재 epoll은 realtime signal과 함께 가장 빠른 입출력 처리 방식 중 하나로 알려져 있습니다. realtime signal보다 더 빠르던가.. 그럴겁니다. 벤치마크 결과가 있는지 찾아봐야 겠군요.

2 epoll

2.1 epoll에 대해서

리눅스의 이벤트 통지 입출력 처리는 주로 select와 poll을 이용한 입출력 다중화모델을 따르고 있습니다. 입출력 다중화를 이용하는 이유는 다수의 프로세스 혹은 스레드를 만들지 않고도 여러 파일을 처리할 수 있기 때문인데요. 프로세스와 스레드의 생성시간이 없으니 그만큼 빠르게 작동할 수 있다는 이점을 누릴 수 있죠. 게다가 IPC를 사용할 필요도 없구요. 멀티 프로세스, 멀티 스레드 환경에서 IPC를 사용해 본 경험이 있다면, 얼마나 까다로운지 아마 이해하실 겁니다.

하지만 select를 이용한 방법은 문제가 좀 있습니다. 아이디어는 훌륭한데, 구현이 그다지 효율적이지 않다는 문제입니다.
  1. 비트 테이블을 하나하나 검사해야 한다.
    select는 고정 비트 테이블인 fd_set을 이용해서 입출력 데이터를 확인하는데요. 고정 비트 테이블이기 때문에 최대 파일 지정번호 + 1만큼의 테이블을 검사해야 한다는 문제가 있습니다. 데이터가 있는 파일의 목록을 돌려주면 참 좋을 건데 말이죠.
  2. 데이터 복사.
    select는 fd_set을 이용해서 데이터를 기다리고, 데이터가 오면 fd_set을 갱신해 버립니다. 이전 fd_set정보를 잃어버리는 거죠. 결국 매번 fd_set을 복사해야 되는데요. 역시 비용이 소비되죠.
epoll은 이름에서 알 수 있듯이 select와 poll로 대표되는 입출력 다중화를 개선한 기술입니다. 즉 아래의 사항에서 기술개선이 이루어졌습니다.
  1. 이벤트가 발생한 파일의 목록을 반환한다.
    파일 목록을 순환하면서 데이터를 처리하면 됩니다. 효율적이죠 ?
  2. 데이터 복사
    fd_set등을 유지할 필요가 없습니다. 효율적이기도 하지만 사용하기도 편하죠.

2.2 Edge Trigger 과 Level Trigger

epoll을 다루다보면 Edge Trigger과 level Trigger 둘 중 하나를 선택해야 하는데요. 간단하지만, 헛갈릴 수 있는 용어라서 설명을 하고 넘어갈까 합니다.

  • Level-triggered는 특정 준위 (상태)가 유지되는 동안 감지.
  • Edge-triggered는 특정 준위가 변화하는 시점에서만 감지.
예를 들어 디지털 신호 0000111000111000111 에서 1에 대한 Trigger이라면 LT는 1이 유지되는 시간동안 횟수에 상관없이 발생하고, Edge-triggered는 0에서 1로 변하는 시점에서만 발생합니다. 즉 이 경우 ET는 3회 발생합니다.


LT 방식은 select나 poll처럼 작동을 하기 때문에 쉽게 이해할 수 있습니다. select, poll은 LT로 작동하는데, 소켓 버퍼가 비기전까지 1로 설정하기 때문에, 소켓에 버퍼가 있는 동안에는 계속 반환합니다.

반면 ET는 처음 이벤트가 발생해서 1이되었을 때만 발생합니다. 만약 이벤트가 발생해서 데이터를 읽었는데, 버퍼의 데이터를 모두 읽지 않고 다음 wait 함수를 호출할 경우, wait 함수에서 봉쇄되는 문제가 생길 수 있습니다. 자칫하면 영원히 봉쇄될 수도 있죠. 다음의 시나리오를 가정해보죠.

  1. read sid of pipe(RFD)에 있는 파일 지정자가 epoll 장치에 ET 상태로 추가된다. 
  2. Pipe write가 2Kb의 데이터를 쓴다.
  3. epoll_wait(2)가 호출되고 RFD는 이벤트가 발생한 파일 지정자를 리턴한다. 
  4. Pipe reader은 RFD로 부터 1Kb데이터를 읽어들인다. 
  5. epoll_wait(2)가 호출된다. 
  6. ET 방식이기 때문에 epoll_wait에서 봉쇄된다.
ET일 경우 생기는 문제를 알 수 있겠죠. 따라서 ET로 작동하게 하려면 non-blocking 소켓에 사용해야만 합니다.
  1. non-block 파일디스크립터를 사용한다.
  2. read(2) 나 write(2) 가 errno로 EGAIN을 반환할때만 wait 을 하도록 하자 (epoll_wait)
ET가 좋은 방법인지는 잘 모르겠습니다. 성능상의 이점이 있다고 하는데, 프로그램이 좀 지나치게 복잡해 질 수 있기 때문입니다. EGAIN일 때까지 루프를 돌면서 데이터를 처리해야 하는데, 이 경우 데이터 버퍼를 관리해야 하기도 하고 여러모로 신경쓸게 많아지거든요.

LT 방식으로 epoll을 사용할 경우, select와 동일한 방식으로 사용할 수 있습니다. 물론 더 빠르겠죠.

epoll은 LT 방식을 기본으로 실행됩니다. 만약 ET방식으로 지정하고 싶다면, 다음과 같이 하면 됩니다.
mev.events = EPOLLIN | EPOLLET ....; 
epoll_ctl(mepollfd, EPOLL_CTL_ADD, afd, &mev); 
 

2.3 최대 등록 파일 개수

/proc/sys/fs/epoll/max_user_watches 에서 확인할 수 있습니다. 여기에는 유저가 등록할 수 있는 파일의 최대 개수가 명시되어 있는데요. User ID당 제한이 설정됩니다. 32bit 커널에서는 파일 하나당 약 90byte의 메모리가 64bit에서는 160바이트가 필요하다고 합니다.

3 epoll API

epoll를 사용하기 위한 함수들을 설명합니다. select의 확장한 기술이기 때문에, 입출력 다중화에 대한 경험이 있다면 쉽게 이해하고 응용할 수 있을 겁니다.

3.1 epoll_create

epoll_create(int size) 
 
epoll_create()는 이벤트를 저장하기 위한 size만큼의 공간을 커널에 요청합니다. 커널에 요청한다고 해서 반드시 size만큼의 공간이 확보되는 건 아니지만 커널이 대략 어느 정도의 공간을 만들어야 할지는 정해줄 수 있습니다. 수행된 후 파일 지정자를 되돌려 주는데, 더 이상 사용하지 않을 거라면 close 함수로 닫아주면 됩니다.

크기는 서비스 마다 다른데요. 일반적으로 예상 최대 동접 x 1.5 정도면 된다고 합니다.

3.2 epoll_wait

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) 
 
실제 이벤트가 발생하는걸 기다리고 있다가, 이벤트가 발생하면 이벤트 관련 정보를 넘겨주는 일을 합니다.

epfd는 epoll_create(2)를 이용해서 생성된 epoll 지정번호구요. 만약 이벤트가 발생하면 반환하는데, 이벤트에 관한 정보는 events에 기록됩니다 . maxevents는 epoll이벤트 풀의 크기고요. timeout는 기다리는 시간입니다. 0보다 작다면 이벤트가 발생할 때까지 기다리고, 0이면 바로 리턴, 0보다 크면 timeout 밀리세컨드 만큼 기다린다. 만약 timeout시간에 이벤트가 발생하지 않는다면 0을 반환합니다.

이벤트가 발생했다면 발생한 이벤트의 갯수를 반환합니다.

3.3 epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 
 
이벤트풀을 제어하기 위해서 사용한다. poll(2)와 매우 비슷하게 작동한다. op는 fd에 대해서 어떤 작업을 할것인지를 정의하기 위해서 사용된다. op가 실행된 결과는event구조체에 적용된다.

다음은 epoll_event구조체의 모습이다.
typedef union epoll_data { 
     void *ptr; 
     int fd; 
     __uint32_t u32; 
     __uint64_t u64; 
} epoll_data_t; 
 
struct epoll_event { 
     __uint32_t events;  /* 발생된 이벤트 */ 
     epoll_data_t data;  /* 유저 데이터로 직접 설정가능하다 */  
}; 
 
epoll_data_t를 유심히 볼필요가 있다. union이라서 사용자 정의 데이터에 대한 포인터를 넘길 수가 있습니다. 예를들어 여기에 pid값이라든지 소켓지정번호 혹은 메시지를 포함한 정보를 구조체로 넘겨줄 수 있는 거죠.

op는 다음과 같은 종류의 작업명령들을 가지고 있습니다. poll(2)와 비교해보면 매우 유사함을 알 수 있을 것이다.
  • EPOLL_CTL_ADD
    fd를 epoll 이벤트 풀에 추가하기위해서 사용한다. 
  • EPOLL_CTL_DEL
    fd를 epoll 이벤트 풀에서 제거하기 위해서 사용한다.
  • EPOLL_CTL_MOD
    이미 이벤트 풀에 들어 있는 fd에 대해서 event의 멤버값을 변경하기 위해서 사용한다.
  • EPOLLIN
    입력(read)이벤트에 대해서 검사한다. 
  • EPOLLOUT
    출력(write)이벤트에 대해서 검사한다.
  • EPOLLERR
    파일지정자에 에러가 발생했는지를 검사한다. 
  • EPOLLHUP
    Hang up이 발생했는지 검사한다.
  • EPOLLPRI
    파일지정자에 중요한 데이터가 발생했는지 검사한다.
  • EPOLLET
    파일지정자에 대해서 ET 행동을 설정한다. 기본 값은 LT.

4 epoll의 장점/단점/해결방법

4.1 장점

  1. 좀더 적은 자원을 차지하면서 효율은 기존의 기술보다 좋다.
  2. select, poll등에 비해 충분히 효율적이면서도 RealTime Signal에 비해서 사용하기 쉽다.

4.2 단점

  1. 표준 지원사항이 아니라서 다른 유닉스에서는 사용할 수 없다.

5 예제 프로그램

epoll시스템 구축을 기념삼아서 간단한 예제프로그램을 만들어 보았다. 아래 프로그램은 echo서버의 epoll버젼으로 LT모드다.
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <string.h> 
#include <sys/epoll.h> 
#include <arpa/inet.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <fcntl.h> 
#include <sys/ioctl.h> 
 
#define SA  struct sockaddr 
#define EPOLL_SIZE        20 
 
int main(int argc, char **argv) 
{ 
    struct sockaddr_in addr, clientaddr; 
    struct eph_comm *conn; 
    int sfd; 
    int cfd; 
    int clilen; 
    int flags = 1; 
    int n, i; 
    int readn; 
    struct epoll_event ev,*events; 
 
    int efd; 
    char buf_in[256]; 
 
    // 이벤트 풀의 크기만큼 events구조체를 생성한다. 
    events = (struct epoll_event *)malloc(sizeof(*events) * EPOLL_SIZE); 
 
    // epoll_create를 이용해서 epoll 지정자를 생성한다.     
    if ((efd = epoll_create(100)) < 0) 
    { 
        perror("epoll_create error"); 
        return 1; 
    } 
 
 
    // -------------------------------------- 
    // 듣기 소켓 생성을 위한 일반적인 코드 
    clilen = sizeof(clientaddr); 
    sfd = socket(AF_INET, SOCK_STREAM, 0);     
    if (sfd == -1) 
    { 
        perror("socket error :"); 
        close(sfd); 
        return 1; 
    } 
    addr.sin_family = AF_INET; 
    addr.sin_port = htons(atoi(argv[1])); 
    addr.sin_addr.s_addr = htonl(INADDR_ANY); 
    if (bind (sfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) 
    { 
        close(sfd); 
        return 1; 
    } 
    listen(sfd, 5); 
    // -------------------------------------- 
 
    // 만들어진 듣기 소켓을 epoll이벤트 풀에 추가한다. 
    // EPOLLIN(read) 이벤트의 발생을 탐지한다. 
    ev.events = EPOLLIN; 
    ev.data.fd = sfd; 
    epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &ev); 
    while(1) 
    { 
        // epoll이벤트 풀에서 이벤트가 발생했는지를 검사한다. 
        n = epoll_wait(efd, events, EPOLL_SIZE, -1); 
        if (n == -1 ) 
        { 
            perror("epoll wait error"); 
        } 
 
        // 만약 이벤트가 발생했다면 발생한 이벤트의 수만큼 
        // 돌면서 데이터를 읽어 옵니다.  
        for (i = 0;    i < n; i++) 
        { 
            // 만약 이벤트가 듣기 소켓에서 발생한 거라면 
            // accept를 이용해서 연결 소켓을 생성한다.  
            if (events[i].data.fd == sfd) 
            { 
                printf("Accept\n"); 
                cfd = accept(sfd, (SA *)&clientaddr, &clilen); 
                ev.events = EPOLLIN; 
                ev.data.fd = cfd; 
                epoll_ctl(efd, EPOLL_CTL_ADD, cfd, &ev); 
            } 
            // 연결소켓에서 이벤트가 발생했다면 
            // 데이터를 읽는다. 
            else 
            { 
                memset(buf_in, 0x00, 256); 
                readn = read(events[i].data.fd, buf_in, 255); 
                if (readn <= 0) 
                { 
                    epoll_ctl(efd, EPOLL_CTL_DEL, events[i].data.fd, events); 
                    close(events[i].data.fd); 
                    printf("Close fd\n", cfd); 
                } 
                else 
                { 
                    printf("read data %s\n", buf_in); 
                    write(events[i].data.fd, buf_in, readn); 
                } 
            } 
        } 
    } 
}

winsock api : select 입출력 다중화 함수


select

select함수는 입출력 다중화를 위해서 사용하는 함수다. 윈도는 BSD select 함수 인터페이스를 그대로 따른다.
int select( 
  __in     int nfds, 
  __inout  fd_set *readfds, 
  __inout  fd_set *writefds, 
  __inout  fd_set *exceptfds, 
  __in     const struct timeval *timeout 
); 
 
함수 선언에서 부터 매개 변수의 자료 형까지 BSD select함수와 다를바가 없다.
  1. nfds : BSD socket과의 호환을 위해 남겨 둔 것으로 실제 사용하지는 않는다. BSD select에서는 데이터 변경을 관리할 파일의 개수를 지정하기 위해 사용한다.winsock은 fd_set에 관리할 파일의 개수를 지정한다.
  2. readfds : 읽기 데이터를 확인할 파일 배열에 대한 포인터 
  3. writefds : 쓰기 데이터를 확인할 파일 배열에 대한 포인터
  4. exceptfds : 예외 사항을 확인할 파일 배열에 대한 포인터 
  5. timeout : 기다림 제한 시간

반환 값

select함수는 fd_set에 포함된 파일목록에서 데이터 변경이 있는 모든 파일의 개수를 반환한다. timeout시간동안 데이터 변경이 없었다면 0을 반환한다.

WSANOTINITIALISEDWSAStartup함수가 성공적으로 실행 된 적이 없다.
WSAEFAULT매개 변수가 잘못 사용 되었다.
WSAENETDOWN네트워크 서비시스템이 실패했다.
WSAENOTSOCK소켓이 아닌 지시자가 포함되어 있다.

설명

select함수는 하나 이상의 소켓의 상태를 알아내기 위해서 사용한다. select함수가 호출 될 때, 각 소켓의 read, write 혹은 error 상태를 검사할 것을 지정할 수 있다. 이들 소켓의 상태 정보는 fd_set 구조체에 입력하는 것으로 요청할 수 있다.

readfds는 소켓에 읽을 데이터가 있는지를 확인한다. 만약 listen 상태에 있는 소켓 즉 듣기 소켓이라면, 클라이언트의 연결이 있을 때, 반환한다. 이 후 서버는 accept함수를 호출해서 연결 소켓을 만든다.

select함수는 데이터 변경이 발생한 파일의 개수만을 반환하기 때문에, fd_set을 순환하면서 어떤 소켓에 데이터 변경이 발생했는지를 검사해야 한다. 이를 위해서는 fd_set 구조체를 제어해야 하는데 이를 위해서 아래의 매크로 함수를 제공한다. 이 매크로 함수는 리눅스와 동일하다.

FD_CLR(s, *set);
fd_set에서 소켓 s를 삭제 한다.
FD_ISSET(s, *set);
fd_set에 소켓 s에 변경된 데이터가 있는지 검사한다. 
FD_SET(s, *set);
fd_set에 소켓 s를 추가한다.
FD_ZERO(s, *set);
fd_set을 0으로 초기화 한다.



CRC : CRC-16, CRC-32에 대한 설명과 구현


CRC(Cyclic Redundancy Check)는 시리얼 전송에서 데이타의 신뢰성을 검증하기 위한 에러 검출 방법의 일종이다.

간 단한 에러 검출방법으로는 parity 비트에 의한 방법과 check-sum에 의한 에러 검출 방법이 있지만 parity 비트에 의한 방법은 데이타 중에 한꺼번에 2비트나 4비트가 변하게 되면 검출을 할 수 없고, check-sum에 의한 방법은 한 바이트에서 +1, 다른 바이트에서는 -1로 에러가 생기는 경우만 해도 에러는 검출 되지 않는다. 즉, 이들 방법으로는 에러를 검출해 낼 수 있는 확률이 대단히 낮다.
CRC에 의한 방법은 높은 신뢰도를 확보하며 에러 검출을 위한 오버헤드가 적고, 랜덤 에러나 버스트 에러를 포함한 에러 검출에 매우 좋은 성능을 갖는 것을 특징으로 한다.

이러한 CRC 방법으로 보통 2가지 종류가 사용 되는데, 원칩 마이크로 프로세서와 같이 간단한 용도에서는 CRC-16 이 사용되고, 이보다 더욱 정확한 에러 검출이 필요한 경우에는 CRC-32를 사용한다.

ZIP,ARJ,RAR 과 같은 압축 프로그램이나 플로피 디스크 등의 데이터 검증 용도에 널리 사용되고 있다.
 
기본 원리
n 비트의 주어진 정보가 있을때, 이를 k 비트 만큼 자리를 올리고 미리 약속한 k 비트의 키 값으로 나누면 r 비트의 나머지가 남게 된다. 송신측에서는 원래의 정보 비트를 k 비트 자리 올린 것에 r 비트의 나머지를 더해서 n+r 비트의 데이타를 만들어 보낸다.
수신측에서는 수신된 n+r 비트의 데이타를 키 값으로 나누어 보고 나머지가 정확히 0 이 되는지를 검사하면 된다.

이 때 k 가 16 비트이면 CRC-16, 32비트이면 CRC-32 가 되고 키 값으로는 수학자 들에 의해 정해진 값을 주로 사용한다.
CRC-16 에는 0x8005, CRC-32 에는 0x04c11db7 이 많이 사용된다. 그리고 r 비트의 나머지를 Frame Check Sequence(FCS)라고 부른다.
여기서 CRC 계산에 사용되는 modulo-2 연산의 세계를 살펴보자.

CRC 계산시의 사칙연산은 carry를 고려하지 않는다.
1 + 1 = 0 (carry는 생각하지 않음)
0 - 1 = 1
덧셈 연산은 뺄셈 연산과 결과가 같으며 XOR 연산과도 같다.
다항식 표현 방법을 통해 CRC 계산 방법을 살펴보자.
(1) 2진 다항식으로 표시

예)비트열 101 --> 다항식 x2 + 1
정보 다항식: 데이터 비트열의 다항식으로 P(x) = pn xn-1 + ... + p3x2 + p2x1 + p1
CRC 다항식: CRC 생성을 위한 다항식으로 G(x) (미리 정해진 키 값)
몫: Q(x)
나머지: R(x)
전송 데이타: T(x)
(2) CRC 계산 방법
P(x)를 k 비트 만큼 자리를 올리고( P(x)에 xk를 곱하는 것과 동일) G(x)로 나누면
xk P(x) = Q(x)*G(x) +/- R(x) 이다.
(k는 CRC 다항식의 최고 차수)
R(x) = dk xk-1 + .. + d2x1 + d1 ( R(x)의 최고 차수는 k-1)
비트열 dk ... d2 d1 을 FCS(Frame Check Sequence) 라 함
위 식에서 xk P(x) + R(x) 는 Q(x)*G(x) 와 같다.
즉, xk P(x) + R(x) 는 G(x)의 배수이므로 G(x) 로 나누면 나머지가 0 임을 알 수 있다.
결과적으로 전송되는 데이터 비트열 : pn ... p3 p2 p1 dk ... d2 d1
즉, 전송 T(x) = xk P(x) + R(x)
 
예) 데이터 비트열 110011 즉 P(x) =x5+x4+x1+x0, CRC 다항식G(x) = x4+x3+1, 즉 11001일 경우 FCS와 전송되는 비트열은?
xkP(x) = x4 (x5 + x4 + x1 + 1) = x9 + x8 + x5 + x4, 비트열로 표시하면 1100110000
                   100001
          ____________
11001 | 1100110000
            11001
          ____________
                     10000
                     11001
          ____________
                       1001    
xkP(x) = Q(x)G(x) - R(x)에서
Q(x) = x5 + x0 이므로,
R(x) = x3 + x0, ---> FCS는1001
따라서 전송되는 비트열 1100111001
 
연산의 최적화
CRC의 계산은 일반 나눗셈 명령을 이용해 구현할 수 없다. 1비씩 shift 하면서 XOR 연산을 통해 나머지를 구해야 한다.
하지만 정보 비트에 대해 하나하나씩 연산을 하는 것에는 분명 속도 개선의 여지가 있다.
실제 계산 시에는 모든 바이트에 대해 CRC 다항식에 대한 CRC값을 계산해 표로 만들어 두고 들어오는 데이타를 인덱스로 삼아 계산값을 바로 얻는 방법을 사용 한다.
CRC-16 C소스 : crc16h.c
CRC-32 C소스 : crc32h.c