크리에이티브 커먼즈 라이센스

홈페이지HOME

PHP

JavaScript

HTML

CSS

드롭다운메뉴

제로보드4

도   움   말

백지 건강강좌/자가임상체험

물박사(자가면역질환...)^미러

전동명(천연물질사전)^미러

竹田 장승옥(블로그)^글목록

계산환산(계산기)^ 단위변환

관주성경 TTS 일반파일 TTS

바이블로 Bible_ro 다운로드

다운로드>폰에 복사
>파일선택...설치됩니다.

[도움말 보기] 바로가기

개선 요구, 충고는
아래 관리자 연락
전화/메일 부탁 드립니다.
최대한 빨리 처리하고
업데이트 올리겠습니다.

asm_11.

(PUSH, POP)



프로그래밍 언어도 하나의 언어입니다.

언어란 하나의 약속입니다.

우리가 약속을 모를 때 우리는 전혀 무능한 사람입니다.

그러나 일단 약속을 알고 나면 우리는 유능한 사람이 됩니다.

우리는 외국 사람들과 대화하기 위하여, 그 외국 사람들 사이의 약속인 해당 외국어를

열심히 배웁니다.

프로그래밍 언어를 배우는 것은 확실히 그보다 쉽습니다.






제 11 장  PUSH, POP

우리는 앞 장에서 8088의 스택에 관해서 비교적 상세히 살펴 보았다.

그 지식은 우리에게 많은 도움이 될 것이다.

이제 이 장에서는 역시 스택을 이용하는 명령으로서, 구조화(모듈화) 프로그래밍을

위해 반드시 알아 두어야 하는 push 명령과 pop 명령에 관해서 알아 보고, 나아가

다양한 16진수 입력 프로그램도 짜 보기로 한다.


제 1 절  스택과 PUSH, POP

프로지서를 호출하여 실행하고 나서 그 프로시저를 호출했던 주 프로시저로 복귀하기

위하여 복귀 주소를 저장하는 call, ret 명령은 스택을 이용하였다.

마찬가지로 이제 배우려는 push, pop 명령도 역시 스택을 이용한다.

push는 밀어 낸다는 말 뜻 그대로 어떤 레지스터의 값을 일시 스택에 저장해 두는

명령어이며, pop은 push 명령으로 스택에 저장해 두었던 값을 복구해 준다.


1.  레지스터 값의 저장과 복구

그러면 무엇 때문에 레지스터의 값을 저장하고 복구할 필요가 있을까?

단지 하나의 프로시저로 구성되는 프로그램에서는 그 필요가 없을지 모른다.

그러나 여러 개의 프로시저가 서로 호출하고 복귀하는 모듈화 프로그램을 짠다면

한정된 레지스터에 다른 프로시저가 저장해 둔 값을 손상시키지 않고 같은 레지스터를

이용하기 위하여 먼저 그 레지스터의 기존 값을 안전한 곳에 저장해 두었다가 나중에

복구할 필요가 생기는 것이다.

값을 저장해 두기만 하고 복구하는 것을 잊어 버린다면 문제이다.

원래의 값을 복구하는 일만 잊어버리지 않는다면, 우리는 레지스터 값을 저장하고

복구하는 방법으로 현재 프로시저를 호출한 프로그램에서 사용되는 변수(레지스터

값)에는 아무 영향을 주지 않고 마음대로 원하는 레지스터의 값을 변경하여 유용한

프로시저를 만들 수 있을 것이다.

이와 같이 어떤 프로시저 내에서만 유효한 변수를 지역 변수라고 부른다.






2.  저장과 복구의 실제, lifo

레지스터의 값을 손상시키지 않기 위하여 저장/복구할 필요가 있음을 알았다.

그러면 실제로 어떤 경우에 어떻게 저장하고 복구하면 좋을 것인가?

프로그램 전체를 시작하고 끝내는 주 프로시저 내에서는 저장과 복구의 필요성이

별로 없을지 모른다.

그러나, 현재 프로시저가 주 프로시저에 의해 호출되거나 또는 다른 프로시저에

의해 호출되었다면, 현재 프로시저의 시작 부분에서는 현재 프로시저에서 어떤 값을

저장하기 위하여 사용하고자 하는 모든 레지스터의 값을 저장해 둔다.

그리고 현재 프로시저를 끝내기 직전에 현재 프로시저의 시작 부분에서 저장했던

모든 레지스터의 값을 복구해 주면 되는 것이다.

물론, 복귀 주소와 마찬가지로 레지스터 값도 스택에 저장되는 것이므로 나중에

저장한 것을 먼저 복구하는 LIFO(last in first out) 구조를 따라야 된다.

다시 말해서 cx, dx 차례로 저장했다면 복구할 때는 dx, cx 차례로 하면 된다.






3.  지역 변수와 전역 변수

필자는 당장 긴요하지 않은 어려운 용어를 나열하여 설명하는 것은 싫어한다.

어려운 느낌만 줄 우려가 있기 때문이다.

그러나 배우는 입장에서는 과감한 시각의 전환이 필요하다.

처음 보는 얼굴이 생소하듯이, 처음 배우는 지식이 낯선 것은 너무나 당연하다.

그러나 깊이는 몰라도 몇 번 얼굴을 대하다 보면 친근감이 생긴다.

컴퓨터나 프로그래밍에 관해서도 같은 법이다.

지금도 필자는 메모리라는 말만 나오면 골치가 아프지만, 필요에 따라서 어떻게

다루다 보니 그럭저럭 사용에 불편을 느끼지 않고 사용하고 있다.

사실 컴퓨터나 ㅍ로그래밍을 배우면서 우리가 반드시 익혀야 될 용어나 지식으로

말하자면, 그 양에 있어서나 외우기 어려운 정도에 있어서 중학교 영어를 배우는

것보다 쉬우면 쉬웠지 더 어렵다고 생각되지는 않는다.

잔소리는 이 정도로 줄이자.

우리는 앞에서 8088의 레지스터가 변수와 동일한 것은 아니지만, 변수와 마찬가지로

어떤 값을 담아 두거나 담겨 있는 값을 변경할 수 있다고 배웠다.

지금 우리가 배우고 있는 push, pop 명령을 사용하면 현재 프로시저에서 레지스터에

담아 사용한 값은 pop 명령과 함께 영원히 사라진다.

따라서 현재 프로시저에서 사용한 그 레지스터 값은 현재 프로시저 내에서만 유효하고

다른(외부) 프로시저에는 전혀 영향을 미치지 않는다.

이와 같이 특정 프로시저 내에서만 사용되고 그 프로시저를 호출한 프로그램(주

프로시저)에는 아무 영향을 주지 않는 변수를 지역 변수(local variable)라 부른다.

그와 달리, 예컨대 주 프로시저에서 저장한 레지스터 값을 호출된 모든 프로시저에서도

동일하게 사용한다면 그 변수는 전역 변수가 될 것이다.


4.  push/pop과 call/ret의 차이

push/pop이나 call/ret이나 워드 값을 스택에 저장해 두었다가 복구한다는 면에서는

기능이 전혀 동일한 것으로 생각된다.

그러나, 두 가지 명령 조합에는 실질적인 차이가 있다.

먼저 push/pop이 메모리 번지를 저장하고 복구하는 반면에, call/ret은 레지스터의

값을 저장하고 복구한다는 차이가 있음을 알 수 있다.

또 한 가지 call/ret은 우리가 저장/복구할 내용을 지정해 줄 필요가 없을 뿐더러

임의로 지정할 수도 없는 반면, push/pop 명령에는 저장/복구할 레지스터의 이름을

우리가 명시해 주어야 된다는 점도 다를 것이다.






제 2 절  16진수 입력, 아스키 문자를 출력하는 프로그램

지금까지 우리는 16진수의 1 자리 또는 2 자리를 출력하는 프로그램을 만들기도

했고, 16진수의 1 자리 또는 2 자리를 입력 받는 프로그램도 만들어 보았다.

입력을 받아서 출력하는 프로그램은 만들어 보지 않았던 것이다.

그 외에도 기존에 우리가 만든 프로그램은 0에서 9, 또는 대문자 A에서 F 사이에

들어 있는 문자가 입력되지 않으면 비정상적으로 반응하는 한계가 있었다.

이제 우리는 2 자리의 16진수를 입력 받아 그 코드값에 대응하는 아스키 문자를

출력해 주는 프로그램을 만들되, 유효하지 않은 문자가 입력되면 다른 유효한 문자의

입력을 기다릴 수 있는 기능도 포함시키도록 하자.

그렇게 하면 쓸만한 프로그램 하나가 생길 것이다.


1.  프로그램 구상

지금까지 우리는 어떤 프로그램을 무조건 베껴서 만들고, 그것을 실행해 보면서

내용을 이해하는 방법을 사용해 왔다.

그러나 쓸만한 프로그램을 만들기 위해서는, 먼저 구상을 할 필요가 있다.

제대로 된 프로그램이라면 대개는 여러 개의 프로시저가 존재하며, 따라서 상당히

복잡한 구조로 만들어질 가능성이 많을 것이다.

그럴수록 사전에 충분한 구상을 하여 프로그램을 짜야만, 가능한 간결하고 에러에

대한 대책도 확실한 프로그램을 만들 수 있게 된다.

지금 우리가 만들려고 하는 프로그램에는 다음과 같은 과정이 포함되어야 한다.

        
  1. 걸름 장치를 마련한다.  만약, 16진수의 숫자인 0에서 9까지 및 A,

        B, C, D, E, F를 제외한 다른 문자가 입력되면 다시 입력을 받아 들이도록 한다.

         또한 정상적인 입력은 화면에 표시하되, 재입력이 필요한 경우에는 화면에

        아무 표시도 하지 않도록 int 21h 함수의 08h 및 02h 명령을 사용한다.
  2.     
  3. 유효한 문자가 입력되면 그 문자를 화면에 출력한 후, 그 문자의 코드값에서     30 또는 37을 빼고 입력된 문자와 동일한 숫자를 재저장한다.
  4.     
  5. 위와 같은 방법으로 먼저 가공된 첫 자리 숫자는 왼쪽으로 니블(4 비트)     이동시키고, 뒤에 가공된 둘째 자리 숫자를 합친다.
  6.     
  7. 가공된 2 자리 16진수를 코드값으로 삼아 그에 대응하는 문자를 출력한다.





1) 걸름 장치

걸름 장치로는 다음의 3 그룹 문자들을 무시하고 재입력을 기다려야 된다.

(1) 코드값 0h 내지 29h의

특수 문자

(2) 코드값 47h 이상의 문자

(3) 코드값 3Ah 내지 40h의

각종 기호


2) 코드값 변환

유효 문자를 출력한 후 코드값을 다음과 같이 변환해야 된다.

(1) 0 내지 9 - 코드값에서

30을 빼고 레지스터에 재저장

(2) A 내지 F - 코드값에서

37을 빼고 레지스터에 재저장


3) 두 자리 합침

남은 절차는 첫 자리 숫자와 둘째 자리 숫자를 합치는 일 뿐이다.

물론 합치기 이전에 두 숫자는 서로 다른 레지스터에 저장되어 있어야 된다.






2.  프로그램(주 프로시저) 작성

위와 같은 구상을 어느 정도 이해는 하겠지만, 실제로 프로그램을 만들려고 하면

그리 간단한 일은 아니다.

걸름 장치는 다소 여러 줄의 명령을 사용하여 복잡해질 것이고, 또한 두 차례의

동일한 작업이 반복될 것이므로 별도 프로시저로 구성하여 호출 실행하기로 하자.


1) 첫 자리 저장

일단 주 프로시저에서는 보조 프로시저를 호출하고 보조 프로시저에서 걸름 장치를

거치고 변환된 숫자가 넘어 오면 그 처음 숫자를 왼쪽으로 니블 이동시켜 두었다가

나중에 넘어 오는 숫자와 합치는 일만 하도록 만들어 보자.


     - a 100 <Enter>

     ????:0100 call 0200 <Enter>(1)

    보조 프로시저 호출

     ????:0103 movdl, al <Enter>(2) 가공된

    첫 자리 숫자를 dl 레지스터에 복사

     ????:0105 movcl, 04 <Enter>(3)

     ????:0107 shldl, cl <Enter>(4) 왼쪽으로

    니블 이동


명령 (1)에서 보조 프로시저를 호출한다.

그러면 보조 프로시저는 16진수의 첫 자리를 입력 받아 걸러서 유효한 숫자라면

화면에 출력한 후 30h 또는 37h를 뺀 후 그 값을 주 프로시저로 넘겨 준다.

명령 (2)에서는 보조 프로시저로부터 넘겨 받은 al 레지스터의 값을 dl 레지스터로

복사하며, 명령 (3) (4)에서 니블 이동시켜 둔다.

이것으로 첫 자리 숫자에 대한 입력 작업은 끝난다.






2) 둘째 자리 합침

동일한 과정으로 둘째 자리 숫자에 대한 입력 작업을 프로그래밍하자.

이하 상자 안에 a ??? <Enter> 명령줄이 없으면, 앞의 명령줄에 이어서

디버그가 출력하는 메모리 번지에 계속해서 입력해 주면 그만이다.

여러 부분으로 나눈 것은 단지 설명의 편의를 위한 것일 뿐이다.


     ????:0109  call 0200 <Enter> (5)

    명령 (10) 호출

     ????:010C adddl, al <Enter>(6) 가공된

    둘째 자리 숫자를 dl 레지스터에 합침


역시 보조 프로시저를 호출하여 입력을 받고 가공한 숫자가 넘어 오면, 이번에는

단지 먼저 dl 레지스터에 저장된 값에 보태 주면 그만이므로 다소 간단하다.


3) 아스키 문자 출력

이제는 순서대로 입력한 2 자리 16진수와 동일한 수가 dl 레지스터에 있다.

그것을 코드값으로 삼아 해당 문자를 화면에 출력하는 일만 남은 것이다.


     ????:010E movah, 02<Enter> (7)

     ????:0110 int21 <Enter>(8)

    입력된 코드값에 대응하는 아스키 문자 출력

     ????:0112 int20 <Enter>(9)

     ????:0114 <Enter>


이상으로 프로그램(주 프로시저) 만들기는 끝났다.






3.  보조 프로시저 작성

보조 프로시저는 다소 복잡하게 보이겠지만, 흐름을 알고 보면 별 것 아니다.


1) 입력 문자 검사 준비

먼저 dx 레지스터의 값을 push 명령으로 저장한 후, 입력되는 문자를 일단 화면에

출력하지 않고 16진수의 숫자로서 유효한 문자인지 검사할 준비를 한다.

우리가 키보드로 입력하는 문자는 메모리에 저장됨과 동시에 화면에 출력된다.

그러나, int 21h 함수의 ah = 08h 명령은 입력을 메모리에 저장하기만 하고 화면에는

출력하지 않는다.  그 함수를 이용하자는 것이다.


     - a 200 <Enter>

     ????:0200 push dx <Enter>(10)

     ????:0201 movah, 08<Enter> (11)

    키보드에서 문자를 읽어 들이지만, 그것을 화면에 출력하지는 않음

     ????:0203 int21 <Enter>(12)







2) 머리와 꼬리 잘라 버리기

코드값 00h에서 FFh까지의 아스키 문자 구성을 보면 다음과 같다.

        
  1. 00h ∼ 29h : 특수 문자
  2.     
  3. 30h ∼ 39h : 0 ∼ 9
  4.     
  5. 3Ah ∼ 40h : 각종 기호
  6.     
  7. 41h ∼ 46h : A ∼ F
  8.     
  9. 47h 이후 : 문자 G 이후

우리게게 필요한 16진수 숫자는 (2) (4)이므로 (1) (3) (5)를 걸러 내면 된다.

검사 초기에 먼저 코드값이 30h(문자 0의 코드값)보다 작은 (1) 그룹과, 46h(문자

F의 코드값)보다 큰 (5) 그룹을 차례대로 걸러 버린다.

만약 걸러 버릴 조건이 참이 되면 다시 문자 입력을 받아 들이도록 검사 과정

이전의 명령줄인 위의 int 21h 명령인 (12) 명령줄로 분기한다.


     ????:0205 cmpal, 30 <Enter>(13)

     ????:0207 jb 0203 <Enter>(14)

    코드값이 30h보다 작으면 재입력을 위해 분기

     ????:0209 cmpal, 46 <Enter>(15)

     ????:020B ja 0203 <Enter>(16)

    코드값이 46h보다 크면 재입력을 위해 분기


이 작업으로 일단 (1) (5) 그룹의 입력은 무시되고 재입력을 받게 되며, 이 검사를

통과하여 다음 명령줄로 넘어가는 것은 (2) (3) (4) 그룹으로 한정된다.

머리와 꼬리를 잘라 버리고 몸통만 남은 셈이다.






3) 몸통 둘로 나누기

이제 남아 있는 것은 (2) (3) (4)의 3 그룹이다.

유효한 (2) 그룹과 (4) 그룹 사이에 끼어 있는 버려야 될 (3) 그룹은 생선의 몸통에

박혀 있는 먹지 못할 내장과도 같은 것이다.

버려야 될 내장을 깔끔하게 처리하기 위해 우선 몸통을 둘로 나누자.

내장을 터뜨려 몸통을 더럽히는 잘못을 범해서는 안 된다.

그러기 위해서는 한쪽으로는 몸통 반쪽만 떼어 내고, 다른 쪽으로는 남은 반쪽과

내장을 함께 잘라 두어야 될 것이다.

따라서 이후의 과정은 (2) 그룹을 떼고 (3) (4) 그룹에서 한 번 더 걸럼 장치를

통해 (3) 그룹을 버리든지, 아니면 (4) 그룹을 떼고 (2) (3)그룹에서 한 번 더 걸럼

장치를 통해 (3) 그룹을 버리든지 두 가지 방법 중 한 가지를 사용하면 된다.

그러면, 걸름 장치는 완성되는 셈이다.

계속해서 걸러내기만 하는 것도 지루하므로, 우선 유효한 입력 (2) 그룹에 속하는

문자 0에서 9 사이의 입력에 대하여 작업하도록 하자.

그러기 위해서는 먼저 (3) (4) 그룹을 잘라서 제쳐 두고 시작해야 될 것이다.


     ????:020D cmpal, 39 <Enter>(17)

     ????:020F ja 021b <Enter>(18)

    코드값이 39h보다 크면(즉 3Ah와 46h 사이의 값이면) 마지막 걸름 장치인 명령

    (25)로 분기







4) 문자 0 내지 9 화면 출력

이제 (1) (5) 그룹을 먼저 갈라 내어 재입력을 요구하고, (3) (4) 그룹을 다시

잘라 내어 뒤의 걸름 장치로 보내 버렸으므로 남아 있는 것은 (2) 그룹 뿐이다.

즉 우리가 0네서 9 사이의 문자를 입력한 경우의 처리 명령들이 필요한 것이다.

16진수의 숫자로 유효한 문자가 입력되었으므로 먼저 화면에 출력해 주자.


     ????:0211 movah, 02<Enter> (19)

     ????:0213 movdl, al <Enter>(20) 코드값

    30h∼39h 중 하나 dl 레지스터에 복사

     ????:0215 int21 <Enter>(21)

    입력 받은 문자를 화면에 출력


명령 (19)를 생략하면 명령 (21)에서 문자 출력을 할 수 없게 된다.

왜냐 하면 현재 ah 레지스터에는 명령 (11)에서 저장한 08h가 있기 때문이다.

ah = 08일 때 int 21h 함수는 입력만 받아 들이고 화면 출력은 하지 않는다.

명령 (19)로 화면 출력 대기 상태에 들어가게 되는 것이다.

출력은 dl 레지스터의 값에 따라 행해지므로, 명령 (20)에서 al 레지스터에 입력되어

있는 코드값을 dl 레지스터로 복사해 준다.

이제 준비가 끝났으므로 명령 (21)로 입력한 문자 그대로를 화면에 출력한다.

그 문자는 0 내지 9 중 하나가 될 것이다.

화면 출력은 유효한 입력이 있었다는 표시 이상의 의미는 없다.

다음 작업이 더 중요한 것이다.






5) 문자 0 내지 9 코드값 변환

화면에 출력된 문자는 0 내지 9 중 하나이지만, 메모리에 저장되어 있는 코드값은

30h 내지 39h 중 하나이다.

이 코드값을 출력된 문자와 같은 값으로 만들어 주어야 된다.  우리의 목적은

입력된 숫자 자체를 코드값으로 사용하는 16진수의 출력이기 때문이다.

이 그룹의 코드값은 문자보다 정확하게 30h가 크다.

그러므로 코드값에서 30h를 빼면 문자 그대로의 값이 남게 된다.


     ????:0217 subal, 30 <Enter>(22) 코드값에서

    30을 빼고 al 레지스터에 저장

     ????:0219 popdx <Enter>(23)

     ????:021A ret <Enter>(24)


설명은 길었지만, 입력할 내용은 간단하다.

명령 (22) 한 줄로 코드값 변환은 끝난다.

이제 이 가지로 진행하던 작업은 완전히 끝났다.

그러므로, 명령 (23)으로 프로시저의 처음에 안전하게 저장해 두었던 dx 레지스터의

값을 복구하고 나서 명령 (24)로 주 프로시저로 돌아가는 것이다.

이것으로 프로시저가 완성되었다고 오해해서는 안 된다.

앞서 우리는 그룹 (3) (4)에 속하는 코드값 3Ah 내지 46h를 다시 한 번 걸름 장치로

작업하기 위해 제쳐 두었기 때문이다.






6) 최후의 걸름 장치

남아 있는 것은 몸통 반쪽과 버랴야 할 내장이 함께 붙어 있는 것이다.

즉 남아 있는 코드값 3Ah 내지 46h에서 3Ah 내지 40h는 쓸모 없는 것이다.

따라서 코드값이 41보다 작으면 걸러 버리고 재입력을 요구하면 될 것이다.


     ????:021b cmpal, 41 <Enter>(25)

     ????:021D jb 0203 <Enter>(26)

    코드값이 41h보다 작으면 재입력을 위해 분기



7) 문자 A 내지 F 화면 출력

이제 남아 있는 것은 코드값 41h 내지 46h 중 하나이므로 16진수의 숫자로 유효한

A 내지 F 중 한 문자가 입력된 경우이다.

유효한 문자가 입력되었으므로 그 문자를 화면에 출력하도록 만들자.


     ????:021F movah, 02<Enter> (27)

     ????:0221 movdl, al <Enter>(28) 코드값

    41h∼46h 중 하나 dl 레지스터에 복사

     ????:0223 int21 <Enter>(29)

    입력 받은 문자를 화면에 출력


앞에서 명령 (19)로 ah 레지스터의 값을 02h로 저장했는데 명령 (27)은 불필요한

명령이 아니냐고 반문할 수도 있을 것이다.

그러나 현재 우리가 다루는 가지는 명령 (19)와는 다른 가지이다.

명령 (17)까지는 한 줄기로 뻗어 오다가 명령 (18)에서 두 가지로 나뉘어 한쪽은

명령 (19) 내지 (24)로 마무리되지만, 명령 (25) 이하는 명령 (18)에서 갈라진 다른

가지이기 때문에 현재 가지에서는 명령 (19)는 아무 쓸모 없는 것이다.

알기 쉽게 그림으로 그려 보자.

(( 여기에 어떤 그림이 있었는지 ??? ))


8) 문자 A 내지 F 코드값 변환


    :0225 sub al, 37 <Enter>(30)코드값에서

    37을 빼고 al 레지스터에 저장

    :0227 pop dx <Enter>(31)

    :0228 ret <Enter> (32)







유효 문자 출력


    A 내지 F - 명령 (13) 내지 (16)의 조건이 거짓이므로 무시하고 명령 (25)로

    분기한 후, 명령 (25) (26)의 조건도 거짓이므로 역시 무시하며, 명령 (27) 내지

    (29)에서 화면에 출력

    0 내지 9 - 명령 (13) 내지 (18)의 조건이 모두 거짓이므로 무시하고 명령

    (19) 내지 (21)에서 화면에 출력


코드값을 입력된 문자로 변환


    0 내지 9 - 명령 (22)로 코드값에서 30을 빼고 al 레지스터에 재저장

    A 내지 F - 명령 (30)으로 코드값에서 37을 빼고 al 레지스터에 재저장


주 프로시저로 복귀


    정상적인 입력을 받아 변환까지 끝낸 후에는 dx 레지스터 값을 복구하고 명령

    (24) 또는 명령 (32)로 주 프로시저로 복귀(return)한다.




ID:
PW:

     0 분
     4 분

자유게시판

건강백과 HOME

홈페이지 HOME

조   약     HOME

생활지혜 HOME

서식양식 HOME

법원 전산양식 검색

In Na zum

비공개 HOME

백과넷 포탈 : 건강/법률(메인)/홈피/서식/조약/생활지혜

◁ 2002.9.1.~2021.4.11. ▷

관리자 연락(저작권 의심 신고) : 김병희 010-6204-4973 k8z7@hanmail.net