수안이의 컴퓨터 연구실

  • Mainpage
  • About Me
  • Tags
  • Metapage
  • Notice
  • Location
  • Keywords
  • Guestbook
  • Admin
  • Write an Article
  • Total | 1620654
  • Today | 34
  • Yesterday | 482

95 Articles, Search for 'Programming/UNIX/Linux C'

  1. 2007/06/09 Linux 에서 malloc 으로 할당된 memory 크기를 알수 있는 방법
  2. 2007/05/14 유저 정보 가져오기
  3. 2007/05/14 Unix 시간 조작하기
  4. 2007/05/14 file 정보(stat) 와 종류 알아내기
  5. 2007/05/14 utmp 를 이용한 사용자 로그인정보 관리
  6. 2007/05/14 library 의 사용
  7. 2007/05/14 PIPE 응용
  8. 2007/05/14 데이타와 포인터
  9. 2007/05/14 fcntl 함수 사용하기
  10. 2007/05/14 터미널 제어
«Prev  1 2 3 4 5 ... 10  Next»
Programming/UNIX/Linux C2007/06/09 12:37

Linux 에서 malloc 으로 할당된 memory 크기를 알수 있는 방법

출처 : http://minzkn.wowdns.com:2744/tattertools/48

리눅스에서 malloc 으로 할당한 총 메모리 크기를 알수 있는 방법이 있는데
아직 모르시는 분들을 위해서 끄적끄적 적어봅니다.

물론 hook 을 통해서도 할수 있고 mcheck 를 통해서도 방법이 있는데
아래의 방법이 매우 편한것 같네요.

설명은 다 생략하고 예제 한번 보여드립니다.

"UNIX/Linux C" 카테고리의 다른 글
  • Linux 에서 malloc 으로 할당된 memory 크기를 알... (0)2007/06/09
  • 유저 정보 가져오기 (0)2007/05/14
  • Unix 시간 조작하기 (0)2007/05/14
  • file 정보(stat) 와 종류 알아내기 (0)2007/05/14
  • utmp 를 이용한 사용자 로그인정보 관리 (0)2007/05/14
2007/06/09 12:37 2007/06/09 12:37
Posted by webdizen
Tags Linux, malloc, memory 크기
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/3041

Leave your greetings.

[로그인][오픈아이디란?]

Programming/UNIX/Linux C2007/05/14 17:49

유저 정보 가져오기

유닉스(Linux 포함) 사에서 유저 정보는 전통적으로 /etc/passwd 에 관리된다. 여기에는 유저의 ID, 패스워드, 사용하는 shell (login shell), 유저에게 할당된 홈디렉토리 경로등의 정보가 들어 있다. 최근의 유닉스 시스템은 /etc/shadow 를 통하여 패스워드 정보를 별도로 저장한다. 그러므로 유저정보를 알아내기 위해서는 passwd 와 shadow 파일의 정보를 읽어야 할필요가 있다. 이번 문서에서는 passwd 와 shadow 에서 유저 정보를 가져오는 방법에 대해서 이야기하도록 하겠다.

목차
1 리눅스 사용자 계정 시스템
1.1 유저(User)
1.2 그룹
1.3 권한
2 유저 정보 얻어오기
2.1 passwd 파일분석을 이용한 기본유저 정보
2.2 Shadow Password을 이용한 유저 패스워드
2.2.1 유저 정보 얻기 프로그래밍


1 리눅스 사용자 계정 시스템
리눅스역시 다른 유닉스들과 마찬가지로 다중사용자 운영체제이다. 이렇다 보니 시스템 자원에 대한 접근권한이 매우 중요해 진다. 예를들어 특정 유저의 개인 신상정보를 가진 파일은 결코 다른 유저들이 볼 수 있어서는 안될 것이며 shutdown과 같은 시스템 다운 명령은 특수한 유저(슈퍼유저)만이 제한적으로 실행가능 해야 할것이다. 아뭏든 네트워크장치, 파일, 프로세스, 장치(디스크, CD-ROM, 프린터등)의 모든 시스템 자원에 대해서 사용자 계정수준에서의 접근제어와 관련된 제한이 필요하게 될 것이다. 회사의 직책과 관련된 시스템과 동일하다고 보면 된다. 일반 사원인지, 대리인지, 과장인지, 사장인지에 따라서 접근할수 있는 회사자원의 레벨이 결정되는 것과 마찬가지다. 일반 공지사항이야 전직원이 볼 수 있지만 경영과 관련된 중요 문건은 최고경영진만 볼수 있어야 할것이다.


1.1 유저(User)
회사로 치원 사원 개개인이다. 리눅스 시스템 관점에서 보자면 시스템에 접근할 수 있는 최소단위 객체가 된다. 유저는 접근을 위해서 자신의 ID와 패스워드를 가지며, 이외에도 시스템에서의 원할한 활동을 위한 "사용자 홈디렉토리", "사용자 쉘", "포함되는 그룹"과 같은 각종 부가적인 정보들을 가지게 된다.


리눅스에서 유저는 크게 2개로 나누게 된다. 절대적인 권한을 가지는 슈퍼유저와 일반유저 유저다.


슈퍼유저는 보통 ID로 root를 가지며 시스템에서 절대적인 영향력을 행사한다. 시스템의 셧다운, 리부팅, 장치의 제어, 유저제어, 파일제어에 있어서 전혀 제한이 없다. 마음만 먹는다면 rm -rf /로 시스템의 모든 파일을 날려버릴 수도 있는 절대 권한자이다.


일반 유저는 슈퍼유저를 제외한 나머지 유저를 가리킨다. 일반유저는 슈퍼유저가 정하는 바에 따라서 시스템내에서의 권한과 활동영역에 있어서 제한을 받게 된다.


1.2 그룹
회사에서는 인적자원의 효율성을 높이기 위해서 개인별로 직책과 직급을 두고 이를 다시 묶어서 부서(팀)를 만들어서 관리한다. 기술개발팀, 경영지원팀, 회계팀, 인사팀등인데, 한명의 직원은 하나 혹은 그 이상의 팀에 소속될 수 있을 것이고 팀의 특징에 따라서 권한이 재조정 될것이다.


회사와 같이 부서(팀)을 만들어서 조직을 효율적으로 만드는것과 같은 구조는 다른 모든 집단에서 (이름만 약간 바꾸어서) 공통적으로 나타나며 운영체제역시 예외없이 이러한 구조를 따른다.


리눅스에서는 이를 그룹이라고 한다. 리눅스에서의 그룹역시 유저자원을 효율적으로 관리하기 위한 장치다. 하나의 유저는 하나이상의 그룹에 포함될 수 있다.

1.3 권한

유저와 그룹은 고유의 권한이 있으며, 이러한 권한은 주로 파일에 적용된다. 즉 파일일 읽을수 있는지(r), 쓸수 있는지(w), 실행할 수 있는지(x)에 대해 적의 하는 것으로 이루어진다. 리눅스에서 모든 것은 파일로 다루어지므로 파일에 대해서 권한을 설정한다는 것은 결국 시스템 전체에 대한 권한을 설정하는 것과 동일한 효과를 가진다.


리눅스는 각각의 파일에 대해서 r, w, x 권한을 유저,그룹,다른유저(other)에 대해서 명시하는 것으로 권한을 제어한다. 권한에 대한 자세한 내용은 유닉스파일을 참고하도록 하자.


2 유저 정보 얻어오기
사원과 팀의 정보를 유지하기 위해서 인사카드가 있는 것처럼 리눅스 역시 이러한 정보를 관리하기 위한 파일을 유지한다. 이번장에서는 이러한 유저정보관련 파일과 이를 제어하는 방법에 대해서 알아보도록 하겠다.

2.1 passwd 파일분석을 이용한 기본유저 정보

User 의 정보는 /etc/passwd 파일에 존재하므로, 우선은 passwd 파일에 사용자의 정보가 어떻게 저장되는 지를 알아보도록 하겠다. 다음은 passwd 파일의 내용중 일부 내용이다
yundream:x:500:500::/home/yundream:/bin/bash

passwd 파일은 하나의 행에 하나의 유저 정보를 가지고 있으며, 7개의 정보를 가지고 있다. 각 정보는 ":" 를 통해 구분된다. 각 필드가 가지고 있는 정보는 다음과 같다.
계정:패스워드:UID:GID:GECOS:디렉토리:쉘

계정은 시스템내에서 사용되는 사용자의 이름으로 영문소문자의 조합으로 이루어 진다.
"패스워드는 사용자가 로그인시 사용되는 비밀번호이다.
ID는 유저ID 로 숫자로 구성되어 있으며, 해당시스템내에서 유일한 값을 가진다. 일반적인 시스템 프로그램은 UID 를 통하여서 유저를 구분한다.
GID는 사용자가 포함된 그룹이며 UID 와 마찬가지로 숫자로 표기된다. ?LInux 상에서는 계정이 생성되면 자신의 그룹이 만들어지며, 해당 그룹에 포함되게 된다. Solaris 상에서 계정이 생성되면 "other" 그룹에 포함되게 된다. 각 계정에 대한 그룹 정책은 OS에 따라서 약간식 다르게 책정되어 지는데 보통 그룹에 대한 정보는 /etc/group 에 저장된다.
GECOS 에는 유저에 대한 기타정보가 저장되며, 이 필드는 채울수도 있고, 그렇지 않고 비워둘수도 있다.
디렉토리는 사용자가 사용하는 홈디렉토리인데 쉘상에서 echo $HOME 를 통해서 자신의 홈디렉토리를 알아볼수 있다.
쉘 은 사용자가 로그인해서 사용하는 shell 의 종류이다. Unix 는 Windows 와는 달리 다양한 종류의 쉘이 존재하는데, 사용자는 그중 원하는 쉘을 선택해서 사용할수 있다. 보통 bash(배쉬쉘), csh(씨쉘) 을 많이 사용한다.

위의 passwd 파일의 내용에는 yundream 계정사용자는 UID 번호가 500 이고, GID 는 500 이며, 홈디렉토리는 /home/yundream 이고 bash 쉘을 사용한다" 라는 정보를 포함하고 있다.


2.2 Shadow Password을 이용한 유저 패스워드
그런데 위의 정보를 보면 두번째 패스워드 필드가 "x" 로 채워져 있는것을 볼수 있다. 이는 사용자의 패스워드 정보를 "다른파일" 에 저장해 두었다는것을 나타낸다. /etc/passwd 파일을 보면 그 권한이 "644" 로 누구든지 읽기 가능하도록 되어 있다는 것을 알수 있다. 이는 누구든지 해당 계정 사용자의 패스워드 를 가져올수 있다라는 의미가 된다. 비록 패스워드가 "암호화" 되어있기는 하지만, 일단 패스워드 파일을 얻어낼수 있다면, 이를 이용해서 사용자의 패스워드를 크랙하는 방법이 가능해 진다. 해서 사용자의 패스워드 정보를 오직 root 권한자만이 읽을수 있는 파일에 저장하도록 해서 패스워드 정보의 보안을 높이는 방법을 채택했는데, 이를 shadow 패스워드 라고 하며 /etc/shadow 에 저장된다. shadow 파일의 권한을 보면 아래와 같다.
rw-------    1 root     root         1019 12월 30 01:32 /etc/shadow

오직 "root" 권한자만이 읽고 쓸수 있도록되어 있음을 알수 있다. shadow 패스워드의 내용은 다음과 같다.
yundream:$1$pT/VnlSK$PApp4lLLblRLoZVfsd3XX1:11657:0:99999:7:::
cvs:$1$3UzIhJPn$qClbSInt/C1X45X1j7SKK1:11545:0:99999:7:::

passwd 파일과 마찬가지로 ":" 를 통해서 각 필드를 구분하며, 눈치 챘겠지만 2번째 필드에 있는 내용이 암호화된 패스워드 문자열 이다.
shadow 에는 패스워드 갱신일, 패스워드의 유효기간 등의 정보가 추가로 들어있다. 각필드에 대한 자세한 내용은 shadow(5) 를 참고 하기 바란다. 최신의 Unix 는 대부분 shadow 패스워드를 지원하고 있다. 보통 패스워드는 crypt(3) 함수를 통해서 만들어 지는데, 리눅스의 경우 더복잡한 패스워드를 만들기 위해서 crypt 후 ?MD5 암호화 까지 적용 한다. Solaris 와 같은 많은 Unix 운영체제는 여전히 crypt 만 적용하기도 한다.


2.2.1 유저 정보 얻기 프로그래밍
passwd 나 shadow 파일에서 유저 정보를 가져오기 위해서 파일을 직접열어서 각 행을 읽어들이고, ":" 를 기준으로 해서 각 필드를 구분해서 유저정보를 가져오는 방법도 있겠지만, 보통은 C 에서 제공하는 함수들을 많이 사용한다.
passwd 정보 : getpwent(3), setpwent(3), endpwent(3)
shadow 정보 : getspent(3), getspnam(3), setspent(3), endspent(3)

예제 : 패스워드 검사 프로그램


위의 코드를 약간만 응용하면 소위 말하는 "사전공격" 을 수행하는 패스워드 크랙 프로그램을 만들수 있다. 자주 사용할만한 단어를 미리 파일등에 입력해 놓고, 그 파일을 crypt 에 대입해서 암호화된 패스워드 파일과 일치하는지 loop 를 돌면서 계속 검사하도록 루틴을 수정하면 된다. 물론 사전데이타는 어떻게 해서든지 얻어와야 한다.


shadow 패스워드 방식을 사용한다면, 패스워드파일이 누출될 가능성이 줄어들긴 하겠지만, 이왕이면 사전공격에 걸리지 않는 패스워드를 사용하는게 바람직 할것이다.



출처 : http://joinc.co.kr/modules.php?name=new ··· 3Dnested
"UNIX/Linux C" 카테고리의 다른 글
  • Linux 에서 malloc 으로 할당된 memory 크기를 알... (0)2007/06/09
  • 유저 정보 가져오기 (0)2007/05/14
  • Unix 시간 조작하기 (0)2007/05/14
  • file 정보(stat) 와 종류 알아내기 (0)2007/05/14
  • utmp 를 이용한 사용자 로그인정보 관리 (0)2007/05/14
2007/05/14 17:49 2007/05/14 17:49
Posted by webdizen
Tags /etc/shadow, 리눅스 사용자 계정, 유저 정보
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2950

Leave your greetings.

[로그인][오픈아이디란?]

Programming/UNIX/Linux C2007/05/14 17:46

Unix 시간 조작하기

어떤 운영체제이든지 정확한 시간을 사용하는 것은 매우 중요한 문제이다. 시간은 약속과 같은 것이므로, 기본적으로는 모든 컴퓨터 혹은 프로그램이 정확한 시간에 동작할 것이라는 가정을 할수 있어야 한다. 또한 시간은 대부분의 경우 유일하다고 가정할수 있음으로, 유일한이름의 무언가(파일명 같은)를 만들때도 유용하게 사용할수 있다. 그리고 무언가 기록을 남기고자 할때, 시간은 매우 중요하게 사용된다. 이번에는 유닉스상에서 시간을 다루는 법에 대해서 알아보겠다.


UNIX Time Stamp
무엇에든 시작이 있게 마련고 시간도 시작이 있다. 이를테면 "서기", "단기" 와 같은 것들인데, 특정 인물이 태어난 시간을 기준으로 잡아서 시간을 계산하는 것등이다. 반면 Unix 시스템에서 기준으로 사용하는 때는 1970년 1월 1일(GMT)을 기준으로 한다. 이때를 기준으로 해서 지금까지 흐른시간을 초 단위로 측정한다. 유닉스에서 시간측정을 위해서 사용하는 가장 기본적인 함수인 time(2) 역시 시간을 초로 환산하여 돌려준다.



위의 코드를 컴파일 하여서 실행하면 현재시간을 초로 환산하여 돌려준다. 당신의 시스템 시간이 표준시와 비슷하다면 대충 10억 얼마 정도를 돌려줄것이다. time 함수의 원형은 다음과 같다.


비록 time(2)오래된 역사를 가진 표준적인 함수 이긴 하지만, 단지 시간을 초 단위에서 만 얻을 수 있다는 단점을 가진다. 이를 테면 정밀도가 떨어진다고 볼 수 있다. 그래서 최근에는 gettimeofday(2)를 time(2)대신 사용하고 있다.



time 함수가 매우 유용하긴 하지만, 지금 시간은 1970 년부터 "1012902900" 초가 흐른 시간입니다.. 라고 말한다면 아마도 지금의 시간을 아는데 상당한 어려움을 겪에 될것이다. 해서 인간이 보기 좋은 형식으로 만들어줘야 하는데, gmtime(3) 함수를 사용한다.


gmtime 의 아규먼트로 현재시간(초 단위의)을 넣어주면 tm 구조체의 값을 채워서 돌려준다. tm 구조체에는 다음과 같이 선언되어 있다.



예제 #
아래는 예제이다.





그런데 위의 코드를 컴파일해서 실행시켜보면 값이 현재 시간과 약간 다르게 나올것이다. 아마 지금 현재 시간 보다"9시간" 느리게 나올것이다. 이유는 gmtime 이 GMT 기준의 표준시를 돌려주기 때문이다. 우리나라는 GMT 를 기준으롸 봤을때 +9 의 시간을 가진다. 자신이 가지고 있는 시스템의 timezone 을 알아보기 위해서는 "echo $TZ" 을 이용하면 된다.


자기 지역의 시간을 알아보기 위해서는 localtime(3) 을 사용해야 한다. 사용방법 은 gmtime 과 완전히 똑같음으로 별도로 예를 들지는 않겠다. 그냥 위의 코드에서 gmtime 만 localtime 로 바꿔치기 하면 된다.


그 반대의 경우도 생각할수 있을것이다. 즉 "년,월,일,시,분,초" 가 주어졌을때 이를 Unix 시간으로 바꿔줄 경우이다. 실제로 이런 요구는 프로그래밍시 자주 발생한다. 이럴때는 mktime(3) 을 사용하면 된다.


이외에도 localtime(3), ctime(3), asctime(3)등의 관련함수들도 있으니 확인해 보기 바란다.


시스템 시간 설정
시간을 가져오는 것 만큼 컴퓨터의 시간을 설정하는 것 역시 중요하다. 컴퓨터의 시간 설정을 위해서 유닉스는 stime(2)와 settimeofday(2)를 제공한다. stime()은 settimeofday()의 오래된 버젼이다. 컴퓨터 시스템을 직접 제어할 수 있어야 하므로 반드시 root권한을 가지고 있어야만 실행 가능하다. 이들 함수에 대한 자세한 내용은 위의 함수 링크를 참고하기 바란다.


시간 응용
이들 유닉스 시간관련 함수들은 내부분 저수준이거나, 고수준의 함수라 할지라도 출력결과가 마땅치 않을 때가 있다. 예를 들어 "oo년 oo일 oo:oo:oo"이런 식으로 시간을 나타내려고 하면 상당히 귀찮은 코딩 과정을 거쳐야 한다. 코드모음의 time2str을 참고하기 바란다. 필자가 나름대로 작성한 시간출력 함수가 설명되어 있다.


이상으로 유닉스에서 시간을 다루는 함수와 사용방법에 대해 간단히 설명해 보았다. 많은 도움이 되었길 바라며, 다음번엔 좀더 나은 내용으로 찾아올걸 약속? 드립니다.  그럼 빠이~~


출처 : http://joinc.co.kr/modules.php?name=new ··· 3Dnested
"UNIX/Linux C" 카테고리의 다른 글
  • Linux 에서 malloc 으로 할당된 memory 크기를 알... (0)2007/06/09
  • 유저 정보 가져오기 (0)2007/05/14
  • Unix 시간 조작하기 (0)2007/05/14
  • file 정보(stat) 와 종류 알아내기 (0)2007/05/14
  • utmp 를 이용한 사용자 로그인정보 관리 (0)2007/05/14
2007/05/14 17:46 2007/05/14 17:46
Posted by webdizen
Tags Time Stamp, Unix 시간
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2949

Leave your greetings.

[로그인][오픈아이디란?]

Programming/UNIX/Linux C2007/05/14 17:40

file 정보(stat) 와 종류 알아내기

파일은 자신을 나타내기 위해서 수많은 정보를 가지고 있다. 파일이름, 파일크기, 파일의 권한, 파일의 종류(디바이스인지, 링크인지, 소켓인지, 일반파일인지)등 이다. 이번기사는 파일의 상태를 가져오는 방법에 대해서 논의 하고자 한다.


Unix 에서는 디렉토리, 장치, 소켓, FIFO 등 모든것이 파일로 존재한다. 그러므로 우선 우리는 작업을 하고자 하는 파일이 어던 종류의 파일인지를 먼저 판단할수 있어야 한다. Unix 상에서 "ls -al" 을 이용하면 아래와 같은 결과물을 볼수 있을것이다.
[yundream@localhost test]# ls -al
...
-rw-r--r-- 1 root root 249 9월 10 11:25 wc.1
drwxr-xr-x 2 root root 4096 12월 5 18:01 web_install
srwxr-xr-x 1 root root 0 1월 14 18:05 loging_socket
...



위의 결과에서 가장 앞부분 10자리가 그 파일의 특성과 권한을 나타내는데, 그중 제일 앞부분이 파일의 종류를 나타내고, 우리는 가장 앞부분의 한바이트의 문자를 이용해서 어떤 종류의 파일인지를 알아낼수 있다.
Unix 에서 자주 사용하는 파일의 종류를 아래 테이블에 정리해 두었으니 참고 바란다.
- 일반 파일
s 소켓파일
d 디렉토리
p FIFO
l 심볼링 링크된 파일
b 블럭 디바이스
c 캐릭터 디바이스



파일 정보 가져오기 #
파일의 정보는 stat(2)를 통해서 가지고 오며, 가지고온 정보는 struct stat 구조체에 저장된다. stat 구조체는 다음과 같은 내용을 가지고 있다.





위의 구조체의 정보는 inode 테이블을 참조해서 가지고오며, 우리가 필요로 하는(그 이상의) 상세한 정보를 가져온다는 것을 알수 있을것이다. inode 테이블에 대한 내용을 원한다면 /usr/src/linux/include/fs.h 의 sturct inode 를 참조하면 된다.


그럼 간단한 예제 프로그램을 만들어 보겠다. 이 프로그램은 아규먼트로 파일이름을 받아서 그 파일의 정보를 되돌려주는 프로그램으로, 파일의 권한, 퍼미션, 파일의 크기, 마지막에 수정된 날짜, 파일의 종류 등을 되돌려줄것이며, 존재하지 않는 파일의 경우 적절한 에러메시지를 출력하고 종료하게 될것이다.
이 예제의 이름은 file_info 로 하겠다.



완벽하진 않지만 그럭저럭 작동하는 프로그램이다.


프로그램은 아마 설명이 필요 없을 정도로 간단할것이다. 단지 마지막의 file_info.st_nlink (하드링크) 부분이 약간 헷갈릴 것이다. 하드링크란 보통 C 의 link(2) 함수와 쉘 코멘드인 link 에 -d 옵션을 줌으로서 만들어 진다. cp 를 이용한 복사와 하드링크를 혼동하지 말라, cp 를 이용한 복사는 자신만의 inode 를 가지는 전혀 새로운 파일을 만들지만, 하드링크의 경우 동일한 inode 를 가지고 파일이름을 만든다. 하드링크는 inode 를 가지고 파일이름을 만들므로 서로 다른 파일시스템 사이로 연결할수는 없다.
그 이유는 inode 가 해당 파일시스템에서만 유일하기 때문으로 여러개의 파일시스템을 사용할경우 다른 쪽 파일시스템에서 그 inode 가 유일함을 보장할수 없기 때문이다.


stat 는 너무 복잡합니다. 더 간단한 함수는 없을까요?
우리가 어떤 프로그램을 만들때, 우리는 종종 그 프로그램이 사용하는 파일(설정파일, 데이타 파일)들이 존재하는지 등을 검사할 필요가 있다. 이때에도 위의 stat 를 이용해서 해결 할수 있지만, 왠지 복잡하다는 생각이든다.
이때는 access(2) 를 사용하면된다.


access 는 검사할 파일에 대하여 4가지 항목, 읽을수 있는지(R_OK), 쓸수 있는지 (W_OK), 실행가능 한지 (X_OK), 존재하는 파일인지(F_OK) 등에 대한 정보를 돌려준다.




[yundream@localhost test]# gcc -o access access.
파일이 존재하지 않음 : : No such file or directory


파일 정보 변경하기
파일 정보 들을 보면 시간과 관련된 정보들이 있다. 파일에 대한 접근, 수정 시간이 이들인데, utime(2)을 이용해서 변경할 수 있다. 자세한 내용은 함수링크를 참조하기 바란다.


출처 : http://joinc.co.kr/modules.php?name=new ··· 3Dnested
"UNIX/Linux C" 카테고리의 다른 글
  • 유저 정보 가져오기 (0)2007/05/14
  • Unix 시간 조작하기 (0)2007/05/14
  • file 정보(stat) 와 종류 알아내기 (0)2007/05/14
  • utmp 를 이용한 사용자 로그인정보 관리 (0)2007/05/14
  • library 의 사용 (0)2007/05/14
2007/05/14 17:40 2007/05/14 17:40
Posted by webdizen
Tags file, stat, 파일 정보, 파일 종류
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2948

Leave your greetings.

[로그인][오픈아이디란?]

Programming/UNIX/Linux C2007/05/14 17:37

utmp 를 이용한 사용자 로그인정보 관리

utmp 는 커널에서 관리하는 로그인테이블 관리 시스템으로, 주로 현재 시스템을 누구 사용하는지, 언제 로그인을 했으며, 어떤 tty 를 사용하고 있는지 어떤 IP에서 접근했는지 등에 사용자 로그인에 대한 정보를 알려준다.
이러한 특성으로 시스템 사용자 모니터를 위한 도구로 많이 활용된다.
Unix 시스템에서는 utmp 데이내용을 확인하기 위해서 "w" 도구를 주로 사용 하게 되는데, 이번에는 이 "w" 를 직접 제작해 봄으로써, utmp의 개념과 활용방법등에 대해서 알아보도록 하겠다.

utmp에 대한 내용은 각 Unix 마다 다를수 있는데, 이 기사는 Linux readhat 7.x 을 기준으로 작성하도록 하겠다.

여러분이 시스템 관리자라면, 현재 시스템에 누가 작업을 하고 있는지, 어떤 작업을하는지 등에 대한 정보를 알아야될 때가 있을것이다.
이럴경우 시스템 관리자는 "w" 를 사용해서 현재 접근한 사용자의 정보를 알아보려고 시도할 것이다.

[root@localhost test]$ w
11:43am  up 270 days,  9:25,  3 users,  load average: 3.12, 3.22, 3.24
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU  WHAT
mercy4u  pts/1    s210-205-210-195 11:25am 41.00s  0.09s  0.06s  -bash
root     pts/2    s210-205-210-195 11:43am 18.00s  0.05s  0.05s  -bash
mercy4u  pts/4    s210-205-210-195 11:43am  0.00s  0.06s  0.02s  w


위의 결과에서 보듯이 "w" 명령을 이용해서 우리는 시스템에 접근한 사용자에 대한 다양한 정보를 얻을수 있을것이다.

예제 : myutmp.c


위 프로그램을 설명하기 전에 utmp(5) 구조체에 대해서 알아보도록 하자.
utmp 구조체는 아래와 같은 내용을 가진다.


ut_type 은 로그인 타입으로 미리 디파인된 값들을 가지고 있다. 이에 대한 내용은 utmp 의 맨 페이지를 참고하기 바란다.
우리가 보고자 하는 로그인 타입의 유저는, 일반 유저 레벨에서 접근한 경우에 한하므로 ut_type 이 "USER_PROCESS"인 utmp 정보만 가져오도록 하겠다.
shell 명령어로 사용하는 "w" 역시 ut_type 이 "USER_PROCESS" 인경우만 가져오는데, 이러한 ut_type 의 값은 USER_PROCESS 로 7로 디파인되어 있으며,

이 쏘쓰의 첫부분에서는 getutent(3) 함수를 이용하여 utmp파일의 가장최근의 파일 위치에서 부터 utmp 정보를 읽어들이게 는데, setutent(3)을 통하여 utmp파일 의 위치를 가장처음으로 돌렸음으로, utmp의 처음정보부터 읽어오게 될것이다. (굳이 stutent 를 사용할필요는 없지만 안전한 프로그래밍 습관을 위해서..) 이정보는 utmpfp 구조체에 저장이되고, 더이상 utmp 정보가 나오지 않을때까지 - NULL 을 만나기 전까지 - 반복실행되게 된다.
while 루프문 안에서는 getutent 를 통해서 가져온 정보를 이용해서 사용자가 보기편한 형태로 돌려주게 된다.
이 프로그램에서 사용자에게 돌려주는 정보는, 로그인시간, 사용터미널이름, 호스트 아이피, 그리고 현재 진행중인 process 의 이름을 돌려주게 된다.
대부분의 정보들은 단순히 utmp 구조체의 멤버들을 출력시킨것인데, 다만 현재 진행중인 process 가 무엇인지를 확인하기 위하여서, 별도의 함수를 작성하였다.

get_current_pid 는 현재 사용자가 실행중인 프로그램(프로세스)의 pid 를 가져오고, get_current_procname 는 get_current_pid 를 통해서 현재 실행중인 프로세스의 pid 를 넘겨받아서, pid 에 맞는 명령어의 이름을 돌려준다.
이에 대한 자세한 내용을 알고 싶다면 proc 파일시스템에 대한 이해가 필요한데, 상당히 방대한 내용이므로, 여기에서는 개념만 설명하고 넘어가도록 하겠다.

리눅스는 모든 프로세스의 정보를 /proc 밑에 자신의 pid 이름의 디렉토리에 저장을하게 된다. 즉 내가 현재 vi 를 실행시켰는데, 이 vi 에 대한 자세한 프로세스 정보를 알기를 원한다면 ps 등을 이용해서 vi 의 pid 를 알아내고, /proc/pid 으로 이동하면 프로세스의 상세한 정보를 기록한 파일들을 열람함으로써 프로세스의 정보를 알아낼수 있다.
/proc/pid 로 이동하면 이중 stat 라는 파일이 보일것이다. stat에는 프로세스의 pid, 명령어, ppid(부모프로세스의 pid) 등 프로세스의 전반적인 정보를 알아낼수 있는데, 8번째 필드값을 이용해서 가장최근에 실행된 프로세스의 pid 값을 알아낼수 있다.(정확하게는 gid 이지만 우리가 알아내고자 하는 pid 와 동일한 값을 보여주므로 넘어가도록 하겠다. 여기에 대한내용은 유닉스 프로세스에 대한 메뉴얼을 참조하기 바란다.)
그리고 /proc/pid 를 살표보면 exe 라는 심볼링 링크된 파일을 볼수 있을것인데, 이것은 현재 프로세스를 실행시킨 명령어에 대한 심볼릭링크이다.
결론적으로 우리는 /proc/pid/stat 를 이용해서 현재 실행중인 프로세스의 pid를 알수 있음으로 해당 프로세스 pid 의 proc 디렉토리로 이동해서 exe파일이 어떤 명령어에 심볼릭링크 되어있는지를 확인해서, 사용자가 지금 실행중인 프로그램이 어떤것인지를 알수 있게 된다. 심볼릭링크의 원본파일이름은 readlink(2) 를 이용해서 가져올수 있다.

다시한번 정리해 보자면 get_current_pid 를 이용 /proc/pid/stat 파일을 분석해서 현재 실행중인 프로세스의 pid(current_pid) 를 가져오고, get_current_procname 을 이용해서 /proc/current_pid/exe 의 심볼링크된 원본의 파일이름을 읽어 옴으로써 사용자의 현재진행중인 프로그램명을 얻어오게 되는것이다.

프로그램의 마지막에서는 getutline(3)함수를 이용해서 자기자신의 utmp 정보를 출력하도록 만들었다. getutline 는 tty 번호를 이용해서 utmp 정보를 가져온다. tty의 이름은 ttyname(3) 을 이용해서 얻어올수 있다.

아래는 프로그램을 실행시킨 결과이다
[root@localhost test]# ./myutmp
root         tty1     -            2002/09/16 22:12  
root         pts/0    -            2002/09/17 01:34  
root         pts/1    -            2002/09/16 22:13  
root         pts/2    -            2002/09/16 22:13  
root         pts/3    -            2002/09/20 11:53  
root         pts/4    -            2002/09/21 01:51  
root         pts/5    -            2002/09/21 02:03  
root         pts/6    -            2002/09/21 02:28  
root         pts/7    -            2002/09/21 03:07  
root         pts/8    -            2002/09/17 00:43  
root         pts/9    -            2002/09/18 00:13  
root         pts/10   -            2002/09/21 03:08  
root         pts/11   -            2002/09/21 03:11  
root         pts/12   -            2002/09/18 00:52  
root         pts/13   -            2002/09/22 11:14  
root         pts/14   -            2002/09/22 11:15  
root         pts/15   -            2002/09/22 11:16  
root         pts/16   -            2002/09/19 23:37  
root         pts/17   -            2002/09/22 11:32  

my utmp info is ---
root         pts/14   -            2002/09/22 11:15
"UNIX/Linux C" 카테고리의 다른 글
  • Unix 시간 조작하기 (0)2007/05/14
  • file 정보(stat) 와 종류 알아내기 (0)2007/05/14
  • utmp 를 이용한 사용자 로그인정보 관리 (0)2007/05/14
  • library 의 사용 (0)2007/05/14
  • PIPE 응용 (0)2007/05/14
2007/05/14 17:37 2007/05/14 17:37
Posted by webdizen
Tags UTMP, 사용자 로그인 정보
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2947

Leave your greetings.

[로그인][오픈아이디란?]

Programming/UNIX/Linux C2007/05/14 17:07

library 의 사용

이번기사는 유닉스 시스템 상에서 라이브러리를 만들고 사용하는 방법에 대한 내용을 담고 있습니다. 솔라리스와 리눅스 상에서 테스트되었으며, 정적라이브러리, 공유라이브러리, 동적 라이브러리를 제작하고 이를 프로그래밍에 응용하는 방법도 제시하고 있습니다.


1절. 소개
2절. Library 이야기
2.1절. 라이브러리란 무엇인가
2.2절. 라이브러리의 종류
2.2.1절. 왜 정적라이브러리의 사용을 지양하는가
3절. 라이브러리 만들고 사용하기
3.1절. 라이브러리화 할 코드
3.2절. 정적라이브러리 제작
3.3절. 공유라이브러리 제작 / 사용
3.4절. 동적라이브러리의 사용
3.5절. 동적라이브러리를 사용하여 프로그램의 확장성과 유연성을 높이기
3.5.1절. 동적라이브러리를 이용한 예제
4절. 결론

--------------------------------------------------------------------------------

1절. 소개
이 문서는 library 의 사용방법에 대한 내용을 담고 있다. 왜 라이브러리가 필요한지, 라이브러리는 어떤 종류가 있으며, 어떻게 작성할수 있는지, 그리고 어떻게 사용하는지에 대해서 얘기하도록 할것이다. 그리고 중간중간에 이해를 돕기 위한 실제 코딩역시 들어갈 것이다.

라이브러리에 대한 이러저러한 세부적인 내용까지 다루진 않을것이다. 좀더 이론적인 내용을 필요로 한다면 Program Library HOWTO 를 참고하기 바란다. 이 문서에서는 라이브러리를 만들고 활용하는 면에 중점을 둘것이다. 그러므로 위의 문서는 이문서를 읽기전에 대충이라도 한번 읽어보도록 한다.

정적 라이브러리와 공유라이브러리는 일반적인 내용임으로 간단한 설명과 일반적인 예제를 드는 정도로 넘어갈 것이다. 그러나 동적라이브러리에 대해서는 몇가지 다루어야할 이슈들이 있음으로 다른 것들에 비해서 좀더 비중있게 다루게 될것이다.


--------------------------------------------------------------------------------

2절. Library 이야기
2.1절. 라이브러리란 무엇인가
라이브러리란 특정한 코드(함수 혹은 클래스)를 포함하고 있는 컴파일된 파일이다. 이러한 라이브러리를 만드는 이유는 자주 사용되는 특정한 기능을 main 함수에서 분리시켜 놓음으로써, 프로그램을 유지, 디버깅을 쉽게하고 컴파일 시간을 좀더 빠르게 할수 있기 때문이다.

만약 라이브러리를 만들지 않고 모든 함수를 main 에 집어 넣는다면, 수정할때 마다 main 코드를 수정해야 하고 다시 컴파일 해야 할것이다. 당연히 수정하기도 어렵고 컴파일에도 많은 시간이 걸린다.

반면 라이브러리화 해두면 우리는 해당 라이브러리만 다시 컴파일 시켜서 main 함수와 링크 시켜주면 된다. 시간도 아낄뿐더러 수정하기도 매우 쉽다.


--------------------------------------------------------------------------------

2.2절. 라이브러리의 종류
라이브러리에도 그 쓰임새에 따라서 여러가지 종류가 있다(크게 3가지). 가장 흔하게 쓰일수 있는 "정적라이브러리"와 "공유라이브러리", "동적라이브러리" 가 있다.

이들 라이브러리가 서로 구분되어지는 특징은 적재 시간이 될것이다.


정적라이브러리
정적라이브러리는 object file(.o로 끝나는) 의 단순한 모음이다. 정적라이브러린느 보통 .a 의 확장자를 가진다. 간단히 사용할수 있다. 컴파일시 적재되므로 유연성이 떨어진다. 최근에는 정적라이브러리는 지양되고 있는 추세이다. 컴파일시 적재되므로 아무래도 바이너리크기가 약간 커지는 문제가 있을것이다.

공유라이브러리
공유라이브러리는 프로그램이 시작될때 적재된다. 만약 하나의 프로그램이 실행되어서 공유라이브러리를 사용했다면, 그뒤에 공유라이브러리를 사용하는 모든 프로그램은 자동적으로 만들어져 있는 공유라이브러리를 사용하게 된다. 그럼으로써 우리는 좀더 유연한 프로그램을 만들수 잇게 된다.

정적라이브러리와 달리 라이브러리가 컴파일시 적재되지 않으므로 프로그램의 사이즈 자체는 작아지지만 이론상으로 봤을때, 라이브러리를 적재하는 시간이 필요할것이므로 정적라이브러리를 사용한 프로그램보다는 1-5% 정도 느려질수 있다. 하지만 보통은 이러한 느림을 느낄수는 없을것이다.

동적라이브러리
공유라이브러리가 프로그램이 시작될때 적재되는 반면 이것은 프로그램시작중 특정한때에 적재되는 라이브러리이다. 플러그인 모듈등을 구현할때 적합하다. 설정파일등에 읽어들인 라이브러리를 등록시키고 원하는 라이브러리를 실행시키게 하는등의 매우 유연하게 작동하는 프로그램을 만들고자 할때 유용하다.




--------------------------------------------------------------------------------

2.2.1절. 왜 정적라이브러리의 사용을 지양하는가
예전에 libz 라는 라이브러리에 보안 문제가 생겨서 한창 시끄러웠던적이 있다. libz 라이브러리는 각종 서버프로그램에 매우 널리 사용되는 라이브러리였는데, 실제 문제가 되었던 이유는 많은 libz 를 사용하는 프로그램들이 "정적라이브러리" 형식으로 라이브러리를 사용했기 때문에, 버그픽스(bug fix)를 위해서는 문제가 되는 libz 를 사용하는 프로그램들을 다시 컴파일 시켜야 했기 때문이다. 한마디로 버그픽스 자체가 어려웠던게 큰 문제였었다. 도대체 이 프로그램들이 libz 를 사용하고 있는지 그렇지 않은지를 완전하게 알기도 힘들뿐더러, 언제 그많은 프로그램을 다시 컴파일 한단 말인가.

만약 libz 를 정적으로 사용하지 않고 "공유라이브러리" 형태로 사용한다면 bug fix 가 훨씬 쉬웠을것이다. 왜냐면 libz 공유라이브러리는 하나만 있을 것이므로 이것만 업그레이드 시켜주면 되기 때문이다.

아뭏든 이렇게 유연성이 지나치게 떨어진다는 측면이 정적라이브러리를 사용하지 않는 가장 큰 이유가 될것이다. 프로그램들의 덩치가 커지는 문제는 유연성 문제에 비하면 그리큰문제가 되지는 않을것이다.


--------------------------------------------------------------------------------

3절. 라이브러리 만들고 사용하기
이번장에서는 실제로 라이브러리를 만들고 사용하는 방법에 대해서 각 라이브러리 종류별로 알아볼 것이다.


--------------------------------------------------------------------------------

3.1절. 라이브러리화 할 코드
라이브러리의 이름은 libmysum 이 될것이며, 여기에는 2개의 함수가 들어갈 것이다. 하나는 덧셈을 할 함수로 "ysum" 또 하나는 뺄셈을 위한 함수로 "ydiff" 으로 할것이다. 이 라이브러리를 만들기 위해서 mysum.h 와 mysum.c 2개의 파일이 만들어질것이다.





--------------------------------------------------------------------------------

3.2절. 정적라이브러리 제작
정적라이브러리는 위에서 말했듯이 단순히 오브젝트(.o)들의 모임이다. 오브젝트를 만든다음에 ar 이라는 명령을 이용해서 라이브러리 아카이브를 만들면 된다.

[root@localhost test]# gcc -c mysum.c
[root@localhost test]# ar rc libmysum.a mysum.o
                       

아주아주 간단하다. 단지 ar 에 몇가지 옵션만을 이용해서 libmysum 이란 라이 브러리를 만들었다. 'r' 은 libmysum.a 라는 라이브러리 아카이브에 새로운 오브젝트를 추가할것이라는 옵션이다. 'c' 는 아카이브가 존재하지 않을경우 생성하라는 옵션이다.

이제 라이브러리가 실제로 사용가능한지 테스트해보도록 하자.

예제 : print_sum.c




위의 프로그램을 컴파일 하기 위해서는 라이브러리의 위치와 어떤 라이브러리를 사용할것인지를 알려줘야 한다. 라이브러리의 위치는 '-L' 옵션을 이용해서 알려줄수 있으며, '-l' 옵션을 이용해서 어떤 라이브러리를 사용할것인지를 알려줄수 있다. -l 뒤에 사용될 라이브러리 이름은 라이브러리의 이름에서 "lib"와 확장자 "a"를 제외한 나머지 이름이다. 즉 libmysum.a 를 사용할 것이라면 "-lmysum" 이 될것이다.
[root@localhost test]# gcc -o print_sum print_num.c -L./ -lmysum
                       

만약 우리가 사용할 라이브러리가 표준 라이브러리 디렉토리경로에 있다면 -L 을 사용하지 않아도 된다. 표준라이브러리 디렉토리 경로는 /etc/ld.so.conf 에 명시되어 있다.

정적라이브러리 상태로 컴파일한 프로그램의 경우 컴파일시에 라이브러리가 포함되므로 라이브러리를 함께 배포할 필요는 없다.


--------------------------------------------------------------------------------

3.3절. 공유라이브러리 제작 / 사용
print_sum.c 가 컴파일되기 위해서 사용할 라이브러리 형태가 정적라이브러리에서 공유라이브러리로 바뀌였다고 해서 print_sum.c 의 코드가 변경되는건 아니다. 컴파일 방법역시 동일하며 단지 라이브러리 제작방법에 있어서만 차이가 날뿐이다.

이제 위의 mysum.c 를 공유라이브러리 형태로 만들어보자. 공유라이브러리는 보통 .so 의 확장자를 가진다. [root@localhost test]# gcc -fPIC -c mysum.c
[root@localhost test]# gcc -shared -W1,-soname,libmysutff.so.1 -o libmysum.so.1.0.1 mysum.o
[root@localhost test]# cp libmysum.so.1.0.1 /usr/local/lib
[root@localhost test]# ln -s /usr/local/lib/libmysum.so.1.0.1 /usr/local/lib/libmysum.so
                       

우선 mysum.c 를 -fPIC 옵션을 주어서 오브젝트 파일을 만들고, 다시 gcc 를 이용해서 공유라이브러리를 제작한다. 만들어진 라이브러리를 적당한 위치로 옮기고 나서 ln 을 이용해서 컴파일러에서 인식할수 있는 이름으로 심볼릭 링크를 걸어준다.

컴파일 방법은 정적라이브러리를 이용한 코드의 컴파일 방법과 동일하다.
[root@coco test]# gcc -o print_sum print_sum.c -L/usr/local/lib -lmysum
                       



공유라이브러리는 실행시에 라이브러리를 적재함으로 프로그램을 배포할때는 공유라이브러리도 함께 배포되어야 한다. 그렇지 않을경우 다음과 같이 공유라이브러리를 찾을수 없다는 메시지를 출력하면서 프로그램 실행이 중단될 것이다.
[root@coco library]# ./print_sum
./print_sum: error while loading shared libraries: libmysub.so: cannot open shared object file: No such file or directory
                       

위와 같은 오류메시지를 발견했다면 libmysub.so 가 시스템에 존재하는지 확인해 보자. 만약 존재하는데도 위와 같은 오류가 발생한다면 이는 LD_LIBRARY_PATH 나 /etc/ld.so.conf 에 라이브러리 패스가 지정되어 있지 않을 경우이다. 이럴때는 LD_LIBRARY_PATH 환경변수에 libmysub.so 가 있는 디렉토리를 명시해주거나, /etc/ld.so.conf 에 디렉토리를 추가시켜주면 된다.

만약 libmysub.so 가 /usr/my/lib 에 복사되어 있고 환경변수를 통해서 라이브러리의 위치를 알려주고자 할때는 아래와 같이 하면된다.
[root@localhost test]# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/my/lib
                       

그렇지 않고 ld.so.conf 파일을 변경하길 원한다면(이럴경우 관리자 권한을 가지고 있어야 할것이다) ld.so.conf 에 라이브러리 디렉토리를 추가하고 ldconfig 를 한번 실행시켜주면 된다.
[root@localhost test]# cat /usr/my/lib >> /etc/ld.so.conf
[root@localhost test]# ldconfig
                       

ldconfig 를 실행시키게 되면 /etc/ld.so.conf 의 파일을 참조하여서 /etc/ld.so.cache 파일이 만들어지고, 프로그램은 ld.so.cache 의 디렉토리 경로에서 해당 라이브러리가 있는지 찾게 된다.


--------------------------------------------------------------------------------

3.4절. 동적라이브러리의 사용
동적라이브러리라고 해서 동적라이브러리를 만들기 위한 어떤 특별한 방법이 있는것은 아니다. 일반 공유라이브러리를 그대로 쓰며, 단지 실행시간에 동적라이브러리를 호출하기 위한 방법상의 차이만 존재할 뿐이다.

정적/공유 라이브러리가 라이브러리의 생성방법과 컴파일방법에 약간의 차이만 있고 코드는 동일하게 사용되었던것과는 달리 동적라이브러리는 코드자체에 차이가 있다. 그럴수밖에 없는게, 동적라이브러리는 프로그램이 샐행되는 중에 특정한 시점에서 부르고 싶을때 라이브러리를 적재해야 하므로, 라이브러리를 적재하고, 사용하고 해제(free) 하기 위한 코드를 생성해야 하기 때문이다.

linux 에서는 이러한 라이브러리를 호출하기 위한 아래와 같은 함수들을 제공한다. 아래의 함수들은 solaris 에서 동일하게 사용될수 있다.


dlopen 은 동적라이브러리를 적재하기 위해서 사용된다. 첫번째 아규먼트인 filename 은 /usr/my/lib/libmysum.so 와 같이 적재하기 원하는 라이브러리의 이름이다. 만약 적재시킬 라이브러리의 이름이 절대경로로 지정되어 있지 않을경우에는 LD_LIBRARY_PATH 에 등록된 디렉토리에서 찾고, 여기에서도 찾지 못할경우 /etc/ld.so.cache 에 등록된 디렉토리 리스트에서 찾게 된다. dlopen 이 성공적으로 호출되면 해당 라이브러리에 대한 handle 값을 넘겨 준다. flag 는 RTLD_LAZY와 RTLD_NOW 중 하나를 정의할수 있다. RTLD_LAZY는 라이브러리의 코드가 실행시간에 정의되지 않은 심볼을 해결하며, RTLD_NOW 는 dlopen 의 실행이 끝나기전에(return 전에) 라이브러리에 정의되지 않은 심볼을 해결한다.

dlerror 는 dl 관련함수들이 제대로 작동을 수행하지 않았을경우 에러메시지를 되돌려준다. dleooro(), dlsym(), dlclose(), dlopen()중 마지막 호출된 함수의 에러메시지를 되돌려준다.

dlsym 은 dlopen 을 통해서 열린라이브러리를 사용할수 있도록 심볼값을 찾아준다. 심볼이라고 하면 좀 애매한데, 심볼값은 즉 열린라이브러리에서 여러분이 실제로 호출할 함수의이름이라고 생각하면 된다. handle 는 dlopen 에 의해서 반환된 값이다. symbol 은 열린라이브러리에서 여러분이 실제로 부르게될 함수의 이름이다. dlsym 의 리턴값은 dlopen 으로 열린 라이브러리의 호출함수를 가르키게 된다. 리턴값을 보면 void * 형으로 되어 있는데, void 형을 사용하지 말고 호출함수가 리턴하는 형을 직접명시하도록 하자. 이렇게 함으로써 나중에 프로그램을 유지보수가 좀더 수월해진다.


--------------------------------------------------------------------------------

3.5절. 동적라이브러리를 사용하여 프로그램의 확장성과 유연성을 높이기
동적라이브러리는 실행시간에 필요한 라이브러리를 호출할수 있음으로 조금만(사실은 아주 많이겠지만 T.T) 신경쓴다면 매우 확장성높고 유연한 프로그램을 만들수 있다.

동적라이브러리의 가장 대표적인 예가 아마도 Plug-in 이 아닐까 싶다. 만약에 모질라 브라우저가 plug-in 을 지원하지 않는 다면 우리는 새로운 기능들 이 추가될때 마다 브라우저를 다시 코딩하고 컴파일하는 수고를 해야할것이다. 그러나 동적라이브러리를 사용하면 브라우저를 다시 코딩하고 컴파일 할필요 없이, 해당 기능을 지원하는 라이브러리 파일만 받아서 특정 디렉토리에 설치하기만 하면 될것이다. 물론 동적라이브러리를 사용하기만 한다고 해서 이러한 기능이 바로 구현되는 건 아니다. Plug-in 의 효율적인 구성을 위한 표준화된 API를 제공하고 여기에 맞게 Plug-in 용 라이브러리를 제작해야만 할것이다.

우리가 지금까지 얘로든 프로그램을 보면 현재 '+', '-' 연산을 지원하고 있는데, 만약 'x', '/' 연산을 지원하는 라이브러리가 만들어졌다면, 우리는 프로그램의 코딩을 다시해야만 할것이다. 이번에는 동적라이브러리를 이용해서 plug-in 방식의 확장이 가능하도록 프로그램을 다시 만들어 보도록 할것이다.


--------------------------------------------------------------------------------

3.5.1절. 동적라이브러리를 이용한 예제
동적라이브러리를 이용해서 main 프로그램의 재코딩 없이 추가되는 새로운 기능을 추가시키기 위해서는 통일된 인터페이스를 지니는 특정한 형식을 가지도록 라이브러리가 작성되어야 하며, 설정파일을 통하여서 어떤 라이브러리가 불리어져야 하는지에 대한 정보를 읽어들일수 있어야 한다. 그래서 어떤 기능을 추가시키고자 한다면 특정 형식에 맞도록 라이브러리를 제작하고, 설정파일을 변경하는 정도로 만들어진 새로운 라이브러리의 기능을 이용할수 있어야 한다.

설정파일은 다음과 같은 형식으로 만들어진다. 설정파일의 이름은 plugin.cfg 라고 정했다. +,ysum,libmysum.so
-,ydiff,libmysum.so
                               

'-' 연산에대해서는 libmysum.so 라이브러리를 호출하며, ydiff 함수를 사용한다. '=' 연산에 대해서는 libmysum.so 라이브러리를 호출하고 ysum 함수를 사용한다는 뜻이다. 설정파일의 이름은 plugin.cfg 로 하기로 하겠다.

다음은 동적라이브러리로 만들어진 print_sum 의 새로운 버젼이다.

예제 : print_sum_dl.c

                               

위의 예제 프로그램은 다음과 같이 컴파일되어야 한다. 라이브러리 파일의 위치는 /usr/my/lib 아래에 있는것으로 하며, 라이브러리 찾기 경로에 등록되어 있다고 가정하겠다.
[root@localhost test]# gcc -o print_sum_dl print_sum_dl.c -ldl
                               

이 프로그램을 실행하면 사용자의 입력을 기다리는 "> "가 뜨게 되고, 여기에 계산하기 원하는 값을 입력하면 된다. 현재는 '+'와 '-' 연산만을 지원하며, 연산자와 피연산자들 간에 간격이 없어야 한다. 다음은 실행결과 화면이다.  
[root@localhost test]# ./print_sum_dl
> 99+99
my operator is      : +
my call function is : ysum
99 + 99 = 198
[root@localhost test]#
                               

사용자가 프로그램을 실행하면 프로그램은 사용자의 입력을 받아들이고 sscanf 를 이용해서 연산자와 피연산자를 구분하게 된다. 그리고 피연산자를 값으로 하여, 설정파일에 설정된 라이브러리를 불러들이고(dlopen) 해당 함수를 가져와서(dlsym) 실행시키게 된다.

자 이렇게 해서 우리는 '+', '-' 연산이 가능한 프로그램을 하나 만들게 되었다. 그런데 A 라는 개발자가 '*','/' 연산도 있으면 좋겠다고 생각해서 아래와 같은 코드를 가지는 '*', '/' 연산을 위한 라이브러리를 제작하였다.

예제 : mymulti.h

                               

예제 : mymulti.c



A 라는 개발자는 이것을 다음과 같이 공유라이브러리 형태로 만들어서 간단한 라이브러리의 설명과 함께 email 로 전송했다.
[root@localhost test]# gcc -c -fPIC mymulti.c
[root@localhost test]# gcc -shared -W1,-soname,libmymulti.so.1 -o libmymulti.so.1.0.1 mymulti.o
                               



라이브러리를 받았으므로 새로운 라이브러리가 제대로 작동을 하는지 확인을 해보도록 하자. 우선 libmymulti.so.1.0.1 을 /usr/my/lib 로 복사하도록 하자. 그다음 설정파일에 다음과 같은 내용을 가지도록 변경 시키도록 하자.  
+,ysum,libmystuff.so
-,ydiff,libmystuff.so
*,ymulti,libmymulti.so.1.0.1
/,ydiv,libmymulti.so.1.0.1
                               

이제 print_sum_dl 을 실행시켜보자.
[root@localhost test]# ./print_sum_dl
> 10*10
my operator is      : *
my call function is : ymulti
10 * 10 = 100

[root@localhost test]# ./print_sum_dl
> 10/10
my operator is      : /
my call function is : ydiv
10 / 10 = 1
                               

print_sum_dl.c 의 원본파일의 아무런 수정없이 단지 설정파일만 변경시켜 줌으로써 기존의 print_sum_dl 에 "곱하기"와 "나누기"의 새로운 기능이 추가 되었다.

위에서도 말했듯이 이러한 Plug-in 비슷한 기능을 구현하기 위해서는 통일된 함수 API가 제공될수 있어야 한다.


--------------------------------------------------------------------------------

4절. 결론
여기에 있는 내용중 동적라이브러리에 대한 내용은 솔라리스와 리눅스에서만 동일하게 사용할수 있다. Hp-Ux 혹은 윈도우에서는 사용가능하지 않는 방법이다. 이에 대한 몇가지 해법이 존재하는데, 이 내용은 나중에 시간이 되면 다루도록 하겠다. 어쨋든 솔라리스와 리눅스 상에서 코딩되고 윈도우 혹은 다른 유닉스로 포팅될 프로그램이 아니라면 위의 방법을 사용하는데 있어서 문제가 없을것이다.




출처 : http://joinc.co.kr/modules.php?name=new ··· 3Dnested
"UNIX/Linux C" 카테고리의 다른 글
  • file 정보(stat) 와 종류 알아내기 (0)2007/05/14
  • utmp 를 이용한 사용자 로그인정보 관리 (0)2007/05/14
  • library 의 사용 (0)2007/05/14
  • PIPE 응용 (0)2007/05/14
  • 데이타와 포인터 (0)2007/05/14
2007/05/14 17:07 2007/05/14 17:07
Posted by webdizen
Tags Library, Linux, Solaris, Unix
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2943

Leave your greetings.

[로그인][오픈아이디란?]

Programming/UNIX/Linux C2007/05/14 11:10

PIPE 응용

이번기사는 pipe 를 이용한 부모프로세스와 자식프로세스간의 통신 방법에 대한 내용을 담고 있다. 이밖에도 fork와 exec 를 통한 프로세스 생성과 생성된 프로세스와의 통신, pipe 로 생성된 파일 지시자의 구성등에 대한 내용을 담고 있다.


1절. 소개
2절. 만들고자 하는것
2.1절. pipe(2) 함수에 대해서
2.2절. 작동 process
2.3절. 예제 코드

--------------------------------------------------------------------------------

1절. 소개
우리는 이미 PIPE 를 통한 IPC 의 구현에 대해서 몇번에 걸쳐 다루었었다. 그런데 필자가 실수로 보모자식간의 통신을 위한 pipe 의 사용에 대한 내용은 빼먹었었다.

이번에는 이 pipe 의 사용법에 대한 응용을 예를들어 설명할 생각이다.


--------------------------------------------------------------------------------

2절. 만들고자 하는것
우리는 몇개의 실행프로그램으로 이루어진 시스템을 설치하 일종의 자동실행 프로그램을 만들것이다. 이 시스템은 3개의 프로그램으로 이루어져 있다. 이 프로그램들은 일정한 순서대로 실행이 되어야 하며, 하나의 프로그램이 실행되기전에 바로앞의 프로그램이 제대로 실행되었는지 확인후 제대로 실행되었다면 실행되어야 한다.

이것을 구현하기 위해서 우리는 main 실행파일을 하나 만든후, main 실행파일에서 fork&exec 방식을 이용해서 순서대로 나머지 3개의 sub 프로그램들을 실행하게 될것이다.

sub 프로그램의 실행순서와 실행할 파일이름을 지정해주기 위한 설정파일을 가지게 되며, main 프로그램은 설정파일을 읽어들여서 sub 프로그램들을 실행시키게 될것이다.

그런데 이들 프로그램은 앞의 프로그램이 제대로 실행되었는지 확인후 차례대로 실행시켜주어야 할것이다. 그러기 위해서 main 프로그램은 하나의 sub 프로그램을 실행시킨후 실행시킨 sub 프로그램과 pipe 를 이용해서 통신을 하게 될것이다. sub 프로그램은 자신의 pid 를 main 프로그램에 넘겨주게 되고, main 은 이 pid 를 넘겨 받으면 sub 프로그램이 제대로 실행되었다고 판단하고 다음 sub 프로그램을 실행하게 된다.


--------------------------------------------------------------------------------

2.1절. pipe(2) 함수에 대해서
pipe 함수를 호출하게 되면, 읽기전용과 쓰기전용 두개의 파이프를 생성할수 있다.




아규먼트를 보면 int형 배열임을 알수 있다. 이는 pipe 호출로 만들어 지는 읽기전용과 쓰기전용의 파이프 연결자를 넘겨 받기 위함이다.

PIPE 의 전반적인 특성은 이 사이트의 다른 문서들을 참고하기 바란다.


--------------------------------------------------------------------------------

2.2절. 작동 process
이 프로그램은 아마 다음과 같은 방식으로 작동할것이다.
메인 프로그램 시작





exec 함수를 실행시킬경우 열린파일 지시자가 상속된다는 점을 알고 있을것이다. dup2 를 이용해서 기존에 만들어진 쓰기전용 파이프 번호를 표준출력(1) 으로 복사하고 나서 execl 함수를 이용해서 프로그램을 실행시키면, 이 프로그램은 표준출력을 통해서 부모의 파이프 와 통신할수 있게 된다.


--------------------------------------------------------------------------------

2.3절. 예제 코드
예제 코드는 2개의 쏘쓰로 이루어질 것이다. 하나는 main 프로그램으로 프로그램의 이름은 pm.cc 이고, 다른 하나는 pm 프로그램에서 frok&exec 로 실행시킬 sub 프로그램으로 이름은 proc.c 이다.

예제 : proc.c



프로그램이 하는일은 간단하다. 자신의 pid 번호를 write 를 이용해서 표준출력으로 보내는 것이다. 이 표준출력은 pm 에서 생성시킨 pipe 와 연결되어 있음으로 pid 번호는 pm 프로그램으로 전달될 것이다.

이 파일을 컴파일 한다음에 proc1, proc2, proc3 라는 이름으로 복사해서 적당한 디렉토리에 옮겨 놓도록 하자. 필자의 경우 /usr/yundream/bin 에 옮겨 놓았다.

예제 : main.c



pm.cc 는 main 프로그램이다. 설정파일 config.cfg 로 부터 실행시켜야될 파일이름을 읽어들어와서 vector 로 목록을 만든다음에 fork&exec 로 미리 만들어 놓은 proc1, proc2, proc3 를 실행 시키다. config.cfg 파일은 다음과 같이 구성될것이다.
/usr/yundream/bin/proc1
/usr/yundream/bin/proc2
/usr/yundream/bin/proc3




위의 프로그램을 실행시키면 아래와 같은 결과를 보여줄것이다.
[root@localhost test]# ./pm
exec 3642
exec 3643
exec 3644


실행을 했다면 실제로 sub 프로그램들의 표준출력이 어떻게 처리되고 있는지 알아보도록 하자. /proc/3642/fd 로 이동해서 ls 값을 보도록 하자.
[root@localhost fd]# ls -al
합계 0
dr-x------ 2 root root 0 7월 28 14:09 .
dr-xr-xr-x 3 root root 0 7월 28 14:08 ..
lrwx------ 1 root root 64 7월 28 14:09 0 -> /dev/pts/5
l-wx------ 1 root root 64 7월 28 14:09 1 -> pipe:[28721]
lrwx------ 1 root root 64 7월 28 14:09 2 -> /dev/pts/5


위의 결과를 보면 표준출력 1 이 /dev/pts/5 로 연결되어 있지 않고, pipe 로 연결되어 있음을 알수 있다. 또한 이 파이는 쓰기 전용임을 알수 있다.

이제 pm 의 파일지시자 구조를 알아보겠다. pm 의 pid 번호를 알아내고 /proc/pmpid/fd 로 이동해서 ls 값을 알아보도록 하자.
[root@localhost fd]# ls -al
합계 0
dr-x------ 2 root root 0 7월 28 14:11 .
dr-xr-xr-x 3 root root 0 7월 28 14:11 ..
lrwx------ 1 root root 64 7월 28 14:11 0 -> /dev/pts/5
lrwx------ 1 root root 64 7월 28 14:11 1 -> /dev/pts/5
lrwx------ 1 root root 64 7월 28 14:11 2 -> /dev/pts/5
lr-x------ 1 root root 64 7월 28 14:11 3 -> pipe:[28721]
l-wx------ 1 root root 64 7월 28 14:11 4 -> pipe:[28721]


2개의 파이프가 생성되었음을 알수 있다. 하나는 읽기전용 파이프이고, 또다른 하나는 쓰기전용 파이프이다. 그리고 파이프 번호를 보면 28721 로 각 sub 프로그램들의 파이프번호와 일치함을 알수 있다. 이를 통해서 각 sub 프로그램들의 표준출력 1 은 pm 의 읽기전용 파이프의 연결번호인 3 번과 연결되어 있음을 알수 있다.

출처 :http://joinc.co.kr/modules.php?name=new ··· 3Dnested
"UNIX/Linux C" 카테고리의 다른 글
  • utmp 를 이용한 사용자 로그인정보 관리 (0)2007/05/14
  • library 의 사용 (0)2007/05/14
  • PIPE 응용 (0)2007/05/14
  • 데이타와 포인터 (0)2007/05/14
  • fcntl 함수 사용하기 (0)2007/05/14
2007/05/14 11:10 2007/05/14 11:10
Posted by webdizen
Tags pipe
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2941

Leave your greetings.

[로그인][오픈아이디란?]

Programming/UNIX/Linux C2007/05/14 10:56

데이타와 포인터

많은 프로그래머들이 C 의 포인터에 대해서 (심지어 꽤 오랫동안 C 언어를 다룬 프로그래머조차도) 일말의 공포감을 가지고 있다. 이번 기사에서는 실제로 데이타가 어떻게 저장되는지에 대한 여러가지 예제를 통해서 포인터에 대한 내용을 알아볼 것이다. 더불어 형변환, 메모리 카피 등에 대한 몇가지 지식도 얻을수 있을것이다.


1절. 소개
2절. 데이타와 형(Type)
2.1절. 데이타는 bit 의 연속된 나열이다.
2.1.1절. 구조체는 어떻게 저장되는가 ?
2.1.2절. void 형에 대해서
3절. 포인터(Pointer)
3.1절. 포인터는 주소를 가르킨다
3.2절. 배열과 포인터는 동일하다

--------------------------------------------------------------------------------

1절. 소개
C 언어를 가장 처음 접하면서 겪는 어려움은 엄격하게 구분되어 있는 자료형과 이들 자료들을 다루기 위해서 사용하는 포인터라는 개념 그리고 이와 더불어서 사용되는 형변환 이다.

이는 최근의 여러가지 고수준 언어들이 포인터의 사용을 지양하고, 자료형에 그리 엄격하지 않는것과 구분된다. 이들을 유저(프로그래머)가 직접 다루게 되면 아무래도 프로그램 오류를 유발시킬 가능성이 많아 짐으로 컴파일러에서 이러한 것들을 처리하도록 하고 있다.

C 언어에서 이러한 것들의 처리는 프로그래머의 몫이다. 그런 이유로 C 언어를 중급언어라고 한다. 프로그래머에게 위의 문제들의 해결을 맡김으로써 분명 프로그래머에게 많은 부담이 주어지는 건 사실이다. 그러나 또한 이것들을 제대로만 사용할줄 알게 된다면, 다른 언어에서는 곤란한 저수준에서의 프로그래밍이 가능하게 된다. 이런 이유로 C 언어가 시스템/네트웍 프로그래밍에서 다른 언어들 보다 우위에 있게 되는 것이다.

이 문서에서는 이러한 자료들이 어떻게 저장되는지, 형변환이 어떻게 일어나는지 또한 자료에 접근하기 위해서 사용되는 포인터란 어떤것인지에 대해서 알아보도록 할것이다.

이 문서는 C 입문자 에게 포인터 개념을 가르키기 위한 목적으로 작성된 문서는 아니다. 어느정도 C를 아는 사용자에 한해서 여전히 포인터에 대해서 헷갈리는 C 언어 초/중급 사용자를 위한 내용을 담고 있다.


--------------------------------------------------------------------------------

2절. 데이타와 형(Type)
결국 프로그램이 하는 일은 데이타를 저장하고, 읽어들이고 일어들인 데이타를 처리해서 고객이 원하는 정보로 변환해서 보여주는 것이다. 혹은 시스템 프로그램이라면 데이타를 이용해서 시스템을 제어하는 일을 할것이다.

그러므로 데이타가 어떤 방식으로 저장되고 읽어들일수 있는지 이해하는 것은 대단히 중요한 일이다.


--------------------------------------------------------------------------------

2.1절. 데이타는 bit 의 연속된 나열이다.
컴퓨터 입장에서는 프로그래머가 흔히 데이타의 형 구분을 위해서 사용하는 int, char, long int 이런것에 대해서 전혀 상관하지 않는다. 컴퓨터 입장에서는 데이타는 단지 bit 의 나열일 뿐이다. 컴퓨터는 이 bit 를 8bit(1byte) 단위로 저장을 하게 된다.

프로그래머가 흔히 사용하는 데이타 형이라는 것은 프로그래머가 데이타의 조작을 편리하게끔 만들어 놓은 것에 불과 하다. 즉 int 형이라면 연속된 4 byte(32bit) 정보를 memory 혹은 디스크에 저장하고, char 형이라면 1 byte(8bit) 단위로 정보를 저장하고/읽어들일것을 약속한 것일 뿐이다. 그러나 컴퓨터입장에서는 그냥 byte 의 연속된 정보일 뿐이다.

다음의 예제를 실행시켜 보자

예제 : mem.c


다음은 필자의 Linux 박스(kernel 2.4.x) 에서의 실행결과 이다. 실행 값은 다를수 있다. (아시겠지만 '&' 는 주소 연산자이다. 주소값을 되돌려준다) a : int, c : char, d : short int

size int 4
size char 1
size short 2

a[0] : bffff7b0
a[1] : bffff7b4
a[2] : bffff7b8
a[3] : bffff7bc

c[0] : bffff7a0
c[1] : bffff7a1

d[0] : bffff79c
d[1] : bffff79e


sizeof() 함수는 자료형의 크기를 알아내기 위해서 사용하는 함수이다. sizeof 를 사용할경우 해당 자료형의 크기를 byte 단위로 되돌려준다.

printf 에서 형식화된 입출력을 위해 사용된 %x 는 16 진수 형태로 보여주기 위한 인자이다. 변수 a 는 int 형이며, int 형은 아시다시피 32bit 크기를 가진다. 이것을 sizeof 해보면 4(32/8) 를 되돌려줄것이다. 주소 값의 크기를 보라 a[0] 에서 부터 a[3] 까지 주소값이 4 만큼 증가하고 있음을 알수 있다. 이 주소값은 byte 단위로 증가를 함으로 a[0] 에서 부터 a[3] 까지 메모리 상에 연속되게 위치하고 있음을 알수 있을것이다. 단위 : byte

0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| a[0] | a[1] | a[2] | a[3] |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
bffff7b0 7b4 7b8 7bc
a[0] = 324
a[1] = 2000
a[2] = 3
a[3] = 4


변수 c 의 경우에는 char 형인데, char 형은 1byte 크기를 가진다. 실제 주소의 증가분을 봐도 1byte 크기 단위로 증가하고 있음을 알수 있다. 2 바이트 크기를 가지는 short int 도 마찬가지로 2byte 크기 단위로 주소 값이 증가하고 있음을 알수 있다.


--------------------------------------------------------------------------------

2.1.1절. 구조체는 어떻게 저장되는가 ?
위의 char, int, short 와 같은 자료형외에 struct 와 같은 프로그래머가 정의해서 사용하는 자료형도 있다. 구조체는 여러개의 자료형을 묶어 놓은 형식을 취하고 있는데, struct 자료형 역시 컴퓨터의 입장에서 단순히 연속된 byte 의 모음이다. 다음 프로그램을 컴파일후 실행시켜 보자.



실행하면 다음과 같은 결과를 보여줄 것이다. 104
mydata.age : bffff740
mydata.weight : bffff744
mydata.name : bffff748
mydata.juso : bffff758


mydata 구조체의 sizof 값은 104 가 나왔다. 계산을 해보면 4+4+16+80 = 104 로 정확하게 크기가 계산되었음을 알수 있다. 그리고 printf 의 주소값을 보면 각각의 자료형에 알맞도록 메모리 크기가 할당되어 있으며, 메모리 상에서 연속되게 할당되어 있음을 알수 있을 것이다. age, weight 는 4byte 씩, name 16 byte, juso 는 80byte 가 할당되어 있음을 알수 있을 것이다.


--------------------------------------------------------------------------------

2.1.2절. void 형에 대해서
여러분은 void 타입에 대해서 들어본적이 있을것이다. 이거 상당히 혼동될수 있는데, void 타입이란 이를테면 데이타형 을 컴퓨터 입장에서 해석하는 것이다. 프로그래머의 경우 프로그래밍 작업을 수월하게 하기 위해서 다양한 데이타 타입을 이용하지만 말했듯이 컴퓨터에게 있어서 데이타 타입은 사실 필요가 없다. 컴퓨터 입장에서는 단지 연속된 8bit(byte) 데이타의 나열일 뿐이다. 달리 말하자면 컴퓨터 입장에서는 모든 데이타는 void 형이다. 그러므로 void 타입의 경우 모든 데이타형을 저장할수 있게 된다.

보통의 경우 int, char 혹은 struct 와 같은 데이타 타입을 이용해서 작업하는 것은 매우 편리하긴 하지만, 때때로 데이타 타입을 분리해서 작업하면 오히려 불편한 경우가 생길수가 있다. 대표적인 예로 memcpy 를 예로 들어보자. memcpy 는 다음과 같이 선언되어 있는데,




만약 주어지는 인자가 void 형이 아니라고 가정해 보자. 그렇다면 int 형 복사, char 형복사, struct 형 복사를 하기 위한 별도의 memcpy 함수를 만들어야만 할것이다.(이를테면 imemcpy, cmemcpy 등) 이거 대단히 귀찮은 작업이다. 그나마 우리가 크기를 알고 있는 int, char 같은 경우라면 괜찮겠지만, 사용자 정의형 데이타타입을 위한 memcpy 함수를 만드는건 상당히 까다로운 작업이 될것이다.

그렇다면 가장 간단한 방법은 데이타 타입에 상관하지 않고 컴퓨터 입장에서 데이타를 바라 보는 것이다. 바로 컴퓨터 입장에서의 데이타 타입이 void 형이다. 컴퓨터입장에서는 데이타 타입은 void 형 오직 하나이므로, 프로그래머가 정의한 데이타 타입을 공통 데이타 타입인 void 형으로 형변환(cast) 시켜준다면, 타입에 관계없이 작업할수 있게 될것이다.

다음은 void 형의 이해를 돕기 위한 간단한 예제이다.

예제 memcpy2.c


memcpy 한후 f_data 의 메모리 구성을 보면 아래와 같을 것이다.
we = weight
단위 : byte

4 4 16 80 16
+----+----+-------------+----------------------+-----------+
|age |we |name | juso | NULL |
+----+----+-------------+----------------------+-----------+

| |
+-------- 120 byte -------+


참고로 char 형일 경우 void 형으로 형변환(cast) 할 필요가 없다. 왜냐하면 char 는 1byte 단위로 컴퓨터의 데이타 저장단위 1byte 와 동일하기 때문이다.

어쨋든 void 타입을 이용해서 전혀 다른 데이타 형으로 데이타 복사를 하긴 했는데, 그렇다면 f_data 에서 데이타를 가져오는건 어떻게 해야 할까. 가장 간단한 방법은 mydata 형의 변수를 하나더 만든다음에 여기에 memcpy 시키는 방법이 있을수 있을것이다.




이렇게 하면 f_data 에서 sizeof(mydata) 크기인 104 만큼이 data2 로 복사될 것이다.

만약 weight 의 정보만을 가져오고 싶다면, 굳이 struct 전체를 복사할 필요 없이 다음과 같은 방법으로 weight 정보를 가져올수 있을것이다.




우리가 가져오고자 하는 값은 f_data 에서 4 바이트만큼 뒤로 이동한 데이타 이다. (age 가 int 형으로 4byte 의 크기를 가짐으로) 그럼으로 f_data 에 +4 만큼 해주면 weight 가 저장된 곳의 주소를 가르키게 될것이다. 우리가 가져오고 싶어하는 weight 데이타는 4 바이트 크기의 int 형 데이타 임으로 sizeof(int) 의 크기만큼을 my_weight 가 가르키는 주소로 복사하면 될것이다.

name, juso 값 역시 위와 같은 방법으로 가져올수 있다.

그리고 메모리는 연속되게 할당된다는 점에 착안한다면 다음과 같은 코딩도 가능할것이다.




출력을 해보면 알겠지만 구조체가 그대로 c[2] 배열 변수에 복사되었음을 알수 있다. 어차피 c[2] 도 8byte 의 크기를 가지고, mydata 도 8byte 를 가지고 있음으로 void 형으로 형변환시켜주고 복사한다면, 동일한 메모리블럭 구조를 가질수 있기 때문이다.


--------------------------------------------------------------------------------

3절. 포인터(Pointer)
"포인터란 데이타가 저장된 주소를 가르킨다." 대부분의 C 언어 입문서에 보면 보통 이렇게 포인터에 대한 설명을 시작한다. 분명 틀린말은 아니며, 위의 명제대로 포인터의 개념은 매우 간단하다고 할수 있다. 그렇지만 C 언어 초보자이건 중급사용자이건 간에 포인터라는 것이 개념만큼 만만치 않다라는데 공감할것이다.

심지어 C 언어의 활용을 가로막는 가장 큰 적 하면 일순위로 꼽는게 "포인터" 일 정도이다.

이번장에 대해서는 Pointer 에 대한 기본적인 개념에 대해서 알아보도록 하겠다.


--------------------------------------------------------------------------------

3.1절. 포인터는 주소를 가르킨다
포인터는 그 데이타가 저장된 주소를 가르킨다. 다시 mem.c 의 결과를 보도록 하자. 단위 : byte

0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| a[0] | a[1] | a[2] | a[3] |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
bffff7b0 7b4 7b8 7bc

a[0] = 324
a[1] = 2000
a[2] = 3
a[3] = 4


C 에서는 포인터 연산을 위한 연산자를 별도로 제공하는데 '*' 이다. 이것을 통해서 데이타를 포인터를 사용해서 접근할수 있다. 예를 들어 *a 를 사용한다면 이것의 뜻은 "변수 a 를 위해 할당된 메모리의 주소를 가르키는" 의 뜻이 된다. mem.c 에 다음의 코드를 추가 시켜 보자 printf("%d
", *a);


a 가 저장된 주소의 값은 bffff7b0 이다(*a 가 가르키는 주소의 값). %d 를 사용하여서 출력을 하라고 했음으로 bffff7b0 에서 부터 bffff7b3 까지의 값을 int 형으로 변환하여 출력을 하게 될것이다. 그러므로 위의 코드는 "324" 을 출력 시킨다.

그렇다면 한가지 궁금한게 있다. 만약 printf("%c ", *a) 로 출력하라고 하면 어떻게 될까? 에러가 발생하게 될까 ? 눈치 챘겠지만 컴파일에러도 발생하지 않고, 워닝도 발생하지 않을 뿐더러, 실행시 에도 에러가 발생하지 않는다. 위의 경우를 해석해 보자면, a 가 저장된 주소로 부터 1byte 만큼 (%c 는 1byte 캐릭터를 출력하기 위해서 사용함으로) 데이타를 읽어들여서 화면에 출력하게 된다. *a 가 저장된 메모리의 주소에 저장된 데이타를 자세히 살펴보면 bffff7b0 b1 b2 b3
01000100 00000001 00000000 00000000


이 될것이다. (계산기로 계산해 보면 324 는 101000100 이다. 그런데, Linux 는 리틀엔디안 저장방식 을 따름으로 낮은주소의 데이타가 가장 먼저 저장므로 위와 같은 방식으로 메모리에 저장된다. 엔디안에 대한 내용은 Endian에 대해서를 참조하기 바란다) 그러므로 printf("%c ", *a) 가 출력하게 되는 값은 bffff7b0 에서 부터 1byte 만큼 저장되어 있는 값인 01000100 이다. 이것을 10 진수로 변환 시켜보면 68 이고 68 은 ASCII 코드표에서 D 를 가르키므로 결국 'D' 를 출력하게 된다.

아마 포인터에 대한 대략적인 이해를 했을것이다. 그렇다면 a[0] 을 가져 오는건 알겠는데 a[1] 의 값은 어떻게 하면 가져올수 있을지 알아보자. 간단히 생각해서 최초 *a 에서 4 만큼 포인터의 위치를 이동시키면 될것이다. 실제로 이러한 방식으로 포인터 연산을 하게 된다.

그냥 *(a+1) 해주면 된다. +1 이면 혹시 1byte 만큼만 증가하지 않을지.. 걱정이 될수도 있겠지만 이럴경우 컴파일러가 변수 'a' 의 sizeof 를 계산해서 알아서 증가 시켜준다. 즉 sizeof(a) 는 4 임으로 *(a+1) 은 bffff7b0 + 4 의 주소를 가르키게 된다. a[1] 의 값을 가져오길 원한다면 아래와 같이 코딩하면 된다. printf("%d
", *(a+1));


그러면 bffff7b4 에서 bffff7b7 까지의 값을 읽어들여서 int 형으로 변환시켜서 출력 시켜주게 된다. 마찬가지로 *c+1 을 하게 되면 1byte 만큼 증가 시켜서 해당 주소가 가르키는 값을 화면에 출력 시켜줄것이다.

그리고 혼동할수 있는데, *a+1 과 *(a+1)은 그 결과 값이 엄연히 다르다. *a+1 은 *a 의 값에 +1 을 해주는 것이고 (즉 325), *(a+1), a의 주소에 sizeof(a)*1 만큼 이동한 주소값을 가르키는 것이다. 전자는 그냥 덧셈 연산이고, 후자가 포인터 연산이다. 가끔 혼동될수 있으니 주의해야 한다.

자 그러면 예제 memcpy2.c 를 pointer 버젼으로 바꾸어 보자. memcpy2.c 는 void 형을 설명하기 위한 예제로는 쓸만하지만 데이타의 이용을 위해서 비용이 큰 메모리 복사를 사용한다라는 단점을 가지고 있다. 이것을 아래와 같이 포인터 버젼으로 바꾸면 거의 비용이 들지 않는 효율적인 코드를 만들수 있다.

예제 : memcpy3.c




위의 코드는 데이타의 복사가 일어나지 않는다. 참조 2을 보면 f_data 가 data 의 주소위치를 가르키는(포인터) 하도록 했다. 그리고 참조 3에서는 data2 가 다시 f_data 의 주소 위치를 가르키도록 했다. 참조 2에서 주소 연산자 '&' 가 사용된 이유는 data 는 포인터가 아님으로 포인터인 f_data 에 대입시킬수가 없기 때문이다. 대입연산자는 같은 타입일 경우에만 가능하다. 그러므로 주소연산자 '&' 를 이용해서 data 의 주소를 f_data 에 대입 가능하도록 만든것이다. 참조 3 에서 f_data 는 그 자체가 포인터 임으로 포인터인 data2 에 대입해도 전혀 문제가 없다.

위의 결과를 출력해 보면 아래와 비슷하게 나올것이다. 실행시 메모리 상태등에 따라서 값이 다르게 나올수 있다. point size 4
point size 4
data : bffff700
f_data : bffff6f8
data2 : bffff6fc

data2.age 29
data2.weight 64


그런데 좀 이상한게 있다 char *, int * 의 크기가 모두 4 로 나와 있다. 그 이유는 포인터 자체가 하나의 자료형으로 취급되기 때문이다. 포인터를 위한 크기는 운영체제에 따라 다르지만 보통 4바이트인 경우가 많다. 이경우 가르킬수 있는 메모리의 최대 크기는 2^32 이 될것이다. 리눅스는 4byte 의 크기를 가짐으로 대략적으로 리눅스 운영체제가 관리할수 있는 메모리의 최대크기는 4G 바이트 쯤이 될것이며, 실제로 커널에 특별한 패치를 가하지 않는한 이정도의 한계를 가진다.

각 포인터는 자신이 가리켜야할 데이타가 있는 주소의 위치 정보를 가지고 있다. 위의 각 포인터가 가르키는 정보를 그림으로 나타낸다면 아래와 같을 것이다. 단위 : byte
0 1 2 3 0 1 2 3 4 ...... 103
+-+-+-+-+ +-+-+-+-+ +-+-+-+-+-+----------------+-+
|f_data | | data2 | | data |
+-+-+-+-+ +-+-+-+-+ +-+-+-+-+-+----------------+-+
bffff6f8 bffff6fc bffff700
|| | |
|+-----<-------+ |
+------------->--------------+


data2 에서 age 값을 가져오기 위해서 주소 연산자 "->"를 사용하고 있는데, 위의 그림을 보면 이해가 가능할것이다. age 라는 멤버변수의 값은 data2 의 멤버 변수가 아닌 data2 가 가르키고(포인터 하고) 있는 data의 멤버변수이다. 그러므로 반드시 포인터 연산자를 써서, 참조할 데이타가 있는 주소값을 연산해 주어야 한다. 만약 data2.age 로 값을 가져오려고 한다면, 컴파일러는 에러를 리턴하며 컴파일 실패 할것이다.


--------------------------------------------------------------------------------

3.2절. 배열과 포인터는 동일하다
예제 mem.c 에서 변수 a 는 4의 크기를 가지는 배열로 선언되있다. 배열은 포인터과 동일하게 사용가능하다. 즉 a[0] 은 *a 와 같으며, a[1] 은 *(a+1) 과 동일하다.

앞장에서 포인터 연산을 할때 *a+1 은 포인터 연산이 아니라고 했는데, 그 이유는 *a+1 은 a[1] 이 아니고 a[0] + 1 이 되기 때문이다.


출처 : http://joinc.co.kr/modules.php?name=new ··· 3Dnested
"UNIX/Linux C" 카테고리의 다른 글
  • library 의 사용 (0)2007/05/14
  • PIPE 응용 (0)2007/05/14
  • 데이타와 포인터 (0)2007/05/14
  • fcntl 함수 사용하기 (0)2007/05/14
  • 터미널 제어 (0)2007/05/14
2007/05/14 10:56 2007/05/14 10:56
Posted by webdizen
Tags 데이터와 형, 배열과 포인터, 포인터
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2939

Leave your greetings.

[로그인][오픈아이디란?]

Programming/UNIX/Linux C2007/05/14 10:44

fcntl 함수 사용하기

유닉스 시스템플밍에서 가장 비중있게 다루는 것은 역시 파일관련 작업이다. fcntl 은 이러한 파일의 특성을 조정하는 일을 하는 함수로 이것을 이용하면 파일과 관련된 다양하고 세밀한 여러가지 작업을 좀더 쉽게 할수 있다.

1절. 소개
2절. fcntl 을 이용한 파일제어
2.1절. fcntl 로 할수 있는 일들
2.2절. close-on-exec 에 대해서
2.3절. 파일특성조작 하기
2.3.1절. F_DUPFD
2.3.2절. F_SETFD/F_GETFD
2.3.3절. F_GETFL/F_SETFL
2.3.4절. F_GETOWN/F_SETOWN

--------------------------------------------------------------------------------

1절. 소개
유닉스의 모든것은 파일로 이루어져 있다는 사실은 아마도 잘알고 있을 것이다. 그러므로 유능한? 유닉스 프로그래머로 성장하기 위해서는 파일관련된 여러가지 작업들을 능숙하게 처리할수 있어야한다. 실제로 유닉스 프로그램을 하게 되면 가장 많이 하는 작업이 파일과 연관된 작업이다.

일반 파일은 물론이고, 네트웍 프로그래밍시 소켓을 다룰때, IPC, 디바이스등을 다루는 모든 것이 결국은 파일을 다루는 것들이다.

fcntl 은 이러한 파일들의 특성 제어를 위해 제공하는 함수이다.


--------------------------------------------------------------------------------

2절. fcntl 을 이용한 파일제어
fcntl 시스템호출은 이미 열려있는 파일의 특성 제어를 위해서 사용된다.




첫번째 인자로 주어지는 fd 는 open(2), socket(2) 등의 시스템 호출을 통해서 만들어진 파일 지정자이다. 두번째 인자인 cmd 가 fd 에 대한 특성을 제어하기위한 값이다.


--------------------------------------------------------------------------------

2.1절. fcntl 로 할수 있는 일들
fcntl 로 할수 있는 일들은 결국 cmd 에 의해서 결정된다고 볼수 있다. 대략적으로 할수 있는 일들은 다음과 같다. 할수 있는 일은 cmd 에 의해서 결정됨으로 cmd 별로 설명을 했다.


F_DUPFD
이름에서 주는 어감처럼 열려진 파일지정자를 복사하기 위해서 사용한다. 언뜻보면 dup2(2) 함수와 매우 비슷한데, dup2 는 복사될 파일지정자를 사용자가 지시하는 반면, F_DUPFD 를 사용할경우 arg 와 같은 크기의 파일지정자를 되돌려주거나, 이미 사용되어지고 있다면, arg 보다 큰 할당가능한 파일지정번호중 가장 작은 번호를 되돌려준다.

이 복사된 파일지정번호는 잠금, 파일위치 포인터, 플레그 등을 공유한다. 즉 파일지정자들중 하나에서 파일의 위치가 변경된다면(lseek등을 이용), 다른 파일지정자도 변경된다.

그렇지만 close-on-exec 는 공유하지 않는다. close-on-exec 는 다음장에서 다루도록 하겠다.

F_GETFD
리턴값으로 fd 에 대한 flag 값을 넘겨준다. 현재는 FD_CLOEXEC 정보만 넘겨준다. FD_CLOEXEC 는 close-on-exec 정책에 관한 내용으로 다음장에서 다루도록 하겠다.

F_SETFD
FD_CLOEXEC(close-on-exec) 의 값을 지정된 비트값으로 설정한다.

F_GETFL
파일지정자에 대한 플래그값 - open(2) 호출시 지정한 플래그를 되돌려준다.

F_SETFL
arg 에 지정된 값으로 파일지정자 fd 의 플래그를 재 설정한다. 현재는 단지 O_APPEND, O_NONBLOCK, O_ASYNC 만을 설정할수 있다. 다른 플래그들 (O_WRONLY 와 같은) 은 영향을 받지 않는다.

F_GETOWN
이것은 비동기 입출력과 관련되어서 사용되며, SIGIO와 SIGURG 신호를 받는 프로세스 아이디를 얻기 위해서 사용된다.

F_SETOWN
비동기 입출력과 관련되어서 사용되며, SIGIO, SIGURG 시그널을 수신하는 프로세스 아이디(혹은 그룹)을 설정하기 위해서 사용된다.

이외에도 F_SETLK, F_SETLKW, F_SETLK 와 같은 레코드 잠금에 대한 설정도 가능하다. 이 내용들은 fcntl 레코드잠금에서 다루고 있음으로 여기에서 설명하진 않겠다. (또한 이문서의 내용은 파일단위의 특성 조작이므로 레코드단위의 특성조작은 이 문서의 내용과 다른분야이다.)


--------------------------------------------------------------------------------

2.2절. close-on-exec 에 대해서
fcntl의 특성제어를 다루기 전에 close-on-exec에 대해서 간단히 알아보도록 하겠다.

보통 프로세스에서 exec(3) 를 시켜서 새로운 프로세스를 실행시키면 이 새로운 프로세스는 기존의 프로세스의 이미지를 덮어쓰게 된다. 그러면서 특별한 설정이 없을경우 열린파일지정자를 그대로 넘겨주게 된다. 다음 예제를 open-on-exec.c 로 저장하도록 하자.

예제 : open-on-exec.c

                       

execl 로 실행시키는 loop 프로그램은 그냥 무한루프 도는 프로그램 이니 각자 만들기 바란다.

위 프로그램을 실행시킨후 ps 를 확인하고 /proc/pid/fd 로 이동해서 ls 를 해보면
[root@coco fd]# ls -al
합계 0
dr-x------    2 root     root            0 10월 25 13:59 .
dr-xr-xr-x    3 root     root            0 10월 25 13:59 ..
lrwx------    1 root     root           64 10월 25 13:59 0 -> /dev/ttyp0
lrwx------    1 root     root           64 10월 25 13:59 1 -> /dev/ttyp0
lrwx------    1 root     root           64 10월 25 13:59 2 -> /dev/ttyp0
lr-x------    1 root     root           64 10월 25 13:59 3 -> /home/mycvs/test/exec_copy.txt
                       

exec 하면서 열린파일지정자가 상속되었음을 알수 있다.

그러나 때때로 exec 를 이용해서 프로세스를 만들기전에 기존에 열렸던 파일지정자들을 깨끗하게 정리하고 싶을때가 있을것이다. 이러한 경우를 close-on-exec 시킨다라고 말하며, fcntl 을 이용해서 열린 파일지정자에 대해서 close-on-exec 작동을 하도록 할수 있다. 위의 open-on-exec.c 를 close-on-exec 버젼으로 바꾸는 방법은 2.3.2절에서 설명하도록 하겠다.


--------------------------------------------------------------------------------

2.3절. 파일특성조작 하기
2.3.1절. F_DUPFD
위에서 설명을 이미 했음으로 간단한 예제로 이해를 돕는 수준에서 끝내도록 하겠다.

예제 : f_dupfd.c

                               

위의 프로그램을 실행시켜보면 testfd 로 각각 4, 5 가 아닌 (0, 1, 2 는 표준 입력/출력/에러, 3 은 test.txt) 10, 11 로 파일지정번호가 지정됨을 알수 있을것이다.


--------------------------------------------------------------------------------

2.3.2절. F_SETFD/F_GETFD
close-on-exec 값을 조정하기 위해서 사용된다. F_GETFD 를 이용해서 FD_CLOEXEC 값을 가져오고 F_SETFD 를 이용해서 이 값을 변경한다. 기본적으로는 exec 할때 close 되지 않는다. 그럼 f_dupfd.c 를 close-on-exec 하도록 약간 변경 해보도록 하자.

예제 : close-on-exec

                               

위의 프로그램을 실행하면 아래와 같은 실행 결과를 보여줄것이다.
[root@localhost c_source]# ./close-on-exec
close-on-exec bit off
close-on-exec bit on
                               

위의 프로그램을 실행시켜둔 상태에서 과연 fd 가 close-on-exec 되었는지 /proc/pid/fd 를 확인해보면 기존에 열려있던 fd 가 상속되어있지 않음을 확인할수 있을것이다.


--------------------------------------------------------------------------------

2.3.3절. F_GETFL/F_SETFL
F_GETFL 을 open(2)등에 의해서 열려진 파일지정자 에 대한 플레그 값을 읽어온다. 그리고 F_SETFL 에 의해서 파일지정자에 대한 값(특성)을 세팅한다.

F_SETFL 을 이용해서 변경할수 있는 파일지정자의 특성은 O_APPEND, O_NONBLOCK, O_ASYNC 등이다. O_RDONLY, O_WRONLY, O_RDWR 등의 정보는 읽어올수는 있지만 변경할수는 없다.

우선 읽기권한 정보를 읽어오는 것은 O_ACCMODE 와 비트연산을 함으로써 가져올수 있다. 그밖에 정보는 각각의 플레그(O_APPEND, O_NONBLOCK, O_ASYNC) 등과 비트연산을 함으로써 얻어올수 있다.

예제 : fgetfl_test.c

                               

이 프로그램을 실행시키면 다음과 같은 결과를 보여줄것이다.
[root@localhost c_source]# ./fgetfl_test
O_RDONLY setting
BLOCKING mode setting
                               

최초 open 시의 파일 플레그 값을 읽어오고 있음을 알수 있다.

특성을 변경할수 있는 3개의 경우 단순히 비트연산을 해서 해당 flag 를 킨다음에 그값을 fcntl 을 이용해서 파일지정자에 세팅해주면 된다. 다음 예제는 표준입력(0) 을 NONBLOCK 모드로 변경 시킨 예이다.

fsetfl_test.c
               

기본적으로 표준입력(0) 은 봉쇄모드로 시작된다. 이것을 fcntl 을 이용해서 비봉쇄 모드로 만들고 테스트한것이다. 위의 프로그램을 실행하면 처음 입력은 봉쇄모드로 사용자 입력이 있을때까지 기다리고, 그 후에는 비봉쇄 모드로 바쁜 상태에서 사용자 입력을 처리하는것을 볼수 있을것이다.

O_ASYNC 와 O_APPEND 역시 동일한 방법으로 처리가능하다.

O_APPEND 는 대충 어떠한 특성변경을 위해서 사용하는지 알것이다. O_ASYNC 에 대해서는 별로 익숙하지 않을수도 있는데, 유닉스 I/O 모델중 흔히 말하는 "비동기 입출력" 모델을 구현하기 위해서 사용된다. 비동기는 언제 일어날지 알수 없는 사건을 말하며, 유닉스에서는 이러한 비동기 사건을 알려주기 위해서 시그널(signal)을 사용한다. 유닉스의 I/O 모델의 종류는 리눅스 I/O 모델 을 참고하기 바란다.

이 문서에서는 비동기 입력출방법 자체에 대한 내용은 다루지 않을것이다. 이것은 리눅스 I/O 모델의 또다른 구현임으로 리눅스 I/O 모델 관련 문서를 다루면서 언급할것이다. (현재는 Blocking I/O, Non-Blocking I/O, I/O Multiplexing 까지를 다룬 문서들이 제공되고 있다) 다루고 있다)


--------------------------------------------------------------------------------

2.3.4절. F_GETOWN/F_SETOWN
바로 윗장에서 우리는 F_GETFL/F_SETFL 을 이용해서 파일지정자의 특성을 변경하는 법을 배웠었다. 그중에서 비동기 입출력 설정을 위한 O_ASYNC 플레그를 설정하는 방법을 배웠었는데, F_GETOWN/F_SETOWN 은 이 비동기 입출력과 관련되어서 사용되는 플래그이다.

이들 플레그를 사용함으로써 비동기 입출력 모드로 파일지정자가 설정되어 있을때, 어떤 프로세스(혹은 그룹) 아이디로 부터 오는 시그널을 받을지를 지정해줄수 있다.



출처 : http://joinc.co.kr/modules.php?name=new ··· 3Dnested
"UNIX/Linux C" 카테고리의 다른 글
  • PIPE 응용 (0)2007/05/14
  • 데이타와 포인터 (0)2007/05/14
  • fcntl 함수 사용하기 (0)2007/05/14
  • 터미널 제어 (0)2007/05/14
  • random 값 얻어오기 (0)2007/05/14
2007/05/14 10:44 2007/05/14 10:44
Posted by webdizen
Tags fcntl, 파일 특성 조작
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2937

Leave your greetings.

[로그인][오픈아이디란?]

Programming/UNIX/Linux C2007/05/14 10:37

터미널 제어

이번 기사는 termios 관련 함수모음을 이용한 터미널제어에 관한 내용을 다루며, 그중에서도 쉽게 접할수 있는 키보드+모니터쌍으로 구성된 터미널관련 내용을 다룬다.

1절. 터미널
1.1절. 터미널이란 무엇인가 ?
1.2절. 터미널의 종류
1.2.1절. Text Terminal
1.2.2절. Window (Terminal ?)& Thin Clients
2절. 터미널 제어
2.1절. 터미널 제어가 왜 필요한가 ?
2.2절. 터미널 장치 파일(tty)
2.3절. termios 함수를 통한 터미널제어
2.3.1절. 터미널 기본모드
2.3.1.1절. Canonical Mode Input Processing
2.3.1.2절. NonCanonical Mode Input Processing
2.3.2절. termios 구조체 - 터미널 속성구조체
2.3.3절. 터미널 속성 얻어오기
2.3.4절. 터미널 속성 설정
2.3.5절. 몇가지 예제들
2.3.6절. 터미널 검사하기
2.3.7절. 터미널 윈도우 사이즈
2.3.8절. stty 명령어
3절. 결론

--------------------------------------------------------------------------------

1절. 터미널
1.1절. 터미널이란 무엇인가 ?
터미널(terminal) 은 모니터(moniter)와 키보드(keyboard)의 조합으로 지역적으로 붙어있거나 혹은 원격으로 연결된 컴퓨터와 대화하기 위해서 사용되며, 장치의 개념으로 이해할수 있다. 프로그래머는 프로그램을 실행시키기 위해서 키보드를 이용해서 명령을 실행시키며, 그 결과는 모니터를 통해서 출력된다. 우리는 터미널이 없이는 컴퓨터를 효과적으로 다룰수 없을것이다. - 모니터+키보드 대신에 천공테이프를 통해서 명령을 실행시키고, 그 결과를 라인프린터를 통해서 출력받는 다고 가정해보라 -

유닉스 시스템은 기본적으로 text 터미널을 사용하고 있다. 이 텍스트 터미널은 1970 년대 메인프레임(mainframe) 컴퓨터와 통신하기 위해서 사용했었는데, 프로그래머는 전용 장치 (모니터와 키보드가 연결된)를 케이블로 메인프레임과 연결시킨후 프로그래밍작업을 하거나, 문서를 만들고, 시스템을 관리하는 일을 했다.

지금은 터미널장치와 소프트웨어에 많은 발전이 이루어져서, 그당시와는 비교할수 없는 편리한 환경에서 작업을 할수 있다. 만약 여러분이 리눅스를 사용하고 있다면 (X Windows가 아닌) 아마도 여러가지 작업을 위해서 키보드와 모니터 - 터미널 - 를 이용하고 있을것이다. 또한 CTRL+ALT+F1 ... F6 키를 이용하면 여러개의 가상 터미널을 만들어서 가상 터미널 상을 오가면서 동시에 몇개의 다른 작업을 수행할수도 있을것이다.

추가적으로 여러분이 X 윈도우를 사용한다면 터미널을 흉내내는 (emulate) 어플리케이션들을 사용해서, 수십개 이상의 터미널을 띄워서 자유자재로 터미널상을 오가면서 작업을 할수도 있다. 이러한 터미널 emulate 프로그램으로는 xterm, rxvt, zterm 등이 있으며, 한글 입/출력을 지원 기능을 추가한 hanterm 등이 있다. 만약 외형적으로 아주 멋진 터미널 emulate 를 구한다면 Eterm 과 같은 어플리케이션을 사용할수도 있을것이다. 윈도우즈에서는 crt, putty 등의 터미널 emulate 어플리케이션이 존재한다.

현재 우리가 사용하는 개인 PC 는 터미널장치로 키보드와 모니터를 이용하는데, 이들 장치는 각각 그래픽 카드와 PS/2 포트에 연결되어서 사용되어진다. 그러나 위에서 예를든 메인프레임, 라우터 그리고 OS가 내장된 몇가지 소형장비들은 오랜역사를 지니고 있는 serial port(직렬포트) 에 직접 케이블을 연결해서 사용하기도 한다. 라우터에 문제가 생겨서 A/S 직원이 A/S 하는걸 보았다면 아마도 전용 터미널을 serial port (com port) 와 연결해서 작업하는걸 보았을것이다.


--------------------------------------------------------------------------------

1.2절. 터미널의 종류
1.2.1절. Text Terminal
텍스트 터미널은 호스트 컴퓨터와 통신하기 위해서 ASCII 코드를 이용한다. 이러한 ASCII 코드는 보통은 키보드를 통해서 바로 입력이 가능한 문자들이며 모니터를 통해서 바로 출력될수 있다. 그리고 이러한 문자들외에도 몇가지 제어를 위한 문자(Special control byte)들이 존재한다. 이들 제어문자는 커서이동, 삭제 와 같은 입력을 제어하기 위해서 사용된다. 이러한 제어문자는 수백가지 종류가 존재하고 있다. 또한 강조글자(bold) 밑줄문자(underline) 와 색상변경과 같은 출력 모양의 조정을 할수도 있다. 이러한 출력모양 조정을 위해서 ANSI 를 사용한다.

이들 텍스트 터미널은 기본적으로 ASCII 코드만을 통해서 통신을 하기 때문에, 매우저렴하게 전용장치를 개발할수 있으며, 어플리케이션 역시 쉽게 개발가능하다. - 정확히 말하자면 ASCII 256 중에 상위 128 개만을 사용한다 - 386, 286 급 컴퓨터 정도면 충분히 터미널 장치로 사용할수 있을것이며, 만약 휴대성이 중요하다면 액정디스플레이가 달린 매우 조그마한 싸이즈로 만들수도 있을것이며, 실제 라우터 세팅을 위한 전용 장치들이 액정디스플레이와 키보드만을 갖춘 형태로 사용되어지고 있다.


--------------------------------------------------------------------------------

1.2.2절. Window (Terminal ?)& Thin Clients
text 터미널이 아닌것은 Thin Clients(혹은 윈도우 터미널) 라고 말할수 있다. 통신수단으로 그래픽 정보를 사용한다. 이들 Thin Clients 의 경우 전송받은 GUI 화면을 빠른시간에 모니터에 뿌려줄수 있어야 함으로 텍스트 터미널을 구현하기 위한 장치보다 더 비용이 많이든다.

또한 그래픽정보를 전송해야 함으로 텍스트 터미널보다 네트웍 자원을 많이 소모한다.

Thin 클라이언트를 이용 할경우 많은 저장공간과 빠른 연산능력을 가진 중앙컴퓨터를 하나두고, 다른 사용자는 적은 저장공간과 상대적으로 느린 연산능력을 가진 (Thin) 컴퓨터를 두어서 실제 어플리케이션을 사용하거나, 데이타를 이용할때 중앙 컴퓨터에 연결해서 필요한 작업을 하는 컴퓨팅 환경을 만들수 있을것이다.

이럴경우 여러가지 중요 정보가 중앙컴퓨터에 집중되고, 다수의 연결 사용자 컴퓨터에 투자하는 비용을 줄일수 있음으로 정보관리와 비용측면에서 이익을 볼수 있을거라고 생각된다. 그러나 실제로 이러한 컴퓨팅 환경을 만들어서 쓰는 경우는 그리 흔하지 않다. 지역네트웍에 과도한 부하를 유발할수 있고, 중앙 컴퓨터에 문제가 생겼을 경우 모든 작업이 중단될수 있기 때문이다. 거기에 자신의 정보가 자신이 하드에 있지 않고 중앙의 다른 하드에 있다는 것 자체를 많은 직원들이 싫어하는 (믿어워 하지 못하는) 경향이 있기 때문이다.

그래서 주로 원격지의 MS 윈도우즈 계열의 서버를 관리하기 위한 목적으로 많이 쓰인다. VNC 라든지 Windows Terminal Server 가 이러한 어플리케이션이다. 리눅스의 X 윈도우의 경우 C/S 환경으로 만들어져 있기 때문에 기본적으로 Window 터미널로써의 역활을 수행할수 있다.


--------------------------------------------------------------------------------

2절. 터미널 제어
여기에서는 기본적으로 text terminal 을 기준으로 설명하게 될것이며, 테스트를 하기 위한 운영체제는 Linux (Kernel 2.4.x) 가 될것이다. 또한 시리얼통신을 위한 terminal 환경과 같은 것들은 다루지 않을것이다. 이문서에서는 PC에서 가장 일반적으로 사용되는 키보드+모니터 (/dev/tty) 에 대해서만 다룰것이다. 시리얼통신과 같은 특수? 한것들은 역시(-.-;) 기회가 된다면(언젠가) 다루도록 하겠다.


--------------------------------------------------------------------------------

2.1절. 터미널 제어가 왜 필요한가 ?
기본적으로 사용자는 컴퓨터혹은 여러가지 주변장치와 대화할때 터미널을 이용해서 대화하게 된다. 보통 우리가 컴퓨터 모니터 앞에 앉아서 작업을 할때는 표준 터미널장치인 /dev/tty 를 통해서 컴퓨터와 대화를 하겠지만 때때로 직렬(serial)포트를 통해서 컴퓨터와 대화를 해야하는 경우도 생길것이다. 이들 직렬포트와 통신하기 위해서는 /dev/ttyS0 과 같은 장치파일 들을 이용해서 통신을 하게 되는데, 이경우 회선속도를 서로 맞추어줘야 하며, 여러가지 이스케이프문자(Ctrl+C 와 같은)에 대한 처리등을 해주어야 함으로 터미널특성 변경등의 작업을 해주어야 한다. - 이 문서에서는 시리얼 통신과 관련된 주제는 다루지 않을것이다. -

그리고 /dev/tty 를 통해서 작업을 하고자 할때도 키보드 입력을 반향(echo) 시켜야 할것인지, 특수문자입력에 대한 정책수정, 캐리지리턴을 받아들여야 할것인지, Ctrl+d, Ctrl+c 와 같은 제어문자를 받아들일것인지 무시할것인지 등 필요에 따라서 터미널의 특성을 변경시켜줘야 하는 경우가 있다. 가장 간단한 예로 아이디와 패스워드를 입력받는 터미널기반의 프로그램을 만들어야 한다고 했을때, 패스워드 입력시에는 키보드로 입력되는 문자가 모니터에 바로 출력되게 하면 안될것이다. 이럴경우는 터미널의 특성을 변경시켜서 키보드입력이 화면에 반향(echo)되지 않도록 해주어야 한다. 다음의 예제 프로그램을 실행시켜 보자.

예제 : echo_off.c

                       

위의 프로그램은 사용자 패스워드를 입력받는 일을한다. 패스워드의 경우 화면에 바로 출력되면 안됨으로, 터미널의 라인 출력 특성을 "반향 끔" 으로 설정하고 키입력을 받아들이도록 했다. 그리고 사용자 키입력을 "*" 로 대체 시켜서 화면에 출력하도록 해서 사용자 패스워드를 입력 받도록 만들었다.


--------------------------------------------------------------------------------

2.2절. 터미널 장치 파일(tty)
각각의 터미널은 자신이 사용하는 장치파일에 연결해서 통신을 하게 된다. 예를들어 com1 포트와 시리얼 통신을 해야 한다면 /dev/ttyS0 과 연결을 해야 한다. 그렇지 않고 일반적인 개인 PC 의 표준터미널인 키보드&모니터 와 통신하기를 원한다면 /dev/tty0 과같은 장치파일과 연결해야 할것이다. 이러한 장치파일은 ASCII 코드중 화면에 표시되는 문자(chricter)를 이용해서 통신을 하게 됨으로 캐릭터 디바이스 파일(문자 장치파일) 이라고 부른다. ls -al 로 위의 파일의 정보를 알아보면 아래와 같을것이다.
[root@localhost /dev]# ls -al /dev/tty*        
crw-rw-rw-    1 root     root       5,   0 10월 12  2001 /dev/tty
crw--w----    1 root     root       4,   0 12월  1 12:21 /dev/tty0
crw--w----    1 root     tty        4,   1 12월  1 12:21 /dev/tty1
crw--w----    1 root     root       4,  10 10월 12  2001 /dev/tty10
crw--w----    1 root     root       4,  11 10월 12  2001 /dev/tty11
crw--w----    1 root     root       4,  12 10월 12  2001 /dev/tty12
crw--w----    1 root     root       4,  13 10월 12  2001 /dev/tty13
...
                       

파일 리스트의 가장앞에 있는 'c' 가 캐릭터 디바이스 파일임을 나타내주는 표시이다.

이러한 장치파일에에 접근해서 원하는 장치와 통신하는 방법은 의외로 간단하다. 일반파일과 마찬가지로 open 한다음에 read, write 함수를 사용해서 정보를 읽고 쓰면 된다.     | <-- APP 개발자영역 --> | <-- 디바이스 드라이버 개발자영역 --> |
 +-----+     +-----------+     +------------+    +---------------+    +------+
 | APP | --- | open(2)   | --- | /dev/ttyS0 | -- | device driver | -- | 장치 |  
 +-----+     | read(2)   |     +------------+    +---------------+    +------+
             | write(2)  |    
             +-----------+    
                       

위의 그림은 일반 어플리케이션에서 어떻게 장치와 통신할수 있는지에 대한 계략적인 모양을 나타낸 그림이다. 장치는 키보드가 될수도 있을것이며, 때에 따라서는 COM,USB 포트가 될수도 있을것이다. 많이 알려져 있는 대부분의 장치 디바이스 드라이버는 이미 제작되어 있음으로 일반 어플리케이션 프로그래머라면 open, read, write 를 이용해서 장치와 통신하는 부분까지만 신경쓰면 된다. 그러나 알려져 있지 않은 장치를 컴퓨터에 연결해서 사용해야 할경우에는 직접 디바이스 드라이버를 제작해야 할것이다.

Barcode 리더기로 부터 읽어들인 데이타를 처리하는 어플리케이션을 만들고자 한다면, (com1 포트를 사용한다고 가정하자), 어플리케이션 제작자는 단지 /dev/ttyS0 을 open 한다음에 read 함수를 이용해서 읽을 데이타가 있는지 검사하고, 데이타가 들어오면 이것을 적당하게 처리하도록 해주면 된다. 물론 내부적으론 몇가지 다른 일들을 해줘야 겠지만, 개념적으로 보자면 매우 간단하게 구현가능함을 알수 있다.


--------------------------------------------------------------------------------

2.3절. termios 함수를 통한 터미널제어
유닉스에서는 이러한 터미널제어를 위해서 termios 라는 함수모음을 제공한다. 우리는 이 함수들을 통해서 터미널을 원하는 방향으로 제어할수 있게 된다. 이번장에서는 termios 함수들에 대한 설명과 이를 이용한 터미널제어의 방법에 대해서 알아보도록 하겠다.

기본적으로 termios 는 디바이스(장치)와의 asynchronous(비동기) 통신을 위한 프로그래밍 인터페이스를 제공한다.


--------------------------------------------------------------------------------

2.3.1절. 터미널 기본모드
터미널은 크게 2가지 모드인 "정규모드"와 "비정규모드" 로 나뉜다. 이것들은 터미널을 제어하는데 매우 중요한 요소임으로 별도의 장을 만들어서 설명을 했다.


--------------------------------------------------------------------------------

2.3.1.1절. Canonical Mode Input Processing
흔히 정규모드라고 하며 기본적으로 라인단위로 입출력을 처리하게 된다. 여기에서 말하는 라인의 개념은 우리들이 에디터에서 작업할때의 라인과 같은 개념으로 '\n' 혹은 EOF(End-Of-File) 를 만날때까지의 문자열을 말한다. 보통 사용자와 컴퓨터와 대화하기 위한 입출력은 정규모드 상태에서 행해진다. 이상태에서는 문자열 입력후 Enter 키를 눌러야 리턴이 될것이다. (혹은 Ctrl+d 를 눌러서 EOF 를 발생시킬경우)

또한 정규 방식에서는 ERASE와 KILL 문자등이 허용된다. 정규방식 자체가 라인단위 입출력작동임으로 당연히 Erase, kill 문자를 사용할수 있을것이다.

기본적으로 터미널은 정규모드로 시작한다.


--------------------------------------------------------------------------------

2.3.1.2절. NonCanonical Mode Input Processing
비정규모드라고 한다. 정규모드가 라인단위로 입출력을 처리하는 것과 달리 비정규모드는 한바이트씩 처리하게 된다. 한바이트씩 처리하게 됨으로 정규모드에서 가능했던 여러가지 특수문자들(ERASE, KILL, EOF, NL, CR)을 사용할수 없게 된다. - 당연하다 이미 돌려진 문자에 대해서 ERASE 등을 할수 있을 턱이 없다 -

예제 echo_off.c 가 비정규모드로 작동하는 프로그램인데, delete 키등이 먹지 않음을 알수 있다. 위에서 말했듯이 비정규모드에서는 ERASE 문자가 사용되지 않기 때문이다. echo_off.c 를 비정규 모드로 작성한 이유는 정규모드로 할경우 개행문자 '\n' 이 입력되기 전까지는 리턴되지 않음으로 바이트단위로 리턴하도록 하기 위해서이다. 위 예제코드에서 new_settings.c_lflag &= (~ICANON); 을 주석처리 한다음에 실행시키면 엔터키를 입력하기 전까지는 화면에 "*" 이 출력되지 않음을 알수 있을것이다. 대신 Delete 와 같은 몇몇 특수문자의 전달이 가능함을 알수 있다.


--------------------------------------------------------------------------------

2.3.2절. termios 구조체 - 터미널 속성구조체
모든 터미널 데이타는 다음의 termios 구조체의 각 플레그의 비트값을 읽어오거나 비트값을 세팅해줌으로써 검사와 설정을 할수 있다.

                               

c_inflag 는 말그대로 입력과 관련되어서 터미널 속성을 변경하기 위해서 사용된다. c_oflag 는 출력과 관련된 속성변경을 처리히가 위해서, c_cflag 는 직렬(시리얼)통신과 관련된 터미널 속성변경을 위해서 사용된다. 마지막 c_lflag 는 실제 사용자에게 보여지는 터미널의 속성 즉 출력의 반향(ECHO)를 on/off, 특수입력문자들(Ctrl+C 와 같은)의 속성 제어를 제어하기 위해서 사용한다.

다음은 각각의 플래그를 통해서 설정할수 있는 터미널의 속성들이다. 아마 별로 사용하지 않는 몇가지 정도는 빠졌을수도 있음으로 자세한 모든 터미널 속성정보를 알고 싶다면 termios 의 man 페이지를 참고하기 바란다.

표 1. 터미널 속성값 - c_iflag

설명 터미널 입력과 관련된 제어를 한다. 입력제어 값들은 모드 on 되어있는 상태이다.
IGNBRK break 컨디션을 무시한다. break 컨디션은 연속된 0의 값을 가지는 비트로 정의된다. 만약 IGNBRK 가 on 되어 있고 입력데이타에 break 컨디션이 전달된다면 무시하게 된다.
BRKINT 이것은 break 컨디션 상에서 SIGINT 를 가로채기 위해서 사용된다. 만약 IGNBRK 가 off 되어있고, BRKINT가 세팅되어 있는 상태에서 break 컨디션이 입력된다면 이것은 출력쿠에 쌓이게 된다. 만약 터미널이 foreground process 상태로 돌고 있는 도중이라면, SIGINT 신호를 발생시키게 된다.
IGNPAR parity 에러무시한다.
INPCK parity 체크를 실시한다.
ISTRIP 만약 이 플레그를 on 시키면, 유효한 입력 문자를 처음 7bit 로 세팅하게 된다. 그렇지 않을경우 8bit 입력으로 처리한다.
INLCR NL 문자(new-line)를 CR(carriage return) 문자로 대체시킨다. 만약 이 플레그가 on 된다면, NL 문자는 CR 문자로 변경된다.
IGNCR CR 문자를 무시한다. on 될경우 입력되는 CR 문자를 무시한다. (읽지 않는다)
ICRNL CR 문자를 NL 문자로 대체시킨다. IGNCR 이 on 되어 있지 않은 상태에서 ICRNL이 on 되어 있을경우 받은 CR 을 NL 로 변경한다.
IXON start 와 stop 출력제어를 활성화 한다. 만약 플레그가 on 인 상태에서 STOP 캐릭터를 받았다면 출력을 멈추게 된다. 그러다가 START 캐릭터를 받게 되면 다시 출력을 시작하게 된다. 이것은 flow-control(흐름제어)를 위한 용도로 사용되며, 실제 STOP, START 캐릭터를 읽어들이지는 않는다. 만약 플레그가 off 상태라면 STOP, START 캐릭터를 읽어들인다.
IXOFF 입력제어를 위해서 start 와 stop 를 활성화 한다. 만약에 플레그가 on 으로 되어있다면 시스템은 입력큐가 가득 찼을때 STOP 캐릭터를 전송한다. 그리고나서 입력큐에 있는 데이타를 모두 읽었다면 START 캐릭터를 전송한다. IXON과 함께 흐름제어를 위해서 사용된다.
IXANY 어떤 문자에 대해서라도 출력을 한다. 이 플레그가 on 이라면 입력된 어떤 문자라도 출력을 시작 하게 된다.
IMAXBEL 만약 입력스트림이 overflows 될경우 ASCII BEL 을 반향한다.

표 2. 터미널 속성값 - c_iflag

설명 출력을 어떻게 다룰지에 관한 설정을한다. 기본설정은 모든 비트에 대해서 on(1) 의 상태이다.
OPOST Post-process output 모드. 만약 이 플레그가 off 상태라면 문자는 변화없이 전달된다. 그렇지 않고 on 이라면, 뒤에 오는 flag 에 의해서 변경될수 있다.
OLCUC 이 플레그가 on 되어 있을경우 영문소문자 는 영문대문자로 치환되어서 전달된다.
ONLCR 이 플레그가 on 되어 있을경우 NL 문자는 CR-NL 문자로 치환되어서 전달된다.
OCRNL 이 플레그가 on 되어 있다면 CR 문자는 NL 문자로 치환된다.
ONOCR 이 플레그가 on 되어 있다면 CR이 아닌 첫번째 문자부터 전달되게 된다.
OFILL 시간지연을 위해서 fill 문자를 이용한다. 이 플레그가 on 되어 있다면, 문자 전달시간지연을 위해서 time 지연을 사용하지 않고 fill 문자를 전달하는 것으로 대신한다.
OFDEL 이 플레그가 on 되어 있다면 fill 문자로 DEL이 전달된다.
NLDLY new-line 문자지연이다. NL0 과 NL1 두개의 값이 준비되어 있다. NL0 은 지연없음이며, NL1 의 경우 약 0.1 초 정도의 지연시간이 생긴다. 만약 OFILL 플레그가 on 되어 있다면, 지연을 만들기 위해서 두개의 fill 문자를 전달한다.
CRDLY CR 문자지연이다. 여기에는 CR0, CR1, CR2, CR3 의 값이 준비되어 있다. CR0 은 지연없음, CR1 은 컬럼위치에 따라 지연시간 결정, CR2 는 약 0.10 초, CR3 는 약 0.15초의 지연이 생긴다.
TABDLY 수평탭 시간지연이다. TAB0, TAB1, TAB2, TAB3 의 값이 준비되어 있다. TAB0은 지연없음, TAB1 은 컬럼위치에 따라 지연시간 결정, TAB2 는 0.1초가량의 지연된다. TAB3 로했을경우 탭은 스페이스로 확장된다.
BSDLY 백스페이스키 시간지연이다. BS0과 BS1 이 준비되어 있으며, BS0은 지연없음, BS1 은 약 0.05 의 시간지연을 나타낸다. OFILL 플레그가 세팅되어 있으며 BS1 이라면 한개의 fill 문자를 보낸다.

표 3. 터미널 속성값 - c_cflag

설명 하드웨어의 터미널제어에 관련된 속성들이다. 모뎀과 같은 하드웨어 제어에 많이 쓰인다.
CBAUD baud rate 에 대한 설정이다. 하드웨어에 따라서 속도의 변화가 불가능한경우도 있다. 모뎀을 사용해본적이 있다면 baud rate 에 대해서 자주 들어보았을것이다. 자주 사용되는 baud rate 는 B4800, B9600, B19200, B38400 으로 각숫자가 baud 속도를 나타낸다. 참고로 baud 는 하드웨어의 데이타 처리 속도를 나타낸다. 이외에 B50, B75, B110, B134, B150, B200, B300, B600, B1200, B1800, B2400 이 있다. 마찬가지로 B 다음의 숫자가 baud 속도이다.
CSIZE 문자의 크기를 지정하기 위해서 사용한다. CS5, CS6, CS7, CS8 이 준비되어 있으며, 각각 5, 6, 7, 8bit 를 나타낸다.
CSTOPB stop 비트의 수를 정한다. 이 플레그가 on 되어있다면 2개의 stop 비트를 그렇지 않다면 1개의 stop 비트를 보낸다.
CREAD receiver 을 활성화한다.
HUPCL 이 플레그가 세팅되어 있다면, 마지막 프로세스가 종료되거나, 마지막 프로세스의 line(회선)이 닫힐경우 disconnect 가 발생한다.
PARENB parity 를 추가한다. 이 플레그가 on 되어 있으면, parity 를 생성하고 검색하게 된다. 각문자에 parity bit 가 추가된다.

표 4. 터미널 속성값 - c_lflag

설명 로컬(사용자) 터미널에 관한 속성과 관련되어 있다. 초기값은 모두 on 상태이다.
ISIG signal 을 받아들인다. 이 플레그가 on 되어 있다면, INTR, QUIT, SUSP, DSUSP 과 같은 특수 문자를 받아들인다.
ICANON 이 플레그가 on되면 정규모드로 입력이 이루어진다.
NOFLSH queue flush 를 비활성화 시킨다.
ECHO 반향을 설정한다. 만약 이 플레그가 off 되어있다면 입력은 반향되지 않는다.
ECHOE erase 문자를 반향한다. 만약 이 플레그와 함께 ECHO 플레그가 on 되어있다면 ERASE 가 발생할경우 스크린에서 마지막 문자를 지우게 된다.
ECHOE erase 문자를 반향한다. 만약 이 플레그와 함께 ECHO 플레그가 on 되어있다면 ERASE 가 발생할경우 스크린에서 마지막 문자를 지우게 된다.
ECHOPRT 만약 ECHO 플래그가 on 되어있고 ECHOPRT가 on 되었을경우 ERASE 가 발생한다면 (back 스페이스키를 입력한다면) 삭제되는 문자가 '' 뒤에 표시되게 된다. 만약 모든 문자를 삭제했다면 '/' 가 출력되게 된다.
ECHOKE Backspace-Space-Backspace entire line on line kill
ECHONL NL문자가 반향된다. ECHONL 플레그가 on 되어있다면, ECHO 플래그가 on 되어있지 않더라도 NL 문자가 반향된다.
ECHOCTL 제어문자가 반향되도록 한다. 제어문자는 0에서 37 까지의 ASCII 코드이다. 이 플레그를 on 시킨상태에서 CTRL+X 같은 제어문자를 입력할경우 ^X 로 화면에 표시될것이다.

표 5. 제어문자 속성값 - c_cc

설명 c_cc 배열은 제어문자를 제어하기 위해서 사용한다. 즉 CTRL+X, CTRL+X, CTRL+C 등의 문자와 관련된 제어를 할수 있다.
VINTR 일시중지(Ctrl+C)와 관련된 제어이다. 만약 ISIG 플레그가 on 되어 있다면 일시중지 문자를 입력시키면 포그라운드 프로세스에 SIGINT 시그널이 발생된다.
VQUIT Quit 제어문자 Ctrl+\ 와 관련된다. ISIG 플래그가 on 되어 있고 Quit 제어문자가 입력되면 SIGQUIT 시그널이 발생한다.
VERASE ERASE 제어문자(백스페이스) 와 관련된다. 정규모드(ICANON) 플래그 가 on 되어 있고, ERASE 제어문자가 발생하면 가장 마지막 문자가 지워진다.
VKILL KILL 제어문자 (Ctrl+u)와 관련된다.
VEOF Ctrl-d 제어문자와 관련된다. ICANON 플래그가 on 되어 있고, EOF가 발생하면 읽기 대기중인 모든문자들은 개행문자를 만나지 않더라도 바로 프로세스에게 전달된다.
VSTOP STOP 제어문자(Ctrl+s)와 관련된다.
VSUSP SUSP 제어문자 (Ctrl+z)와 관련된다. ISIG 플래그가 on 되어있는 상태에서 Ctrl+z 가 입력되면 모든 포그라운드 프로세스에 SIGSTOP 신호가 전달된다.
VWERSE WERASE 제어문자 (Ctrl+w) 와 관련된다.



--------------------------------------------------------------------------------

2.3.3절. 터미널 속성 얻어오기
터미널의 속성을 변경하기 위해서는 일단 현재 터미널이 속성을 얻어와야 한다. 그리고 나서 변경하고픈 터미널의 속성값을 비트연산을 이용해서 on/off시켜주면 된다. 현재 터미널의 속성을 얻어오기 위해서 tcgetattr 함수를 사용한다. 이함수를 사용하면 터미널의 속성값을 얻어와서 struct termios 구조체를 채운후 되돌려준다.

                               

fd 는 속성을 알기를 원하는 open 된 파일(일반파일 혹은 문자디바이스 파일) 이다. 알아낸 속성은 termios_p 에 채워지게 된다.


--------------------------------------------------------------------------------

2.3.4절. 터미널 속성 설정
tcgetattr 을 이용해서 터미널 속성을 가져왔다면, bit 연산을 통해서 터미널 속성값을 변경해줄수 있다. 이러한 연산은 tcsetattr 함수를 이용한다.
                   

fd 는 속성을 변경시키기 원하는 open된 파일(터미널)을 가리키는 파일 지정자이다. tcgetattr 로 가져온 속성을 비트연산을 통하여 적당히 변경시킨후(termios 구조체의 멤버변수를 이용) 위의 함수를 이용해서 변경할수 있다. optioal_actions 는 변경된 터미널 설정이 언제시점에서 적용될것인지를 결정하기 위해서 사용한다. 다음의 3가지 값중에 하나를 선택해서 지정할수 있다.


TCSANOW
tcsetattr 을 호출한즉시 터미널 속성변경사항이 적용된다.

TCSADRAIN
해당 파일 지정자에 대한 모든 출력이 종료된다음에 속성변경사항이 적용된다.

TCSAFLUSH
해당 파일 지정자에 대한 모든 출력이 종료된다음 속성변경사항이 적용된다. 변화가 발생할때 읽혀지지 않은 데이타는 버려진다.



--------------------------------------------------------------------------------

2.3.5절. 몇가지 예제들
이상 터미널 설정에 관한 다양한 내용들에 대해서 알아봤는데, 몇가지 예제를 통해서 확실히 이해를 해보도록 하자.

우리는 위에서 termios 구조체의 c_lflag 를 이용하면 사용자 터미널의 속성을 변경시킬수 있다는 것을 배웠다. 보통의 터미널 프로그램을 사용하면서 입력을 하는 도중에 CTRL+C 등을 입력하면 SIGINT 신호가 발생해서 프로그램이 죽는다는 것을 알고 있다(시그널 핸들럴르 설치하지 않았을경우). 그러나 때때로 입력도중에 CTRL+C, CTRL+Z 등의 키입력을 무시하고 싶을때가 있을것이다. 이럴경우 temios 구조체와 tcgetattr, tcsetattr 을 이용함으로써 간단하게 위의 특수문자들을 무시할수 있다. 다음은 간단한 예제이다.

sig_ign.c

                               

위의 프로그램을 실행시켜서 입력도중에 CTRL+C, CTRL+Z 등의 키를 눌러서 어떻게 터미널이 반응하는지 확인해 보면 특수문자들이 무시되고 있다는걸 알수 있을것이다.
[root@localhost c_source]# ./sig_ign
^X^C^Z
                               

위의 경우는 CTRL+X, CTRL+C, CTRL+Z 를 입력한 경우인데, 모든 특수문자입력을 무시하고 있음을 알수 있다.

또다른 예제를 만들어보자. 우리는 가끔 사용자의 의향을 묻는 루틴을 만들어야할 필요가 있다 보통은 (Y/N) 등의 문자를 통해서 의향을 묻게 되는데, 사용자가 대소문자를 불문하고 입력할수 있기 때문에, 입력검사할때 대소문자를 따로검사해야 한다. 이럴때 다음과 같은 방법으로 터미널의 속성을 변경함으로써, 좀더 깔끔하게 마무리할수 있다.

input.c

                               

위의 예제 실행하면 사용자가 대문자를 입력하건 소문자를 입력하든지 간에 무조건 대문자로 치환된다는걸 알수 있을것이다. 그런데 사용자가 'Y' 를 입력한다음에 Enter 키를 눌러야지만 내용이 전달 된다. 그냥 'Y' 만 누르면 바로 전달되게 할수 없을까? 그럴때는 터미널 속성을 "비정규모드"로 변경시켜 주면 된다. 위 코드에 new_settings.c_lflag &= (~ICANON); 한줄만 추가시켜주면 된다.

현재 터미널이 몇 bit를 처리하는지 알아오는 예제를 만들어보도록 하자. 역시 매우 간단하다.

예제: csize.c

                               



다음은 터미널의 속도를 가져오는 간단한 예제코드이다.

예제 : baud.c

                               

아마도 B9600 으로 값이 나올것이다.

마지막으로 termios 구조체의 c_cc 값을 이용해서 터미널 속성을 변경 하는 예제이다.

c_cc.c

                               

위의 예제는 c_cc 배열을 이용해서 터미널의 특성을 변경한 것으로, Ctrl+z 키를 눌러도 SIGSTOP 신호가 발생하지 않도록 했다. fpathconf 함수에 대한 내용은 man 페이지를 참고하기 바란다. 만약 문자입력도중에 Ctrl+D 를 눌러도 입력이 종료되지 않도록 하려면 VSUSP 대신에 VEOF 를 넣어주면 된다.


--------------------------------------------------------------------------------

2.3.6절. 터미널 검사하기
우선 open 등을 통해서 열린 파일지정자 fd 가 장치에 연결된것인지 아니면 일반파일에 연결된것인지 알아내야 할것이다. isatty(3) 을 이용하면 된다.

                               

isatty 는 인자로 주어지는 fd 를 검사해서 만약 터미널이라면 1 그렇지 않다면 0을 되돌려준다.

예제 : isatty.c




열린 파일지정자 fd 에 대한 터미널이름을 반환받고 싶다면 ttyname(3) 함수를 사용한다.

                               

만약 fd가 터미널에 연결되어 있지 않다면 NULL을 반환한다. 그렇지 않다면 장치파일 이름을 리턴할것이다.


--------------------------------------------------------------------------------

2.3.7절. 터미널 윈도우 사이즈
요즘에 와서 대부분의 터미널은 터미널 에뮬레이터 어플리케이션을 통해서 X 윈도상에서 작업을 하게 된다. 이경우 터미널 윈도우 사이즈가 중요해진다. 윈도우의 크기변화에 따라서 화면모양을 다시 그려줘야할 필요가 있기 때문이다.

우리가 X 윈도우상에서 터미널 에뮬레이터의 크기를 변화시키면, SIGWINCH 시그널이 발생한다. 그럼으로 윈도우 크기의 변화를 알아내기 위해서는 SIGWINCH 시그널에 대한 시그널 핸들러를 설치하고, 시그널 핸들이 호출되었을때, 현재 터미널의 사이즈를 계산하면 될것이다. 터미널의 사이즈 계산은 ioctl(2) 함수를 사용하면 된다.

예제 : change_winsize.c


ws_row, ws_col 은 문자단위의 크기이며, ws_xpixel, ws_ypixel 은 픽셀단위 크기이다. hanterm 과 같은 터미널 에뮬레이트 프로그램을 띄운다음 위 프로그램을 실행시키고 창의 크기를 변경시키면서 테스트해보도록 하자.


--------------------------------------------------------------------------------

2.3.8절. stty 명령어
참고로 쉘상에서 stty 명령을 이용하면, 현재 터미널의 상세 속성값들을 얻어올수 있다.
[root@localhost c_source]# stty -a
speed 9600 baud; rows 30; columns 80; line = 0;
intr = ^C; quit = ^\; erase = <undef>; kill = ^U; eof = ^D; eol = <undef>
eol2 = <undef>; start = ^Q; stop = ^S; susp = <undef> rprnt = ^R; werase = ^W;
lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff
-iuclc -ixany -imaxbel
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
echoctl echoke
                               

앞에 '-' 가 붙은것은 비활성화 되었다는 뜻이다. 이러한 터미널 속성값은 자식프로세스에게 상속이 된다. 위의 프로그램들을 속성변경후 fork&exec 기법으로 쉘을 실행시키도록 해서 프로그램을 실행시키기전의 stty 출력값과 프로그램실행해서 속성변경후 fork&exec 된 쉘에서 stty 의 출력값과 한번 비교해 보기 바란다.


--------------------------------------------------------------------------------

3절. 결론
이상 termios 구조체와 이것을 이용한 터미널 속성변경에 대해서 간단히 알아보았다. 여기에서는 일반적인 터미널인 키보드+모니터를 기준으로 설명하고 있는데, 시리얼통신을 위한 터미널 속성에 대한 내용은 빠져있다. 이 내용들은 시리얼프로그래밍관련 강좌에서 다루도록 하겠다. 어쨋든 시리얼프로그래밍 역시 기본적으로 이문서에서 다룬 내용에 몇가지더 추가되는 것임으로, 이문서를 읽은후에 시리얼프로그래밍 관련 문서를 보면 쉽게 이해가 될것이다.


출처 : http://joinc.co.kr/modules.php?name=new ··· 3Dnested
"UNIX/Linux C" 카테고리의 다른 글
  • 데이타와 포인터 (0)2007/05/14
  • fcntl 함수 사용하기 (0)2007/05/14
  • 터미널 제어 (0)2007/05/14
  • random 값 얻어오기 (0)2007/05/14
  • openssl 을 통한 데이타 암호화 (0)2007/05/14
2007/05/14 10:37 2007/05/14 10:37
Posted by webdizen
Tags stty, termios, tty, 터미널 장치 파일, 터미널 제어
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2936

Leave your greetings.

[로그인][오픈아이디란?]

«Prev  1 2 3 4 5 ... 10  Next»

RSS HanRSS
Blog Image
webdizen
이 곳은 컴퓨터에 대해 연구하고, 공유하고, 소통하기 위한 연구실입니다. 개인적으로는 OLAP, Data Mining, Semantic Web, Data Modeling에 대해서 연구하고 있습니다.

Categories

전체 (2998)
Webdizen (134)
Life (6)
Diary (16)
Blog (9)
IDEA (1)
Travel (10)
Book (14)
Photo (7)
Movie (7)
Music (13)
Leisure Sports (10)
Funny (5)
Hardware (119)
Software (120)
Windows (5)
Unix & Linux (119)
Installation (4)
Kernel (10)
System (34)
Develop (22)
X-Window (0)
Applicaton (31)
Security (4)
Framework (2)
Hadoop (2)
Programming (805)
Algorithm & Data Structure (1)
Assembly (38)
UNIX/Linux C (95)
C++ (128)
STL (4)
Java (38)
Win32 API (92)
ATL/COM (44)
MFC (151)
.NET (26)
WCF/WPF (4)
C# (28)
Network Programming (17)
Database Programming (12)
OpenGL / DirectX (13)
Multimedia Programming (0)
Game Programming (21)
Parallel Distributed Progra... (0)
Reverse Engineering (0)
Debugging (9)
Python (1)
Ruby (1)
Ruby on Rails (1)
QT (4)
GTK (0)
JSP (0)
PHP (6)
ASP.NET (6)
ASP (3)
Development (28)
Useful Library (2)
Data Modeling (0)
Database (105)
Oracle (4)
MSSQL (41)
MySQL (2)
Data Warehouse (2)
Data Mining (3)
Network (66)
Web (78)
DHTML (4)
XHTML (1)
Javascript (1)
CSS (1)
AJAX (9)
XML (11)
Flex (1)
Silverlight (3)
Security (91)
DoS (1)
Kernel (10)
Scanning (3)
Sniffing (0)
Spoofing (4)
Overflow (28)
Web (11)
Shell (10)
Format String (14)
Window (2)
Embedded (70)
Multimedia (27)
Mobile (14)
Graphic (24)
Management (633)
Knowledge (581)
Hadoop (0)

Notice

  • 메타 블로그 사이트에 등록
  • 새해 맞이 블로그의 변화
  • 블로그 명칭 변경
  • 도메인(www.webdizen.net) 구...
  • TEXTCUBE 1.6.1로 업그레이드...

Tags

  • 패킷 분석
  • Wiki
  • 다이얼로그
  • 데이터 분산
  • 투명한 비트맵
  • 볼링
  • 파리
  • Data Warehouse
  • PHP
  • OTL
  • Thread
  • 프로세서 종류
  • 마주앙 스페셜
  • DTD
  • 까무스
  • 임학관
  • ESTsoft
  • sprintf
  • Windows Server 2008
  • Jelix

Recent Articles

  • ASCII Code의 CRLF 제거 방법.
  • Hadoop 에서 c++ API 이용시....
  • Ubuntu Linux에서 Hadoop 구....
  • 내 심장을 한껏 뛰게한 "국가....
  • 스타 스키마 데이터베이스 설....

Recent Comments

  • ■ 온라인카지노 ▶ http://L....
    asdf 11/21
  • 그리고 혹시 해외여행자보험....
    kim 11/05
  • ★★실제 바다게임장과 똑같....
    asdf 11/04
  • sbsyama.co.to← 짱5000만당....
    asdf 11/04
  • ♡KicaZ??o(???) 바카라사....
    fdsf3fass 11/03

Recent Trackbacks

  • 파일 열기/저장하기 CFileDialog.
    은마군의 나태블록 02/11
  • World IT Show 2008.
    상우 :: Oranzie's BLOG 2008
  • cvs서버 설치하기.
    3인3색 2008
  • 속속 공개되는 Google Chart....
    PHP와 Web 2.0 2007
  • 마방진을 구하는 프로그램.
    Oranzie's BLOG 3 2007

Archive

  • 2009/09 (3)
  • 2009/08 (1)
  • 2009/03 (1)
  • 2009/02 (9)
  • 2009/01 (13)

Calendar

«   2009/11   »
일 월 화 수 목 금 토
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          

Bookmarks

    • Administration
      • IIS.NET
      • NTFAQ
      • OS의 모든 것
      • 리눅스포털
    • Database
      • SQL Server Central
      • SQL Team
    • Development
      • .NET Heaven
      • ASP Alliance
      • ASP.NET 2.0
      • Bullog.net
      • C# Corner
      • C++ (C PlusPlus.com)
      • C++ Reference
      • CodeGuru
      • CodePlex
      • DebugLab
      • Dev Articles
      • Devpia
      • DotNet Junkies
      • DotNet Zone
      • Driver Online
      • GOSU.NET
      • HOONS 닷넷
      • Joinc 팀블로그
      • KOSR
      • MSDN Home Page
      • OSR Online
      • Sky.ph - 개발자 커뮤니...
      • TAEYO.NET
      • The Code Project
      • WindowsClient.net
      • 김상욱의 개발자 Side
      • 조인시 위키
    • Human Networks
      • belief21c's e-space
      • I think I can
      • Invisible Rover's Blog :D
      • Rodman®
      • ■ Feel So Good~! ■
      • 까만 나비
      • 나를 가꾸는 시간.
      • 나만의 즐거움~~!
      • 단녕
      • 상우 :: Oranzie's BLOG
    • Information Technology
      • Microsoft TechNet
      • 지디넷코리아 - 글로벌...
    • Security
      • FoundStone
      • milw0rm
      • NewOrder
      • OpenRCE
      • Phrack.org
      • Reverse Engineering b1...
      • Reverse Engineering Team
      • RootKit
      • SecurityFocus
      • SecurityXploded by Nag...
      • Wow Hacker
      • Zone-H
Textcube
Louice Studio Inc.
Powered by Textcube. Original designed by Tistory.