작업 제어 언어

위키백과, 우리 모두의 백과사전.
이동: 둘러보기, 검색

작업 제어 언어(Job Control Language, JCL)는 IBM 메인프레임 운영 체제에 사용되는 스크립트 언어로, 일괄 처리 작업을 수행하거나 하부 시스템을 시작하는 방법을 시스템에 지시한다.

실제로 IBM JCL에는 두 가지가 있다: 하나는 DOS/360으로 시작하는 운영 체제 계열에 대한 것(최신의 것이 z/VSE). 나머지 하나는 OS/360부터 z/OS에 이르는 계열에 대한 것. 이들은 몇 가지 기본적인 문법과 몇 가지 기본 개념은 동일하지만 그 밖의 것들은 매우 다르다.

도스 및 OS JCL에 공통적으로 쓰이는 기능[편집]

잡, 스텝, 프로시저[편집]

도스 JCL과 OS JCL에서 일의 단위는 (job)이다. 하나의 잡은 하나 이상의 스텝(step)을 이루며, 각 스텝은 하나의 특정 프로그램을 실행하기 위한 요청이다.

이를테면 관계형 데이터베이스 시대 이전에, 관리를 위한 인쇄물 형태의 보고서를 만들어내는 잡은 다음의 스텝들을 이룰 수 있다:

  • 적절한 레코드들을 선택하고 이들을 임시 파일로 복사하기 위해 사용자가 작성한 프로그램
  • 보통 일반 목적의 유틸리티를 이용하여 임시 파일을 필요한 순서에 따라 정렬
  • 부분합과 같은 기타 유용한 정보를 포함하고 있는 프로그램으로서 최종 사용자가 읽기 쉽게끔 정보를 표현하기 위해 사용자가 작성한 프로그램
  • 모니터나 터미널에 보여줄 최종 사용자 정보의 선택된 페이지들에 서식을 입히기 위해 사용자가 작성한 프로그램

도스 JCL, OS JCL 둘 다 최초의 카드(card)는 JOB 카드여야 한다. 이 카드는 다음을 수행한다.

  • 잡을 식별한다.
  • 일반적으로 컴퓨터 서비스 부서가 적절한 사용자 부서에 계산서를 보낼 수 있도록 정보를 제공한다.
  • 잡 전반을 어떻게 실행할 것인지를 정의한다. (예: 다른 잡에 상대적인 잡의 우선 순위)

프로시저(proc)는 잡에 삽입된, 스텝 및 스텝 그룹에 대해 미리 작성된 JCL이다. 도스 JCL과 OS JCL 둘 다 이러한 프로시저를 사용할 수 있다. 프로시저는 하나의 잡이나 각기 다른 잡에서 여러 번 사용되는 스텝을 되풀이하는데 쓰인다. 이를 통해 프로그래머의 시간을 절약하고 오류의 위험도를 낮출 수 있다. 프로시저를 실행하기 위해 JCL 파일에 하나의 카드를 포함할 수 있는데, 이 카드는 프로시저를 특정한 파일로부터 복사한 뒤 이를 작업 흐름에 삽입한다. 또, 프로시저는 매개변수(parameter)를 포함하여 개별 이용에 맞게 프로시저를 맞춤식으로 정의할 수 있다.

기본 문법[편집]

도스, OS JCL 둘 다 한 줄에 이용 가능한 길이는 최대 80자인데, 그 까닭은 DOS/360, OS/360이 처음 사용되었을 때 새로운 입력을 컴퓨터 시스템에 제공한 주 방식이 80열 천공 카드였기 때문이다. 나중에 더 길어진 레코드 길이로 디스크나 테이프 파일을 통해 잡을 제출할 수 있게 되었지만 운영 체제의 잡 제출 구성 요소들은 80자가 넘어가는 문자들은 모두 무시했다.

더 구체적으로 말해 두 운영 체제 계열은 한 줄에 단지 71자만 사용한다. 73번째부터 80번째까지의 글자들은 일반적으로 카드 시퀀스 번호이며, 운영 체제가 보고하는 오류의 위치를 찾아내는데 유용하다. 72번째 문자는 일반적으로 빈 채로 남아 있지만 OS JCL의 경우 비어 있지 않은 문자를 포함함으로써 JCL이 다음 카드에도 계속됨을 지시할 수 있다.

모든 명령어, 매개변수 이름, 값들은 USS 파일 이름을 제외하고는 모두 대문자여야 한다.

인스트림 입력 (아래 참고)을 제외한 모든 줄들은 슬래시 "/"로 시작해야 하며 운영 체제가 처리하는 모든 줄들은 두 개의 슬래시 "//"로 시작해야 한다. (늘 첫 열에서) 그러나 여기에는 두 가지 예외가 있다: 구분문(delimiter statement)과 주석문(comment statement). 구분문은 슬래시와 별표(/*)로 시작하며 주석문은 두 개의 슬래시와 별표(//*)로 시작한다.

수많은 JCL 문은 71자 안에 맞추기에는 너무 길지만 다음의 과정을 통해 연속 카드의 부정수(不定數)로 확장할 수 있다:

  • 문법에 쉼표(",")가 필요한 위치의 끝을 제외하고 실제 모든 JCL 카드를 끝맺는다.
  • 각각의 연속 카드를 "//"로 시작하되 적어도 하나의 공간을 남긴다.

가장 일반적인 종류의 카드 구조는 다음과 같다:

  • "//"
  • 사이에 공백이 없는, "//"로 시작하는 문의 이름 필드. 이 문에 이름이 없으면 적어도 공란에는 바로 //이 온다.
  • 하나 이상의 공백
  • 문의 종류
  • 하나 이상의 공백
  • 쉼표로 구분되고 사이에 공백이 없는 매개변수 (문의 종류에 따라 다름)

인스트림 입력[편집]

도스 JCL과 OS JCL은 둘 다 인스트림 입력을 지원하는데 이를테면 카드들은 운영 체제가 아닌 응용 프로그램이 처리한다. 오랜 기간에 걸쳐 보관해야 하는 데이터는 일반적으로 디스크에 저장되지만, 상호작용적인 터미널이 일반화되기 전까지 이러한 디스크 파일을 만들고 편집하는 유일한 방법은 새로운 데이터를 카드에 공급하는 것이었다.

도스 JCL과 OS JCL은 인스트림 입력의 시작 신호를 주는 방법이 다르지만 둘 다 카드의 첫 열에 인스트림 입력을 "/*"로 마친다는 것은 동일하다.

복잡한 특징들[편집]

유닉스에서의 파일 복사는 다음과 같은 단순한 형태로 가능하다:

cp oldFile newFile

OS/360 계열에서는 다음과 같다

//IS198CPY JOB (IS198T30500),'COPY JOB',CLASS=L,MSGCLASS=X
//COPY01   EXEC PGM=IEBGENER
//SYSPRINT DD SYSOUT=*
//SYSUT1   DD DSN=OLDFILE,DISP=SHR
//SYSUT2   DD DSN=NEWFILE,
//            DISP=(NEW,CATLG,DELETE),
//            SPACE=(CYL,(40,5),RLSE),
//            DCB=(LRECL=115,BLKSIZE=1150)
//SYSIN    DD DUMMY

시스템/360과 PC 환경의 차이는 다음과 같다:

  • 저사양의 시스템/360 CPU는 1980년대 중반 PC보다 성능이 낮고 값은 더 비쌌다. OS/360은 최소 32 KB의 메모리가 장착된 시스템에 맞게 고안되었고 DOS/360의 경우 최소 16KB의 메모리가 장착된 시스템에 맞게 고안되었다. 360/30 CPU (1964년 시스템/360이 발표된 시기에는 저사양)는 초당 5K에서 13K 개의 명령을 처리하였다.[1] 최초의 IBM PC (1981년의 모델 5150)는 16KB 또는 64KB의 메모리를 갖추고 있었고 초당 약 330,000개의 명령을 처리했다.[2][3] 그 결과 JCL은 컴퓨터가 처리하기 쉬워야 했고 프로그래머들이 쉽게 사용할 수 있는지에 대한 우선 순위는 매우 낮았다. 이 시기에 프로그래머들에 들이는 비용은 컴퓨터 보다 값이 더 저렴했다.
  • JCL은 일괄 처리를 위해 고안되었다. 스텝의 결과에 따라 무엇을 할 것인지를 포함하여 운영 체제에 모든 것을 알려줘야 했다. 이를테면 DISP=(NEW,CATLG,DELETE)는 "프로그램이 성공적으로 실행되면 파일을 새로 하나 만들고 카탈로그로 처리하라. 성공하지 않으면 새로운 파일을 삭제하라."를 뜻한다.
  • 시스템/360 머신은 하나의 조직에 속한 모든 사용자가 공유하도록 설계되었다. 그러므로 JOB 카드는 운영 체제에게 사용자의 계정 청구 방법(IS198T30500), 다른 사용자의 잡에 상대적으로 우선 순위가 어떻게 되는지(CLASS=L) 등을 알려준다. //SYSPRINT DD SYSOUT=*는 컴퓨터가 프로그램의 보고서를 기본 프린터에 일반 문서로 출력하도록 지시한다. DISP=SHR는 운영 체제에게 다른 프로그램들이 OLDFILE을 동시에 읽을 수 있음을 지시한다.

도스 JCL[편집]

위치 매개변수[편집]

// TLBL TAPEFIL,'COPYTAPE.JOB',,,,2
// ASSGN SYS005,DISK,VOL=VOL01,SHR
// DLBL DISKFIL,'COPYTAPE.JOB',0,SD
// EXTENT SYS005,VOL01,1,0,800,1600

도스 JCL에서 매개변수들은 위치성을 지니므로 읽고 쓰기를 더 어렵게 만든다:

  • 프로그래머는 어느 항목이 어느 위치로 어떠한 종류의 문으로 이동하는지 기억해야 한다.
  • 일부 선택적 매개변수들을 누락시킨 뒤 나중에 포함되면, 누락된 매개변수들은 상기의 TLBL 문에서와 같이 공란이 없는 쉼표로 표현해 주어야 한다.

OS JCL 보다 매개변수가 적지만 더 많은 문들을 사용함으로써 도스 JCL은 어느 정도는 위치 매개변수의 난이도를 완화시킨다. ASSGN, DLBL, EXTENT 문들은 OS JCL의 하나의 DD 문과 동일한 일을 한다.

장치 의존성[편집]

초기 DOS/360과 대부분의 DOS/VS 버전에서, 잡을 마칠 때 삭제될 기존의 파일과 임시 파일에 대해서도 개별 디스크나 테이프 파일에 사용될 장치의 모델 번호를 지정해야 했다. 이는 고객이 더 현대적인 형태의 장치로 업그레이드하면 수많은 JCL 파일을 변경해야 함을 뜻한다.

나중에 DOS/360 계열이 등장함에 따라 모델 번호를 요구하는 일이 줄었다.

OS JCL[편집]

이 JCL 언어에 쓰이는 문들은 JCL 문이라 한다. OS JCL은 세 가지 기본 문을 이룬다:

  • JOB 문: 잡의 시작, 전체 잡의 정보(발부, 우선 순위 실행, 시간, 공간 제약)를 식별.
  • EXEC 문: 잡의 현재 스텝에서 실행될 프로그램 및 스텝에 대한 정보 식별.
  • DD (Data Description) 문: 스텝에 쓰이는 데이터 파일과 해당 파일에 대한 자세한 정보 식별. DD 문은 스텝 내 어느 순서에라도 올 수 있다.

처음부터 OS 계열의 JCL(최대 z/OS를 포함하여)은 더 유연하고 사용하기 더 쉬워졌다.

다음의 예는 1964년 시스템/360이 등장하자마자 제공되는 오래된 스타일의 문법을 사용한다. 이 오래된 문법은 20년 넘도록 여전히 동일하며 약간의 변경 사항만 있을 뿐이다. 이 오래된 잡들은 종종 복잡한데, 이들은 CLIST 문법을 사용하도록 변환하는 일은 다루기 힘들고 시간 소모적이다.

CLIST 문법이 읽기 더 쉬울지라도 프로그래머들은 오래된 문법을 사용하는 것과 동일한 양의 정보를 제공해 주어야 한다.

JCL 문 코딩 규칙[편집]

각 JCL 문은 5개의 필드로 나뉜다.

 Identifier-Field Name-Field Operation-Field Parameter-Field Comments-Field
                 ^          ^               ^               ^
              no space     space          space           space

식별자 필드(Identifier-Field)는 이름 필드(Name-Field)와 공백 없이 이어져야 한다.

  • 식별자 필드(Identifier-Field): 이 식별자 필드는 데이터가 아닌 JCL 문인 시스템을 지시한다. 식별자 필드는 다음을 이룬다:
    • 구분문(delimiter statement)을 제외한 모든 JCL 문의 열 1과 열 2는 //를 포함한다.
    • 구분문에 해당하는 열 1과 열 2는 /*를 포함한다.
    • JCL 주석문에 속하는 열 1, 2, 3은 //*를 포함한다.
  • 이름 필드(Name-Field): 이름 필드는 다른 문들과 시스템이 이를 가리킬 수 있도록 특정한 문을 식별한다. JCL 문의 경우 이름은 다음과 같이 코딩되어야 한다:
    • 이름은 열 3에서 시작해야 한다.
    • 이름은 1부터 8까지의 알파벳/숫자 또는 국가성 ($, #, @) 문자여야 한다.
    • 첫 문자는 알파벳이어야 한다.
    • 이름 뒤에는 적어도 하나의 공백이 있어야 한다.
  • 연산 필드(Operation-Field): 연산 필드는 문의 종류(명령문의 경우 명령)를 지정한다. 연산 필드는 다음과 같이 코딩되어야 한다:
    • 연산 필드는 문의 문법 상자에서 글자로 이루어진다.
    • 연산 필드 앞에 이름 필드가 온다.
    • 연산 필드 앞뒤로 적어도 하나의 공백이 있어야 한다.

연산 필드는 JOB/EXEC/DD를 말한다.

  JOB  - JOB 문
  EXEC - EXEC 문
  DD   - DD 문
  • 매개변수 필드(Parameter-Field): 오퍼렌드 필드(operand field)라고도 하며 변수는 쉼표로 구별한다. 다음과 같이 코딩되어야 한다:
    • 매개변수 필드는 연산자 필드 뒤에 온다.
    • 매개변수 필드는 몇 가지 정보를 제공하기 위해 JCL 문으로 코딩된 "키워드"인 매개변수를 포함한다. 예) 프로그램_이름,데이터셋_이름,...
  • 주석 필드(Comments-Field): 제어문을 코딩하는 동안 유용한 정보를 포함한다.

키워드 매개변수[편집]

//NEWFILE DD DSN=MYFILE01,UNIT=DISK,SPACE=(TRK,80,10),
//           DCB=(LRECL=100,BLKSIZE=1000),
//           DISP=(NEW,CATLG,DELETE)

OS JCL의 모든 주요 매개변수들은 키워드로 식별되며 어떠한 순서로도 표현이 가능하다. 이 중 일부는 위의 예시처럼 SPACE(새로운 파일에 얼마나 많은 디스크 공간을 할당할 것인지), DCB(파일의 레이아웃의 자세한 사양)와 같은 둘 이상의 하부 매개변수를 포함할 수 있다. 하부 매개변수들은 SPACE에서처럼 가끔 위치성을 띄지만 DCB와 같은 가장 복잡한 매개변수들은 키워드 하부 매개변수가 있다.

위치 매개변수는 키워드 매개변수보다 우선해야 한다. 키워드 매개변수는 등호(=)를 사용하여 언제나 값을 키워드로 할당해야 한다.

장치 독립성[편집]

처음부터 OS 계열 운영 체제의 JCL은 고수준의 장치 독립성을 제공하였다. 잡을 끝낸 뒤 유지되어야 할 새로운 파일들에 대해서도 일반적인 용어(예: UNIT=DISK, UNIT=TAPE)들로 장치 유형을 지정할 수 있다. 물론 문제가 되면 모델 번호라든지, 특정 장치 주소 또한 지정할 수 있다.

매개변수화된 프로시저[편집]

OS JCL 프로시저는 처음부터 매개변수화되었으며 마치 매크로나 단순 함수처럼 만들어준다. 이로써 재사용성을 다양한 환경에서 증대시킨다.

//MYPROC PROC FNAME=MYFILE01,SPTYPE=TRK,SPINIT=50,SPEXT=10,LR=100,BLK=1000
.....
//NEWFILE DD DSN=&FNAME,UNIT=DISK,SPACE=(&SPTYPE,&SPINIT,&SPEXT),
//           DCB=(LRECL=&LR,BLKSIZE=&BLK),DISP=(NEW,CATLG,DELETE)
....

이 예제에서 &로 시작하는 모든 값들은 잡이 프로시저 사용을 요청할 때 지정될 매개변수들이다.

PROC 문은 프로시저에 이름을 줄 뿐만 아니라, 프로그래머가 각각의 매개변수에 대한 기본값을 지정할 수 있게 한다. 그래서 이 예제에서는 하나의 프로세서를 가지고 다양한 크기와 레이아웃의 파일들을 새로 만들어낸다. 이를테면:

//JOB01  JOB ..........
//STEP01 EXEC MYPROC FNAME=JOESFILE,SPTYPE=CYL,SPINIT=10,SPEXT=2,LR=100,BLK=2000

또는

//JOB02  JOB ..........
//STEP01 EXEC MYPROC FNAME=SUESFILE,SPTYPE=TRK,SPINIT=500,SPEXT=100,LR=100,BLK=5000

오버라이드 기능[편집]

OS는 프로그램들이 포함할 수 있는 파일 구조의 사양 중 일부를 JCL에 오버라이드할 수 있게 한다. 이 기능은 대개 새로운 파일들을 만드는데 사용된다. 이를테면:

//STEP01  EXEC PGM=MYPROG
//NEWFILE DD DSN=MYFILE01,UNIT=DISK,SPACE=(TRK,50,10),DCB=BLKSIZE=1000,
//           DISP=(NEW,CATLG,DELETE)

프로그램 MYPROG 내의 코드가 블록 크기를 특정하더라도 새로운 파일이 1000 바이트의 블록 크기를 가질 것이다. 그러므로 프로그램 MYPROG를 변경할 필요가 없다. 한편 이 예제는 DCB의 하부 매개변수 LRECL (레코드 길이)를 지정하지 않고 있으므로 레코드 길이는 MYPROG이 제시하는 바대로 결정된다.

재조회[편집]

멀티 스텝 잡에서 나중의 스텝은 초기 스텝에서 이미 지정한 파일을 통째로 지정하지 않고 재조회를 사용할 수 있다. 이를테면 이 프로시저에서

//MYPROC ................
//MYPR01 EXEC PGM=..........
//NEWFILE DD DSN=&MYFILE,UNIT=DISK,SPACE=(TRK,50,10),
//           DCB=(LRECL=100,BLKSIZE=1000),DISP=(NEW,CATLG,DELETE)
....
//MYPR02 EXEC PGM=..........
//INPUT01 DD DSN=*.MYPR01.NEWFILE

스텝 MYPR02는 스텝 MYPR01의 NEWFILE로 식별된 파일을 사용한다 (DSN은 데이터셋 이름을 말하며 파일의 이름을 지정한다).

여러 잡에 특화된 JCL과 프로시저 호출이 포함된 잡에서 잡에 특화된 스텝은 프로시저에 온전히 지정된 파일을 재조회할 수 있다. 이를테면:

//MYJOB JOB ..........
//STEP01 EXEC MYPROC             Using a procedure
//STEP02 EXEC PGM=.........      Step which is specific to this job
//INPUT01 DD DSN=*.STEP01.MYPR01.NEWFILE

여기서 DSN=*.STEP01.MYPR01.NEWFILE은 "이 잡의 스텝 STEP01이 사용한 프로시저의 스텝 MYPR01의 NEWFILE로 식별된 파일을 사용하라"를 뜻한다. 프로시저의 이름이 아닌 프로시저를 호출한 스텝의 이름을 사용하면 프로그래머가 여러 번 동일한 잡 안에서 재조회에 사용된 프로시저의 인스턴스가 어느 것인지 혼동하지 않고 동일한 프로시저를 사용할 수 있다.

주석[편집]

JCL은 길고 복잡할 수 있어 읽기 쉽지 않다. OS JCL은 프로그래머들이 두 종류의 설명적 주석을 포함할 수 있게 하고 있다:

  • JCL 문과 같은 줄에서. 72번째 열에 X라는 연속 문자를 배치시키고, 그 다음 줄의 열 1-3에 "//"가 오게 함으로써 확장할 수 있다.
  • 주석만 담고 있는 줄. 지엽적인 세세한 부분이 아닌, JCL의 전반적 구조에 대한 주요 사항들을 설명하는데 종종 사용된다. 주석만 담고 있는 줄들은 기나긴, 복잡한 JCL 파일들을 여러 단락으로 나눌 때에도 쓰인다.
//MYJOB JOB ..........
//*  Lines containing only comments.
//******** Often used to divide JCL listing into sections ********
//STEP01 EXEC MYPROC             Comment 2 on same line as statement
//STEP02 EXEC PGM=.........      Comment 3 has been extended and       X
//         overflows into another line.
//INPUT01 DD DSN=STEP01.MYPR01.NEWFILE

입력 파일들의 연결[편집]

OS JCL은 프로그래머들이 입력 파일들을 하나로 연결시킬 수 있게 하여, 이들이 마치 프로그램을 하나의 파일처럼 보이게 만들어준다. 예를 들어:

//INPUT01 DD DSN=MYFILE01,DISP=SHR
//        DD DSN=JOESFILE,DISP=SHR
//        DD DSN=SUESFILE,DISP=SHR

2번째, 3번째 문들은 이름 필드에 값이 없으므로 OS는 이들을 "연결"(concatenation)로 취급한다. 파일들은 동일한 기본 형태 (거의 언제나 연속적인)여야 하며 같은 레코드 길이여야 한다.

조건 처리[편집]

OS는 프로그램들이 성공 여부에 대한 반환 코드를 설정할 것이라 예측한다. 가장 일반적이고 전통적인 값들은 다음과 같다:

  • 0 = 일반 - 모든 것이 정상이다
  • 4 = 경고 - 사소한 오류나 문제가 있다.
  • 8 = 오류 - 상당한 오류나 문제가 있다.
  • 12 = 심각한 문제 - 주된 오류나 문제, 결과(예: 생산된 파일이나 보고서)를 신뢰할 수 없다.
  • 16 = 터미널 오류 - 매우 심각한 문제. 결과를 사용하지 말 것!

OS JCL은 반환 코드를 COND (조건 코드)로 지시하며, 이를 이용하여 차기 단계들의 실행 여부를 결정한다.

//MYJOB JOB ...........
//STEP01 EXEC PGM=PROG01
....
//STEP02 EXEC PGM=PROG02,COND=(4,GT,STEP01)
....
//STEP03 EXEC PGM=PROG03,COND=(8,LE)
....
//STEP04 EXEC PGM=PROG04,COND=(ONLY,STEP01)
....
//STEP05 EXEC PGM=PROG05,COND=(EVEN,STEP03)
....

  1. STEP01을 실행하고 반환 코드를 수집.
  2. 숫자 4가 STEP01의 반한 코드 보다 크면 STEP02를 실행하지 말 것.
  3. 숫자 8이 이전 반환 코드 보다 작거나 같으면 STEP03를 실행하지 말 것.
  4. STEP01이 비정상적으로 종료될 때에만 STEP04를 실행.
  5. STEP03이 비정상적으로 종료될 때에만 STEP05를 실행.

상기의 예는 아래의 의사코드로 변환할 수 있다:

run STEP01
if STEP01's return code is greater than or equal to  4 then
    run STEP02
end if
if any previous return code is less than  8 then
    run STEP03
end if
if STEP01 abnormally ended then
    run STEP04
end if
if STEP03 abnormally ended then
    run STEP05
else
    run STEP05
end if

뒤에 COND 문이 있는 스텝을 읽음으로써 이들을 매우 쉽게 이해할 수 있다. 그러나 나중에 IBM은 COND 매개변수를 보유하면서도 IF 조건문을 JCL에 도입하여 프로그래머들이 코딩을 쉽게 할 수 있게 해 주었다. (COND 변수를 사용하는 기존의 JCL에 대한 변경을 방지)

JCL 유틸리티[편집]

JCL은 수많은 IBM 유틸리티 프로그램을 사용하여 데이터 처리를 돕는다. 유틸리티들은 일괄 처리에 가장 유용하다. 이 유틸리티들은 세 개의 집합을 이룬다:

  • 데이터셋 유틸리티 - 데이터셋의 작성, 인쇄, 복사, 이동, 삭제
  • 시스템 유틸리티 - 카탈로그 유지, 관리
  • 접근 방식 서비스(Access Method Services) - VSAM 및 비 VSAM 데이터셋 처리.

같이 보기[편집]

참조[편집]

  1. CPU MIPS ratings
  2. IBM PC
  3. IBM-compatible computers History of PCs

참고문헌[편집]