가끔씩 마주치게 되는 "OutOfMemoryError : bitmap size exceeds VM budget" 에러는 메모리 누수가 주요 원인입니다. 이와 관련된 링크를 모아봤습니다.

* 액티비티가 멈출 때 비트맵을 재활용(즉 GC)되게 하라

- bitmap 이미지인 경우 recycle() 호출
- onPause에서 수행하는게 좋음
- ((BitmapDrawable)imageView.getDrawable()).getBitmap().recycle();

* 이미지를 미리 줄여서 읽어들여라

- BitmapFactory.Options.inSampleSize 활용

* Activity Context에 대한 참조(reference)를 오랫동안 유지하지 말아라

- Drawable.setCallback(null) 사용
- WeakReference를 가진 static 내부 클래스
- 이미지를 static 변수로 처리하지 마라

* 외부(outer) 클래스의 상태에 의존하지 않는 내부(inner) 클래스는 static으로 선언하라
- 내부클래스는 외부 클래스 인스턴스를 크게 만들며 또한 외부클래스 객체가 필요이상으로 오래 살아있게 되어 메모리를 더 차지할 수 있음
- 외부클래스의 상태 필드에 접근하지 않는(즉 외부 객체의 상태에 의존하지 않는) 내부클래스는 static으로 선언

* Attacking memory problems on Android

목적

samba로 접속가능한 곳을 마운트 시키기

환경

우분투 8.10

방법

sudo apt-get install smbfs

sudo mount.smbfs //(주소)/(디렉토리) /(마운트디렉토리) -o user=(아이디),iocharset=(utf8/euc-kr)

 

부팅시 자동

sudo apt-get install smbfs

fstab수정

sudo vi /etc/fstab

  1. //(주소)/(디렉토리) /(마운트디렉토리)   smbfs   username=(아이디),password=(암호),iocharset=utf8 0 0

(주소)부분을 꼭 ip주소로 넣어주어야되나..

(9.04에서는 주소로 안넣어도 되는듯)

 

마운트 실행

리붓은 귀찮으니까...

sudo mount -a

출처 : http://hworsw.springnote.com/pages/2247994

'리눅스' 카테고리의 다른 글

리눅스에서 exe 실행하도록 설정하기  (0) 2011.02.23
VM 설치 방법 [bundle]  (0) 2011.02.23
우분투에서 telnet 접속 하기  (0) 2011.02.22
ibus 한글 설치하기  (0) 2011.02.21
리눅스에서 IP확인하기  (0) 2011.02.07

[Intro]

 

Android에서 사용하는 이미지는 Bitmap이라는 클래스에서 다~ 알아서 해줍니다.
그리고 이런 Bitmap Object를 쉽게 만들 수 있도록 도와주는
BitmapFactory 클래스 라는 것도 있습니다.

 

BitmapFactory는 여러가지 소스로 부터 Bitmap Object를 만들어 주는 일을 하는데,
전부 static이며 decodeXXX 라는 이름을 가진 메소드들로 이루어져 있습니다.

XXX에는 어떤 것으로 부터 decode를 하여
Bitmap Object를 만들어 낼지에 대한 말들이 들어 가겠죠.

 


[Decoding Methods]

 

BitmapFactory.decodeByteArray() 메소드는 Camera.PictureCallback 으로 부터 받은
Jpeg 사진 데이터를 가지고 Bitmap으로 만들어 줄 때 많이 사용 합니다.
Camera.PictureCallback에서 들어오는 데이터가 byte[] 형식이기 때문에
저 메소드를 사용 해야 하는 것이죠.

 

BitmapFactory.decodeFile() 메소드는 파일을 그대로 읽어 옵니다.
내부적으로는 파일 경로를 가지고 FileInputStream을 만들어서 decodeStream을 합니다.
그냥 파일 경로만 쓰면 다 해주는게 편리 한 것이죠.

 

BitmapFactory.decodeResource() 메소드는 Resource로 부터 Bitmap을 만들어 내며
BitmapFactory.decodeStream() 메소드는 InputStream으로 부터 Bitmap을 만들어 냅니다.
뭐 그냥 이름만 봐도 알 수 있는 것들이지요.

 


[OutOfMemoryError??]

 

보통 이미지 파일을 읽어서 Resizing을 해야 할 때가 있는데,
그럴때는 BitmapFactory로 읽어서 Bitmap.createScaledBitmap() 메소드를 사용하여 줄이면

간단하게 처리 할 수 있습니다.

 

그런데 BitmapFactory를 사용할 때 주의해야 할 점이 있습니다.
아래의 예를 한번 보시죠.

Bitmap src = BitmapFactory.decodeFile("/sdcard/image.jpg");
Bitmap resized = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, true);

이미지 파일로부터 Bitmap을 만든 다음에

다시 dstWidth, dstHeight 만큼 줄여서 resized 라는 Bitmap을 만들어 냈습니다.
보통이라면 저렇게 하는게 맞습니다.

 

읽어서, 줄인다.

 

그런데 만약 이미지 파일의 크기가 아주 크다면 어떻게 될까요?
지금 Dev Phone에서 카메라로 촬영하면
기본적으로 2048 x 1536 크기의 Jpeg 이미지가 촬영된 데이터로 넘어옵니다.
이것을 decode 하려면 3MB 정도의 메모리가 필요 할 텐데,

과연 어떤 모바일 디바이스에서 얼마나 처리 할 수 있을까요?

 

실제로 촬영된 Jpeg 이미지를 여러번 decoding 하다보면

아래와 같은 황당한 메세지를 발견 할 수 있습니다.

java.lang.OutOfMemoryError: bitmap size exceeds VM budget

네... OutOfMemory 입니다.
더 이상 무슨 말이 필요 하겠습니까...
메모리가 딸려서 처리를 제대로 못합니다.

 

이것이 실제로 decoding 후 메모리 해제가 제대로 되지 않아서 그런 것인지,
하더라도 어디서 Leak이 발생 하는지에 대한 정확한 원인은 알 수 없습니다.
이것은 엔지니어들이 해결해야 할 문제 겠죠...

 

하지만 메모리 에러를 피할 수 있는 방법이 있습니다.

 


[BitmapFactory.Options.inSampleSize]

 

BitmapFactory.decodeXXX 시리즈는 똑같은 메소드가 두 개씩 오버로딩 되어 있습니다.
같은 이름이지만 Signature가 다른 메소드의 차이점은
BitmapFactory.Options를 파라메터로 받느냐 안받느냐의 차이죠.

BitmapFactory.Options를 사용하게 되면 decode 할 때 여러가지 옵션을 줄 수 있습니다.


여러가지 많지만 저희가 지금 사용할 것은 inSampleSize 옵션 입니다.

 

inSampleSize 옵션은,
애초에 decode를 할 때 얼마만큼 줄여서 decoding을 할 지 정하는 옵션 입니다.

 

inSampleSize 옵션은 1보다 작은 값일때는 무조건 1로 세팅이 되며,
1보다 큰 값, N일때는 1/N 만큼 이미지를 줄여서 decoding 하게 됩니다.
즉 inSampleSize가 4라면 1/4 만큼 이미지를 줄여서 decoding 해서 Bitmap으로 만들게 되는 것이죠.

 

2의 지수만큼 비례할 때 가장 빠르다고 합니다.
2, 4, 8, 16... 정도 되겠죠?

 

그래서 만약 내가 줄이고자 하는 이미지가 1/4보다는 작고 1/8보다는 클 때,
inSampleSize 옵션에 4를 주어서 decoding 한 다음에,

Bitmap.createScaledBitmap() 메소드를 사용하여 한번 더 줄이면 됩니다.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
Bitmap src = BitmapFactory.decodeFile("/sdcard/image.jpg", options);
Bitmap resized = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, true);

당연한 이야기 이겠지만,
내가 원하고자 하는 사이즈가 딱 1/4 크기라면

Bitmap.createScaledBitmap() 메소드를 쓸 필요가 없지요.

 

inSampleSize 옵션을 잘 활용하면 메모리 부족 현상을 대략적으로 해소 할 수 있습니다.
참고로 제가 저 옵션을 사용한 뒤로는 메모리 에러를 본적이 한~번도 없답니다.

 


[Appendix]

 

inSampleSize 옵션을 사용하면

SkScaledBitmapSampler Object (Library Level) 를 생성 하게 되는데,
Object를 만들때 정해진 SampleSize 만큼 축소하여 width와 height를 정한 뒤에 만들게 됩니다.
그러니까 애초에 축소된 사이즈로 이미지를 decoding 하는 것이죠.

 


[Outro]

 

Android의 기본 어플리케이션 소스를 분석 하다보면
상당히 테크니컬한 기법들을 많이 얻을 수 있습니다.
어떻게 이런 방법으로 만들었나 싶을 정도로 매우 정교하고 복잡하게 만들어져 있지요.
참 대단한 것 같습니다.

 

아 그리고 왜 dstWidth와 dstHeight 변수 선언이 없냐고 따지시는 분들 설마 없겠죠?



출처 : http://blog.naver.com/visualc98/79874750


출처 : Ubuntu에서 안드로이드 디바이스 선택 안되는 문제 (아웃사이더, http://blog.outsider.ne.kr/448)

Ubuntu에서 이클립스로 Hello World튜토리얼 을 AVD가 아닌 실제 디바이스에서 실행하려고(듣던대로 에뮬은 정말 느리더군요) 넥서스원을 실행하니까 AVD를 이용한 에뮬레이터가 자동으로 뜨는 대신 Android Device Chooser 화면이 나왔습니다.(윈도우에서는 디바이스를 연결하면 자동으로 디바이스에서 실행이 되었었습니다. SDK를 설치할때 윈도에서는 드라이버를 설치했었죠.) 물론 안드로이드 디바이스에서 USB 디버깅을 활성화해야 합니다.

사용자 삽입 이미지

Linux에서는 별도의 드라이버가 필요없다고 나와있었는데 기기가 연결이 되기는 했지만 Serial Number가 ???로 나타나면서 인식을 제대로 못했는지 OK버튼이 활성화되지 않아서 다음단계로 넘어갈 수가 없었습니다.

adb devices 실행화면

안드로이드 SDK안에 있는 tools부분의 Android Debug Bridge를 이용해서도 기기를 확인해 볼 수 있는데 adb devices를 실행해도 no permissions라고 나오면서 기기명이 제대로 표시되지 않았습니다. 문서를 찾아보니 Setting up a Device for Development 에 해당부분에 대한 설명이 나와있었습니다.

/etc/udev/rules.d/51-android.rules
root권한으로 위의 위치에 51-android.rules 라는 파일을 생성하고 아래의 내용을 입력하고 저장합니다.(Ubuntu 9.10입니다.)

SUBSYSTEM=="usb", SYSFS{idVendor}=="0bb4", MODE="0666"
Logout을 하고 온 뒤에도 정상적으로 되지 않으면 데몬을 재실행시켜주어야 정상적으로 됩니다.

사용자 삽입 이미지

안드로이드 SDK의 Tools에서 adb kill-server를 실행키셔 서버를 종료시킨되 sudo ./adb start-server로 데몬을 시작시켜줍니다. adb devices를 다시 실행하면 시리얼키가 정상적으로 나오는 것을 볼 수 가 있습니다. 이제 이클립스에서 다시 안드로이드를 Run하면 정상적으로 Device를 선택할 수 있고 아래처럼 기기에서 실행된 Hello World를 볼 수 있습니다.


사용자 삽입 이미지

'안드로이드' 카테고리의 다른 글

OutOfMemoryError  (0) 2011.02.22
Android Bitmap Object Resizing Tip  (0) 2011.02.22
Task Stack 정보 불러오기  (0) 2011.02.15
안드로이드 Process 와 Static  (0) 2011.02.13
가시수명  (0) 2011.02.11
ssh [IP]

예> ssh 192.168.0.1

'리눅스' 카테고리의 다른 글

VM 설치 방법 [bundle]  (0) 2011.02.23
Ubuntu 에서 삼바서버로 접속하기  (0) 2011.02.22
ibus 한글 설치하기  (0) 2011.02.21
리눅스에서 IP확인하기  (0) 2011.02.07
우분투에 안드로이드 개발환경 설정하기  (0) 2011.02.01
커널에서
$ sudo apt-get install ibus-hangul
입력 후 ibus를 restart 처리.
  Activity가 실행되면 Task Stack에 보관됩니다.
  현재 화면에 보이는 Activity는 Task Stack의 가장 위쪽(top)에 위치합니다.   
  
     protected void onCreate(Bundle savedInstanceState)
     {
        ....
        
        ActivityManager am = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> task = am.getRunningTasks(1); //(숫자)는 가져올 Task의 최대갯수
 
        ComponentName topActivity = task.get(0).topActivity;
        String strPackage = topActivity.getPackageName(); //package이름.
        Log.d("Package : ", strPackage);
    }

AndroidManifest.xml에 permission을 추가해 주세요.
<uses-permission android:name="android.permission.GET_TASKS" />

저는 안드로이드 Process 에 대해 좋지 않은 추억이 몇 개 있습니다. 이전 포스트인 '안드로이드 Back & Home 그리고 종료 버튼' 에서도 한번 이야기 했지만, 어플리케이션 실행 시 특정 Process 가 시작되고, 어플리케이션이 종료되면 해당 Process 도 종료된다고 아주 평범하게 생각하고 있었고, 다르게 말하면 Activity 의 생명 주기나, Process 의 생명 주기는 비슷하다고 생각했습니다. 사소한 이 오해가 결국 큰 결과를 불러서 어플리케이션 개발 중간 쯤에 기존 어플리케이션을 왕창 수정해야 하는 아픔을 겪게 되었습니다. 


<안드로이드 Process 는 쉽게 무덤으로 가지 않는다.> 


 안드로이드 개발자 사이트에 보면 Process 의 생명주기에 대하여 다음과 같이 나와있습니다. (문제가 터지기 전에 좀 더 자세히 살펴보았다면 좋았겠지요..)

Processes and lifecycles

The Android system tries to maintain an application process for as long as possible, but eventually it will need to remove old processes when memory runs low. To determine which processes to keep and which to kill, Android places each process into an "importance hierarchy" based on the components running in it and the state of those components. Processes with the lowest importance are eliminated first, then those with the next lowest, and so on. There are five levels in the hierarchy. The following list presents them in order of importance:

 안드로이드에서는 어플리케이션의 Process 를 가능한 유지하기 위해 노력한다고 설명 되어있습니다. 이 내용을 좀더 따지고 들면, 극단적인 메모리 부족 상황이 아니면 한번 실행된 어플리케이션 Process 는 거의 죽지 않는 다고 생각할 수 있습니다. 만일 강제로 종료되는 경우라면? 비어있는 Process 라면 모를까, 무언가 일을 수행 하고 있던 Process 의 경우 안드로이드 시스템이서 해당 프로세스르 바로 다시 시작해 버립니다. 


 따라서, Process 와 연결되어 있는 각종 변수나 객체들은 개발자가 일반적으로 생각하는 것 보다 (Activity 종료 시 다 죽을거다...라던가) 죽지 않고, 아주 아주 오랫동안 살아남게 됩니다. 그 중에 하나가, 이전 포스트에서 이야기했던 Handler 를 들 수 있습니다. 이러한 원인으로 인해, 만일 개발자가 안드로이드 어플리케이션 개발을 위해서 Static 변수를 사용하고자 할 경우, 굉장히 조심할 필요가 있습니다. 그 이유는? Static 변수의 생명 주기는 해당 어플리케이션의 Process 와 같기 때문입니다. 


 간단한 예를 들어 보겠습니다. 어플리케이션 개발자가 다음과 같은 방식으로 어플리케이션을 구성했습니다. 


 1. 파일 다운로드 진척 상황을 주기적으로 감지하는 Downloader 클래스.

 2. 파일 다운로드 진척 상황의 변화를 전달 받기 위한 Listener 인터페이스.

 3. 해당 Listener 를 구현한 파일 다운로드 상황을 GUI로 표시하는 GUI Activity. 

 

  파일 다운로드를 상황을 감지하는 클래스는 현재 다운로드 중인 파일 명, 다운로드 진척사항등의 상태 정보를 가지고 있고, 해당 클래스를 참조해야 하는 Activity 가 여러 개이기 때문에, 사용상의 편의를 위하여 Singleton 형태로 구현 하기로 하였습니다. 


  Activity 는 onResume 상황에서 Singleton 클래스로 부터 다운로드 상황을 읽어와 Progress 를 표시하고(Home키로 잠시 다른 일을 수행하다가 어플리케이션 재 진입 시) 혹시 선택되어진 파일이 없을 경우(어플리케이션 최초 진입 시) 다운로할 파일을 선택하라는 Dialog 를 표시하도록 구현되었습니다.


  일반적인 경우, 개발자는 Singleton 클래스의 초기화에 대하여 신경쓸 필요가 없습니다. 최초 어플리케이션 실행 시에 자동으로 초기화 되기 때문에,  그걸로 충분하기 때문이조. 하지만 안드로이드에서는?

 

 Static 변수로 이루어진 Downloader  클래스는 Process 가 종료되지 않는 이상 해당 값을 계속 유지 합니다. 따라서 사용자가 이 어플리케이션을 최초 실행 할 때는 정상적으로 다운로드할 파일을 선택하라는 메세지가 출력되지만, 한번 어플리케이션을 종료 한 후에는 다시는 (메모리 부족으로 Process 가 종료되기 전까지) 해당 메세지를 볼 수 없습니다.


  간단한 예라서 그다지 큰 문제가 아니라고 느껴질 수도 있습니다. 하지만, 개인적인 경험으로 미루어 볼 때, 좀 더 복잡한 State 값을 관리하는데 접근의 편리함을 이유로 Static 변수나 Singleton 을 사용하게 되면, 특히 특정 Activity 가 다양한 Intent Filter 를 지원해서 여러 다른 어플리케이션에서 해당 Activity 를 마음대로 호출 할 수 있는 상황이라면, Static 변수의 값이 예측 불허 하다는 문제는 여러가지 복잡한 오류를 발생시키곤 했습니다. 


  제가 했던 실수를 반복하지 마시고, Process 가 사라지기 전에는 Activity 혹은 다른 Class 에서 선언한 Static 변수들의 값이 그대로 유지된다는 것을 기억하시고, Static Keyword 를 사용하는데 늘 조심하시길 바랍니다.

출처 : http://blog.naver.com/huewu/110081657442

가시 수명(Visible Lifetime)

액티비티의 가시수명은 onStart 의 호출과, onStop 호출의 사이를 말한다.
이들  메소드의 호출 내에서는 액티비티가 포커스를 가지지도 않고 또 부분적으로 가려질지라도 화면에 보이게 될 것이다.
액티비티는 자신의 전체 수명 동안 포그라운드와 백그라운드 사이를 이동함에 따라 수차례 가시수명을 겪을 수 있다.
극단적인 경우(리소스가 모자라다거나?), 안드로이드 런타임이 가시수명 중에 있는 액티비티를 onStop 호출 없이 종료할 수 있다.

onStart <- 가시 수명 -> onStop
액티비티 생명주기(Activity Life Cycle) 는 총 6단계를 거치며, 상황에 따라 1단계가 더 추가될 수 있다. 
6단계는 시간의 순서에 따라 생성, 시작, 활성화, 비활성화, 중비, 소멸이며, 
애플리케이션이 비활성화되었다가 활성화되는 순간 재시작을 거치게 되어 사실상 7단계로 구성된다. (재시작되지 않은채 소멸될수 있다. )

처음 실행시 OnCreate(), onStart(), onResume()순으로 빠르게 호출이 실행된다. 
이것은 액티비티가 처음으로 실행될 때이며 테스크가 백그라운드로 숨었다가 다시 실행될 때에는 onStart() 나 onResume() 메서드를 호출하고, onCreate()는 생략된다. 
onPause()는 액티비티가 비활성화되어 화면에서 사라지기 직전을 나타낸다. 
onStop() 은 액티비티가 화면상에서 완전히 사라져버릴때 호출된다. onPause() 뒤에 불리며 시스템메모리가 부족한 경우 onStop()은 호출되지 않는다. 

대부분의 경우 액티비티가 화면상에서 사라지는 순간 onPause()onStop()순으로 연속 호출되어 두 메서드 사이의 경계를 구분하기가 어려울 수 있는데, onStop()의 호출없이 onPause() 메서드만 호출되었다가 활성화되었을때 onResume()으로 돌아가는 경우는 현재 액티비티 앞에 반투명 액티비티가 존재하는 경우이다. 

* 일반적인 실행 종료 주기


#  두개의 액티비티가 존재할 경우
* 최초 실행
사용자 삽입 이미지 사용자 삽입 이미지
*  A액티비티 택스트 클릭시 B 액티비티 실행
사용자 삽입 이미지 사용자 삽입 이미지
* 취소 버튼으로 B 액티비티 취소
사용자 삽입 이미지 사용자 삽입 이미지
* A 액티비티 까지 전부 종료
사용자 삽입 이미지 사용자 삽입 이미지

* ActivityLifeCycle.java
package com.froglamb.activity_life_cycle;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;

public class ActivityLifeCycle extends Activity {
    
    private String TAG = "ActivityLifeCycle";
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Log.i(TAG, "onCreate()");    
        
        //SecondaryActivity 추가
        TextView text_view = (TextView) findViewById(R.id.text_view);
        text_view.setOnClickListener(
                new OnClickListener() {
                    public void onClick(View v) {
                        Intent intent = new Intent(ActivityLifeCycle.this, 
                                SecondActivity.class);
                        startActivity(intent);
                    }
                });
    }
    
    @Override
    public void onStart(){
        super.onStart();
        
        Log.i(TAG, "onStart()");
    }
    
    @Override
    public void onStop(){
        super.onStop();
        
        Log.i(TAG, "onStop()");
    }
    
    @Override
    public void onResume(){
        super.onResume();
        
        Log.i(TAG, "onResume()");
    }
    
    @Override
    public void onRestart(){
        super.onRestart();
        
        Log.i(TAG, "onRestart()");
    }
    
    @Override
    public void onDestroy(){
        super.onDestroy();
        
        Log.i(TAG, "onDestroy()");
    }
    
    @Override
    public void onPause(){
        super.onPause();
        
        Log.i(TAG, "onPause()");
    }
        
}

* SecondActivity.java
package com.froglamb.activity_life_cycle;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class SecondActivity extends Activity{
    private String TAG = "ActivityLifeCycle";
    
    @Override
    public void onCreate(Bundle savedInstanceBundle){
        super.onCreate(savedInstanceBundle);
        Log.i(TAG, "SecondActivity/onCreate()");
        
        TextView textview = new TextView(this);
        textview.setText("SecondActivity !!!");
        setContentView(textview);
    }
}


* Manifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.froglamb.activity_life_cycle"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".ActivityLifeCycle"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="SecondActivity" android:theme="@android:style/Theme.Translucent" />

    </application>
    <uses-sdk android:minSdkVersion="8" />

</manifest> 

* main.xml - Layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:id="@+id/text_view"
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    />
</LinearLayout>

출처 : 
http://ngio.co.kr/tc/tag/onResume()

'안드로이드' 카테고리의 다른 글

안드로이드 Process 와 Static  (0) 2011.02.13
가시수명  (0) 2011.02.11
이클립스에서 jar 파일 만들기  (0) 2011.02.10
framework-res.apk 수정방법  (0) 2011.02.10
커널 Log 확인 방법  (0) 2011.02.09

+ Recent posts