linux/system programming2011. 7. 8. 21:45
쓰레드(Thread)

 

프로세스와 쓰레드

프로세스는 운영체제가 실행시키는 하나의 프로그램이다.하나의 작업으로 비유할 수 있는데 워드 프로그램,동영상 실행 프로그램, 탐색기와 같이 여러 개의 프로세스가 동시에 수행되는 것을 multi-tasking이라 한다. Thread는 프로세스보다는 작은 단위이고, 하나의 프로세스에서 여러 개의 thread가 돌아가는 방식을 multi-thread 방식이라 한다.

 

@쓰레드의 생성방법

Thread를 생성시키는 방법으로는 java.lang.Thread 클래스를 extends 사용해 상속받는 방법과 

java.lang.Runnable 인터페이스를 implements하는 방법이 있다.Thread 클래스가 Runnable 인터페이스를 implements하고 있기 때문에, Thread를 상속받는 방법보다는 Runnable 인터페이스를 implements하고 있기 때문에, Thread를 상속받는 방법보다는 Runnable 인터페이스를 implements하는 방법을 많이 사용한다.

 

@쓰레드의 생명 주기

Thread를 start()했다고 해서 바로 실행이 되는 것이 아니고, Runnable 상태에 있다가 우선순위에 따라서 Running 상태로 넘어가 run()이 수행된다.run()의 모든 수행이 종료하게 되면, Dead 상태로써 프로그램이 종료된다.

 

@쓰레드의 우선 순위

Thread가 Runnable 상태에서 Running 상태로 넘어가기 위해서 서로간에 가준이 되는 것이 바로 우선 순위다. 최대 우선 순위는 10이고, 최소 우선 순위는 0이 된다.main()에서 생성된 thread는 대부분 우선 순위 5를 갖게 된다.

 

@쓰레드의 제어

Running 상태에서 CPU를 독점해 사용하고 있는 thread를 특정 메소드를 호출해 Block상태로 보내는 방법에는 일정 시간동안 Block 시킬 수 있는 sleep(), 양보하는 yield(), 현재 실행 중인 thread를 Block 시키는 join()등이 있다.

 

@쓰레드의 동기화

공유 자원을 동시에 thread들이 접근하려할 때 , 공유 자원을 보호하고, 데이터 무결성을 보장하는 방법으로써, synchronized 키워드를 사용해 공유 데이터를 접근하는 메소드에 동기화를 적용시킬 수 있다. 메소드 전체에 동기화를 부여할 수도 있고 특정 영역만 동기화를 적용시킬 수도 있다.

 

@wait() & notify()

동기화만으로 해결이 안되는 thread의 문제점인 데이터가 없는데도 데이터를 요구할 때를 대비해서, 데이터가 없을 떄는 기다리고, 데이터가 저장되면 알려주겠다는 메커니즘을 구현하는 메소드가 wait()와 notify()이다. 
[출처] 쓰레드(Thread)란?|작성자 뚜루미



프로그램이 실행되기 위해서는 메모리에 로딩 되어야 합니다. 
즉, 프로그램 크기만큼 메모리를 차지하게 되는 것이죠.
 
대개 메모리에서 수행되는 프로그램을 프로세스라고 부릅니다.
 
최근에는 많은 사용자에게 서비스하는 프로그램이 늘어나는 추세입니다.
 
웹 프로그램이 전형적인 예라고 볼 수 있겠죠.
 
따라서, 많은 사용자를 위해서는 메모리 등의 시스템 자원 소모를 줄이는
 
효율적인 프로그래밍 방식을 고려해야 합니다.
 
이런 경우에 사용하는 것이 쓰레드(Thread)입니다.
 
쓰레드는 시스템 자원을 공유하는 방식입니다. 쉽게 말해서
 
동일한 데이터를 가지고 작업하는 프로그램이 여러번 실행되어야 할 때
 
매번 프로세스를 생성하여 메모리를 차지하지 않고
 
동일한 메모리 공간을 공유하는 방식으로 시스템 자원을 아껴쓰고
 
CPU를 보다 효율적으로 사용하게 되는 것이죠.
 
쉽게 설명한다고 해놓고, 그다지 쉽게 되지는 않았네요..
 
Posted by babuzzzy
linux/system programming2011. 6. 27. 21:59
ㅇ- 에러코드를 담고 있는 error 변수
- perror() 함수를 이용한 오류 메시지 출력
- strerror() 함수를 이용한 오류 메시지 출력
 
개요

- 리눅스에서 시스템콜 및 라이브러리 함수를 수행하다가 오류가 발생되면
  사용자의 프로그램으로 오류 결과를 넘겨준다
 
- 사용자 프로그램에서는 함수의 리턴 값을 조회함으로써 오류의 발생 여부를 확인할수 있다.
 
- 일반적으로 오류 시 리턴 값은 다음과 같다.
     시스템 콜 오류 시 리턴 값 : -1
     라이브러리 함수 오류 시 리턴 값 : NULL

- 위의 리턴 값과 함께 오류를 확인할 수 있는 방법을 좀 더 자세히 살펴보자
 
 에러 코드를 담고 있는 error 변수
시스템 콜 오류 시 리턴 값은 보통 -1 이 리턴 된다고 했다.
그럼, 시스템 콜이 오류가 나는 원인은 한가지 일까 여러가지 일까
시스템 콜의 오류 발생 시에 오류의 원인을 구체적으로 파악할수 있는 방법이 없을까?

시스템 콜은 오류가 나는 경우에 구체적인 원인을 나타내는 상수 값을 errno 변수에 저장한다
그러므로 시스템 콜 오류시에는 errno 변수의 내용을 살펴봄으로써 오류의 원인을 구체적으로 파악할 수
있게 된다

errno 변수에 설정되는 값은 /usr/include/asm/errno.h 헤더 파일에 정의되어 있다.
실제로는 /usr/include/asm-generic/errno-base.h 을 포함하고 있기 때문에 이파일을 열어야한다.

그럼 에러노 변수는 어디에 있을까?
==>$ whereis errno.h 명령어로 알수 있다.
 사용자 어플리케이션에서 errno변수를 사용하기 위해서는  
#include<errno.h> 를 반드시 포함해야한다 


 sys_nerr  , sys_errlist

 
errno와 연관된 추가적인 변수들도 stdio.h 파일에 선언됨

sys_nerr, sys_errlist 사용 예


컴파일후 결과

예제와 출력 결과를 보면
sys_nerr는 포함하고 있는 에러메시지의 개수를 나타내고
sys_errlist 는 포인터 배열로 에러메시지를 문자열로 저장하고 있는걸 알수있다


perror() 함수를 이용한 오류 메시지 출력 

 오류 메시지를 출력할 때 가장 널리 사용되는 함수는 perror()로 그 형식은 다음과 같다.
 
#include <stdio.h>
void perror(const char *s);
이때 매개 변수 s 에는 오류 메시지 앞에 덧붙이고 싶은 문자열을 전달하면 된다.

myperror.c 오류 메시지 출력 예


실행을 하면 ./myperror
open : No such file or directory perror에서 open을 썻기때문에 open 이라고 출력
 
strerror() 함수를 이용한 오류 메시지 출력

오류 메시지를 출력할때 널리 사용하는 함수는 strerror()로 그 형식은 다음과 같다
#include <string.h>

char *strerror(int errnum);
errnum 에 맞는 오류 메시지 문자열을 리턴한다.
만일 잘못된 errnum을 인자로 넘겨주면 " Unknown error nnn " 문자열을 리턴한다


실행 예


결론

이제까지 살펴본대로 각종 시스템 콜이나 라이브러리 함수의 오류 처리가 필요한 경우
오류 메시지 출력은 strerror() 또는 perror() 함수를 이용하고

오류의 원인에 따라 프로그램의 제어가 필요할 때는 메뉴얼(man)에 정의된 대로 error 변수의 값에따라 
처리를 한다

 
Posted by babuzzzy
linux/system programming2011. 6. 26. 04:15
리눅스에서 공유라이브러리를 로드하고 참조 함수를 확인하는 프로그램을 동적로더(ld.so) 라고한다.
이 로더는 /etc/ld.so.conf
에 설정된 경로를 이용하여 공유 라이브러리를 검색한다.

이 구성 파일에 경로를 추가한뒤 ldconfig 명령을 이용하여 캐쉬를 업데이트한다.
 

1. 공유라이브러리 예
 


1. myshared.c 파일을 만든다
2. $ gcc -c -fPIC myshared.c   ====> 공유라이브러리 함수로 컴파일 할때는
독립한 코드로 만들기 위해 -fPIC 옵션을 이용한다 
 

3. 공유 라이브러리 파일을 만들기위해 ar을 이용하는 것이 아니라 
gcc의 -shared 옵션을 이용하여 생성한다.


$ gcc -shared -o libmyshared.so myshared.o
그러면 libmyshared.so 파일 즉 공유 라이브러리 파일일 만들어진것을 알수 있다.

공유라이브러리 호출


1. mymain2.c 생성

 2.$ gcc mymain2.c -o mymain2 -lmyshared -L. 
===> 명령어로 컴파일을한다
3. ldd mymain2 실행한다
라이브 파일을 찾을수 없다고 나온다


해결책은?
 
방법 1 etc/ld.so.conf 파일에 경로를 설정한후 ldconfig로 캐쉬를 갱신하면된다
우선 etc/ld.so.conf 파일을 열어보자

ld.so.conf폴더안에 *.conf 란 파일명으로 만들면 포함된다는뜻이다

즉 ld.so.conf 안에 원하는파일 명 나는 mymain2.conf 를 만들고
libmyshared.so 파일의 전체경로를 적어둔다

그런다음 ldconfig 로 캐쉬를 갱신한다

방법2 
LD_LIBRARY_PATH 환경변수를 이용하여 공유라이브러리를 설정할수 있다.

1. 현재 설정되어 있는 동적 라이브러리 경로를 조회
$ echo $LD_LIBRARY_PATH
2. 동적라이브러리 경로 추가



- 리눅스는 시스템 부팅시에 /etc/profile을 실행하므로 이곳에
LD_LIBRARY_PATH 변수값을 지정하면 시스템 부팅시에도 
공유 라이브러리의 경로가 자동으로 설정된다.

최종 출력

 잘 출력 되는걸 알수 있다

Posted by babuzzzy
linux/system programming2011. 6. 26. 03:25
1. 정적 라이브러리 생성

1. mystatic.c 를 만든다
2. $ gcc -c mystatic.c  로 오브젝트 코드를 생성한다  ==> mystatic.o 생성
3. ar rv libmystatic.a mystatic.o       ===>mystatic.c 로 라이브러리 코드를 생성한다



2. 정적 라이브러리 호출 

1. mymain1.c 생성  

2. 컴파일 하는법이 조금 다르다
gcc mymain1.c -o mymain1 -lmystatic -L.

이렇게 컴파일을 해야하는데 mystatic앞에 l은 lib에서 앞에만 따온것이다 무조건 l만 붙여야한다 공식이다
그리고 뒤에 -L. 이것은 libmystatic.a  라이브러리 파일이 현재같은 폴더에 있기때문에 여기를 검색하라
이런 명령이다

기본적으로 컴파일러는 /usr/lib 라이브러리 함수를 찾아 링크를 하기때문이다

그렴 최종 출력은
 


잘 출력 되는것을 알수 있다.


Posted by babuzzzy
linux/system programming2011. 6. 26. 02:50
리눅스에는 실행 프로그램의 시스템 콜을 추적할 수 있는 strace와 라이브러리 함수를 추적할 수 있는 
ltrace 도구가 제공된다

다음은 앞서 gdb에서 이용한 mydebug 실행 파일을 strace 와 ltrace로 확인해 본 내용이다.
$ strace ./mydebug 2>&1 | less

 


참 복잡하다
시스템 콜를 추적할수 있다.

이번엔 ltrace를 사용해보자

$ ltrace ./mydebug


장점 :
strace와 ltrace를 이용하면 실행 프로그램의 실행 과정을 추적해 볼수 있어 
전문적인 디버깅이 가능하다

프로그램의 실행이 이상하게 되거나 예상되로 진행이 되지 않는 경우 사용하면 편리하다.
 
자 그럼 대충 큰틀을 정리하자

- 리눅스에는 커널의 기능을 사용자들이 이용할 수 있도록 시스템 콜이 제공된다.
- 시스템 콜은 커널 모드에서 수행되고, 라이브러리 함수는 유저모드에서 수행한다.
- 리눅스의 gcc 컴파일러를 이용하여 정적 라이브러리와 공유 라이브러리를 생성할수 있다.
-리눅스에서 제공되는 디버깅 도구로는 gdb, ddd, ldd,strace, ltrace 등이 있다.

 
 

Posted by babuzzzy
linux/system programming2011. 6. 26. 02:38
개발자는 컴파일 과정을 통해 만든 실행 파일을 이용하여 실행을 하면서 문제점을 수정하는 디버깅 과정을
반드시 거치게 된다.

리눅스에는 다음과 같이 프로그램을 디버깅 할 수 있는 도구가 있다.
각 도구마다 디버깅 요소가 다르므로, 필요에 따라 선택하여 사용하는 것이 좋다


각 도구의 자세한 사용방법은 man 페이지를 통해 확인할수 있다
일단 간단히 알아보자

gdb 
gdb는 GNU에서 제공하는 공개되어 있는 디버거

이 도구를 이용하려면 컴파일 시 -g 옵션과 함께 컴파일 되어야한다.
gdb를 이용하는 방법은 다음과 같다

$ gdb 실행파일명 또는 gdb -P 실행중인 PID

제공되는 명령은 다음과같다


 디버깅을 실제로 해보자
 

코드를 작성하고
gcc mydebug.c -g -o mydebug 로 컴파일을 한다
중요한건 -g 옵션을 줘야 한다는것이다.

 $ gdb mydebug 명령어로 gdb를 실행한다
 
위에 명령어를 사용하면서 실행법을 익혀본다 

Posted by babuzzzy
linux/system programming2011. 6. 22. 23:07
시스템 콜 및 라이브러리 함수를 이용하여 소스 프로그램을 작성하면 컴파일 과정을 통해
실행 파일을 만들어야 한다

또한 이 실행 코드를 재사용하기 위해서 라이브러리를 생성하여 사용하기도 한다.

이 절에서는 리눅스에서 컴파일 하는 방법과 과정을 소개하고
정적 혹은 공유 라이브러리를 작성하는 방법 및 해당 라이브러리를 사용하기 위해서
설정해야 할 사항에 대하여 살펴본다.

 컴파일
컴파일 방법은 gcc 소스파일명 -o 실행파일명
 

 
gcc -v --save-temps sample.c -o sample1 
이 명령어는 -v옵션은 컴파일 과정을 화면으로 출력, --save-temps 옵션은 컴파일 과정에서 발생되는
중간 파일을 지우지 않고 저장
이명령어를 실행하면  실행파일 sample1말고도  sample.i    sample.o    sample.s  파일이 더 생성된다
 
cc1 - asm코드를 생성(*.s)
as - 어셈블러, object를 생성(*.o)
collect2 - 링커, 실행파일을 생성


정적 라이브러리와 공유 라이브러리

 리눅스에서 프로그램을 개발하다 보면 여러 사람들이 함께 개발하는 경우가 종종 있다.
그 때 같은 기능을 여러 사람이 공동으로 사용하게 되는 경우가 있는데,

그러한 기능을 각각의 소스 프로그램에 함수로 넣어 사용하는 방법보다는 라이브러리를 만들어 함께
사용하면
- 사용하기가 훨씬 편리
- 같은 소스코드를 중복 기술하지 않아 효율적인 프로그램 작성 가능
- 다른 프로젝트에도 손쉽게 활용 가능
 
라이브러리의 개요 
기본적으로 제공되는 표준 라이브러리는 /usr/lib에 존재
c 컴파일러는 프로그램 컴파일 시 기본적으로 이 디렉토리(/usr/lib)에서 라이브러리 함수를 찾아 링크 수행

gcc 의 -L 과 -l 옵션을 이용하면 다른 디렉토리에 있는 라이브러리도 이용할수 있음 

라이브러리의 이름은 lib로 시작되며 라이브러리의 의미를 나타내는 이름 다음에 파일의 확장자 .a .so로 구성된다

 예를들면 libm.a libm.so

여기서 m은 math 즉 수학관련 라이브러리를 의미한다 이 라이브러리를 이용하여 컴파일 할때에는
다음과 같이 명령을 수행한다
gcc sample.c -o -lm(소문자 엘 과 엠)
 

라이브러리의 종류 
라이브러리에는 정적(static)라이브러리와 공유(shared)라이브러리 두 종류가 있다.
그 차이점과 생성 방법을 알아보도록 하자

정적 라이브러리
정적 라이브러리 란 미리 만들어 놓은 라이브러리 함수의 오브젝트 코드를
ar 도구를 이용하여 아카이브(모아놓은) 해놓은 것

컴파일 과정 중 링크 단계에서 프로그램의 오브젝트 코드와 지정한 라이브러리의 오브젝트코드를
결합하여 하나의 실행


가능한 파일을 생성한다 
 
이 정적 라이브러리 파일은 확장자로 .a를 가지며 이 라이브러리 함수를 이용할 때에는 관련된
헤더파일을 include하여 사용 

정적라이브러리 생성방법
 


1) mystatic 파일의 오브젝트 코드를 생성한다
 gcc -c mystatic.c

2) ar명령을 이용하여 라이브러리 파일을 생성한다
ar rv libmystatic.a mystatic.o

위 명령을 치면 libmystatic.a 파일이 생긴다

정적 라이브러리 함수 호출예

정적라이브러리 함수를 호출하면서 컴파일을한다
 gcc mymain1.c -o mymain1 -lmystatic -L.
여기서  -lmystatic -L. 는 현재 파일에 있는 라이브러리를 쓴다는 뜻이다
위에서 -L 과 -l (소문자 엘) 옵션을 이용하면 다른 디렉토리에 있는 라이브러리도 이용할 수 있음
 

공유라이브러리 
정적 라이브러리를 이용하여 실행 파일을 생성하면 실행 파일에  라이브러리 함수의 코드가 포함된다

따라서 이 라이브러리 함수를 여러 프로그램에서 사용하면 각각의 실행 파일에 라이브러리 함수의
코드가 포함된다

 따라서 이 라이브러리 함수를 여러 프로그램에서 사용하면 각각의 실행 파일에 라이브러리 함수의 코드가 
 포함되고 동일한 함수의 코드가 메모리 여러 군대에 존재하게 된다.

 이는 메모리나 하드 디스크 공간이 그 만큼 낭비된다는 의미이다.
 
이와 같은 정적 라이브러리 함수의 단점을 극복한 것이 공유 라이브러리이다

프로그램이 공유 라이브러리를 사용하면 실행 파일에 라이브러리 함수 코드가 포함되는 것이 아니라, 
실행 시 사용 가능한 공유코드를 참조하는 방식으로 링크된다

따라서 이 공유 라이브러리 함수를 이용하는 프로그램이 여러개 일지라도 하나의 라이브러리를 함께
이용하므로, 메모리나 하드디스크를 효과적으로 사용할 수 있다.

리눅스에서 공유 라이브러리를 로드하고 참조 함수를 확인하는 프로그램을 동적 로더(ld.so)라고 한다
이 로더는 /etc/ld.so.conf에 설정된 경로를 이용하여 공유 라이브러리를 검색한다.

이 구성 파일에 경로를 추가한 후 ldconfig명령을 이용하면 캐쉬를 업데이트 한다.



 

Posted by babuzzzy
linux/system programming2011. 6. 22. 22:41
시스템 콜 목록

 
라이브러리 함수 목록


 
Posted by babuzzzy
linux/system programming2011. 6. 22. 22:36
프로그램을 작성하다보면 개발자가 직접 함수를 만드는 경우도 있지만
이미 만들어진 함수를 이용하는 경우가 많다.

이와 같은 함수의 종류에는 라이브러리 함수와 시스템 콜 이라고 하는 함수가 있다.

이번 에는 라이브러리 함수와 시스템 콜의 차이점을 알아보고, 자주 사용되는 각 함수들의 목록을 확인해보자

 라이브러리 함수와 시스템 콜의 차이

일반적으로 시스템 콜이란 커널의 자원을 사용자가 사용할 수 있도록 만들어 놓은 함수들을 말한다
ex) -open 시스템 콜 호출
     - 시스템 콜을 호출하면 커널 모드로 전환하여 실행됨

그에 비해 라이브러리 함수란 사용자들이 많이 사용할 것같은 기능들을 미리 함수로 만들어 놓은 것
- 문자열 처리, 표준 입출력, 수학 관련 공식 등
- 라이브러리 함수를 호출하여도 사용자 프로그램은 사용자 모드에서 실행됨
시스템 콜 예문


 time() 함수는 시스템의 시간을 확인할 때 사용하는 시스템 콜이다. 
이 함수에 대한 자세한 사용법은 온라인 메뉴얼 명령 man의 시스템 콜에 대한 섹션 2에서 확인할 수 있다.
 
man 2 time 명령을 수행하면 time() 시스템 콜에 대한 문법과 함께 파라미터, 리턴값 등에 대한 자세한 정보를 알 수 있다

리눅스에서 프로그램을 잘 작성하려면 온라인 메뉴얼을 자주 참조해야한다.
 


이 코드를 컴파일 해서 실행하면 
Segmentation fault 오류가 난다 이유는
여기서 cur_time 변수는 포인터변수이다
time()은  시스템 콜이다. 호출되면 커널 모드로 전환하고 커널에서 시스템의 시간 정보를 읽어 사용자 프로그램에
그 값을 전달해 준다

그런데 커널에서 넘겨준 시간을 사용자 프로그램에 보관을 하려고 보니 메모리 공간이 없다
왜냐하면 포인터 변수만 선언하고 메모리 공간을 할당하지 않았기 때문이다.

따라서 세그멘테이션 오류가 발생한다.


 프로그램을 실행해보면 올바르게 수행된다
여기에서 time()과 같은 시스템 콜은 사용자 프로그램에서 커널의 메모리 공간을 직접 이용할 수 없으므로 사용자 프로그램에서 메모리 공간을 할당해야 한다는 점을 기억하자 

이제는 라이브러리 함수의 예를 보자

 이것을 컴파일해서 실행해보면 current time 이 문자열로 출력이 된다

이 프로그램은 ctime()이라는 라이브 러리 함수를 이용한 예이다
리눅스에서 라이브러리 함수에 대핸 온라인 메뉴얼은 섹션 3에서 확인 할수 있다
man 3 ctime
 
ctime()라이브러리 함수는 time() 시스템 콜에 의해 확인된 초단위의 시간정보를 사람들이
쉽게 확인 할수 있는 문자 형식으로 변환시켜주는 함수로 처리결과를 char* 로 반환한다

즉 주소 형태로 반환한다. 따라서 포인터 변수 등을 이용하여 결과를 처리해야한다

소스 코드 8~11 을 보면 time_str이라는 포인터 변수가 선언되고 특별한 메모리 할당이 없이도 ctime()
함수의 리턴값을 정상적으로 처리하고 있다

 함수 설명 
ctime  
ctime() 함수는 라이브러리 함수로 사용자 모드에서 처리되는 함수이다.
이 함수를 호출하면 사용자 공간에 메모리 공간을 할당하고 문자 형식으로 변환된 시간 정보를 저장한다

따라서 사용자 프로그램은 자신의 공간에 할당된 그 부분을 포인터 변수를 통해 참조할 수 있게 된다
그러므로 라이브러리 함수를 이용하는 경우에는 사용자 프로그램에서 별도의 공간을 만들지 않아도
함수의 처리 결과를 이용할 수 있다.

시스템 콜 vs 라이브러리 함수



Posted by babuzzzy
linux/system programming2011. 6. 22. 20:52
리눅스에서의 시스템프로그램 이란?

리눅스에서 시스템 프로그래밍을 한다는 것은  리눅스 시스템 즉 커널이 제공하는 기능을 이용한다는 의미이다

따라서 리눅스에서 시스템 프로그래밍을 잘하려면 커널의 구조를 이해할 필요가 있다

 우선 운영체제에 대해 알아보자
 -운영체제란 
 컴퓨터를 작동시키고 자원을 관리하여 사용자의 응용프로그램이 효율적으로 실행될수 있는 환경을 제공하는 자원관리 프로그램이다 

운영체제가 관리해야하는 자원은  물리적자원과 ,추상적인 자원으로 나누어진다

 이와 같이 운용체제의 자원을 이용하는 응용프로그램을 작성하려면 운용체제의 구조를 잘 이해하고 지원되는 기능을 효율적으로 이용할 줄 알아야한다

- 리눅스 운용체제의 구조

 


리눅스에서 사용자가 명령이나 프로그램 등을 실행하면 리눅스 커널에서 제공되는 시스템 콜이나 라이브러리 함수등
일종의 API 들을 통해 커널의 기능을 수행하도록 설계되어 있다.

따라서 이 시스템 콜과 라이브러리의 이용방법을 잘 숙지하면 어떤 사용자든 리눅스 커널이 제공하는 기능을 이용할수 있다



Posted by babuzzzy