리눅스에 대한 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

재지정(Redirection)

리눅스에서 프로그램은 보통 세 개의 파일 서술사를 열게 됩니다. 바로 표준 입력(standard input, STDIN), 표준 출력(standard output, STDOUT), 그리고 표준 에러(standard error, STDERR)가 되지요. 

순서대로 파일 디스크립터(fild descriptor)는 0, 1, 2입니다.

 

리다이렉션(Redirection)이라고 하는 것은 이러한 서술자 중 하나 이상을 파일로 다시 지정하는 수단이라고 볼 수 있는데요. 어떻게 리다이렉션을 사용할까요?

 

1) program > file_name

>는 이해하기 쉽습니다. program에서 출력하는 것을 file_name이라는 파일에 기록하겠다는 것입니다. 더 잘 이해하기 위해서 저의 리눅스를 활용해보도록 하지요.

제 리눅스의 /tmp 디렉터리에는 다음과 같은 파일들이 있습니다.

무슨 파일들인지는 잘 모르겠네요.


[root@localhost tmp]# ls
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-ModemManager.service-OOhWCc
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-bolt.service-KuASd9
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-chronyd.service-mPGGCi
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-colord.service-uTlHDZ
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-fwupd.service-mV4Wkl
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-rtkit-daemon.service-eaiBmp

ls 명령을 치는 순간 화면에 보이는 파일들의 목록이 있죠? 이것은 표준 출력으로 표시한 것입니다. 쉽게 이해하기 위해서 모니터가 표준 출력이라고 보세요.

 

이제는 이것을 파일로 저장해보도록 합시다. 위의 꺽쇠를 이용하면 됩니다.


[root@localhost tmp]# ls > list_stdout
[root@localhost tmp]# cat list_stdout
list_stdout
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-ModemManager.service-OOhWCc
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-bolt.service-KuASd9
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-chronyd.service-mPGGCi
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-colord.service-uTlHDZ
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-fwupd.service-mV4Wkl
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-rtkit-daemon.service-eaiBmp
tracker-extract-files.0

그러면 list_stdout이 생겨날텐데 이것을 cat명령어로 실행해보면 파일의 내용을 볼 수 있습니다. 위의 ls 명령어의 결과와 같죠?

 

그렇다면 다음의 명령은 어떻게 출력이 될까요? 분명 위의 ls에는 aaa.txt라는 파일은 없었습니다. 과연 파일로 저장될까요? 


[root@localhost tmp]# ls -l aaa.txt > list_aaa
ls: cannot access 'aaa.txt': 그런 파일이나 디렉터리가 없습니다
[root@localhost tmp]# cat list_aaa

list_aaa가 생겨나지 않았고 메시지가 출력이 되네요. 여기서 위의 메시지는 stderr로 출력된 에러 메시지입니다. stderr로 출력된 에러 메시지는 list_aaa의 파일로 저장이 되지 않네요. 

 

그 이유는 기본적으로 ls -l aaa.txt > list_aaa는 ls -l aaa.txt 1> list_aaa와 같이 동작합니다. 여기서 숫자 1은 표줄 출력(stdout)을 의미하게 됩니다. 1이 파일 디스크립터라면 우리는 표준 에러는 다음과 같이 저장할 수 있겠군요. 

 

아래의 결과를 보면 2(표준에러)가 리다이렉션되어 파일의 내용으로 전달되는 것을 알 수 있습니다.

 


[root@localhost tmp]# ls -l aaa.txt 2> list_aaa
[root@localhost tmp]# cat list_aaa
ls: cannot access 'aaa.txt': 그런 파일이나 디렉터리가 없습니다

 

2) program >> file_name

꺽쇠가 두개 연달아 있는 것은 파일의 끝에 내용을 덧붙이라는 것으로 여러분이 직접 실험해보시기 바랍니다.

 

 

 

 

3) program < file_name

여기서부터는 조금 헷갈릴 수 있으니 잘 들으세요. 아니, 보세요. file_name의 파일 내용을 program의 표준 입력으로 사용하겠다 라는 의미가 됩니다. 

 

ls는 표준입력을 받지 않는다.

자, 프로그램에는 표준 입력으로 사용자의 입력을 받는 것이 있고 그렇지 않는 프로그램이 있습니다. 여기서 ls는 사용자의 입력을 표준입력으로 받을까요? 결론은 아닌데요. 

아래의 내용으로 확인해보겠습니다.

우선 아까 없던 aaa.txt를 만들어볼텐데 aaa.txt에는 aaa라는 문자를 기록하도록 하지요. 그리고 다시 echo를 사용해 bbb라는 내용을 갖는 aaa파일을 만들어봅시다.


[root@localhost tmp]# echo aaa > aaa.txt
[root@localhost tmp]# ls aaa.txt
aaa.txt
[root@localhost tmp]# echo bbb > aaa
[root@localhost tmp]# ls aaa
aaa

(echo는 그 다음의 입력을 표준 출력으로 출력하는데 아까 배운 >을 통해서 파일로 저장했습니다. )

 

이제 ls에 aaa.txt의 내용을 표준입력으로 받아보도록 하겠습니다. 


[root@localhost tmp]# ls < aaa.txt
aaa
aaa.txt
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-ModemManager.service-OOhWCc
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-bolt.service-KuASd9
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-chronyd.service-mPGGCi
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-colord.service-uTlHDZ
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-fwupd.service-mV4Wkl
systemd-private-b2dd06a5829f42f5b5f2e122f17b9afb-rtkit-daemon.service-eaiBmp
tracker-extract-files.0

뭐 얼핏보면 뭔가 출력이 됐는데 우리가 원하는 결과는 aaa.txt의 내용은 bbb가 ls의 입력으로 들어가서 ls aaa라는 명령어가 수행되어야하는 것입니다. 근데 제가 전달하는 입력을 씹고 현재 디렉터리의 파일 내용을 출력하고 있죠. 이것은 ls의 기본 동작입니다. 

ls는 이 처럼 표준 입력을 받지 않습니다. 대신 argv라고하는 argument vector를 통해서 사용자 입력을 받고 있는 셈입니다.

 

표준입력을 받는 명령어는 무엇이 있을까요? 

대표적으로 cat, grep 등이 있습니다. 

 

cat의 기본 동작

cat의 기본동작(아무런 옵션을 주지 않을때의 동작)은 입력받은 거 그대로 출력하는 기능입니다. 그 입력은 표준입력으로 받습니다. 

표준 입력으로 받으니 이것을 파일의 내용을 입력으로 줘보겠습니다.


[root@localhost tmp]# echo Hello > hello.txt
[root@localhost tmp]# cat < hello.txt
Hello

 

cat명령어는 argv로 파일 이름을 인자로 받을 수 있도록 프로그램이 되어있는데, 결과는 위와 같습니다.

 

 

 

 

grep의 기본 동작

grep은 기본적으로 바로 뒤에 인자가 포함된 문자열이 입력이 되면 그 문자열을 다시 출력해줍니다. 아래의 결과를 보면 알 수 있는데 이 grep은 표준 입력으로 입력을 받습니다.


[root@localhost tmp]# grep reak
AAA
BBB
rea
rreak
rreak
rreakk
rreakk
mmsmsms

자, 여기서 눈 여겨 봐야할 것은 grep은 굵은 글씨인 rreak, rreakk만 출력하였습니다. 왜냐면 인자로 받은 reak라는 문자열이 포함되있기 때문이죠.

 

표준 입력으로 받으니 우리는 파일을 입력으로 리다이렉션을 쓸 수 있겠군요. 위의 입력했던 내용을 그대로 vi 편집기로 저장합니다. 


[root@localhost tmp]# vi grep_test
[root@localhost tmp]# grep reak < grep_test
rreak
rreakk


vi 편집기 내용


AAA 
BBB 
rea 
rreak
rreakk
mmsmsms 

 

표준 입력으로 받은 결과와 동일함을 알 수 있네요.

 

리다이렉션을 통한 Copy

리다이렉션을 통해서 파일을 복사할 수도 있는데요. 아래를 보도록 합시다.  echo로 file을 생성한 후에 cat의 입력으로 file의 내용을 받습니다. 그 후에 cat은 표준 출력으로 file의 내용을 출력하는데 다시 >로 표준 출력의 내용을 file_copied라는 파일에 저장합니다. 결국은 복사를 수행한 것과 같게 됩니다.


[root@localhost tmp]# echo hello > file
[root@localhost tmp]# cat < file > file_copied
[root@localhost tmp]# cat file_copied
hello

 

 

 

 

 

파이프( | )

파이프라는 것은 program1의 표준 출력을 program2의 표준입력으로 입력받습니다. 명령어의 결과를 다시 어떤 명령어의 입력으로 받아야할때 사용되며 리눅스에서 매우 흔히 쓰입니다. 형식은 아래와 같습니다.

program1 | program2

파이프라는 것은 program1의 표준 출력을 program2의 표준입력으로 입력받습니다. 리눅스에서 매우 흔히 쓰이는데요. 가장 빈번하게 사용하는 grep과 연관시켜서 사용해봅시다. 

우선 제 리눅스에서 /bin으로 이동했습니다. 그리고 ls -l 명령을 보면 무수히 많은 파일들이 보이는데 그 중에서 zip이라는 문자열을 포함한 것을 확인해보고 싶습니다. 

그렇다면 ls -l로 우선 표준 출력을 하고 이 표준 출력된 내용을 zip이라는 인자를 전달받는 grep에게 표준 입력으로  전달시키면 되겠죠?

답은 간단합니다. 아래의 명령을 실행시켜보세요.


[root@localhost bin]# ls -l | grep zip
lrwxrwxrwx. 1 root root           5  5월 10  2019 bunzip2 -> bzip2
lrwxrwxrwx. 1 root root           5  5월 10  2019 bzcat -> bzip2
-rwxr-xr-x. 1 root root       38472  5월 10  2019 bzip2
-rwxr-xr-x. 1 root root       17560  5월 10  2019 bzip2recover
-rwxr-xr-x. 1 root root       40256  5월 10  2019 funzip
-rwxr-xr-x. 1 root root        3447  5월 10  2019 gpg-zip
-rwxr-xr-x. 1 root root        2345 11월  8  2019 gunzip
-rwxr-xr-x. 1 root root      170544 11월  8  2019 gzip
lrwxrwxrwx. 1 root root           6  5월 11  2019 mzip -> mtools
-rwxr-xr-x. 1 root root        5656  5월 13  2019 preunzip
-rwxr-xr-x. 1 root root        5656  5월 13  2019 prezip
-rwxr-xr-x. 1 root root       13352  5월 13  2019 prezip-bin
-rwxr-xr-x. 2 root root      210408  5월 10  2019 unzip
-rwxr-xr-x. 1 root root      105064  5월 10  2019 unzipsfx
-rwxr-xr-x. 1 root root      234496  5월 11  2019 zip
-rwxr-xr-x. 1 root root      105376  5월 11  2019 zipcloak
-rwxr-xr-x. 1 root root        2953 10월 10  2008 zipgrep
-rwxr-xr-x. 2 root root      210408  5월 10  2019 zipinfo
-rwxr-xr-x. 1 root root      100104  5월 11  2019 zipnote
-rwxr-xr-x. 1 root root      100112  5월 11  2019 zipsplit

굳이 그림으로 표현하면 아래와 같이 실행됩니다.

 

이렇게 리다이렉션과 파이프에 대해서 알아보았습니다.

궁금하신 점은 댓글로 달아주세요.

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

RecyclerView로 수직 이동은 많이 해보았지만 수평이동을 하고 싶을때는 LinearLayoutManager를 아래와 같이 생성하면 된다. 기본적으로 LinearLayoutManager를 사용하면 수직 이동이다.

 

recyclerView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL, false));

 

이렇게만 만들면 아이템을 양옆으로 드래그할때 아무 지점의 임의에 위치에서 멈추는데, 혹시 멈출때 어떤 하나의 아이템의 위치에서 멈추게 만들고 싶다면 SnapHelper의 객체를 이용하면 되는데 아래의 코드를 추가하면 된다.

recyclerView=(RecyclerView)findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL, false));
recyclerView.setAdapter(adapter);

SnapHelper snapHelper = new PagerSnapHelper();
if (recyclerView.getOnFlingListener() == null)
	snapHelper.attachToRecyclerView(recyclerView);

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
  @Override
  public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
  	super.onScrollStateChanged(recyclerView, newState);
  }

  @Override
  public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
    super.onScrolled(recyclerView, dx, dy);
    LinearLayoutManager layoutManager =
    LinearLayoutManager.class.cast(recyclerView.getLayoutManager());
  }
});

 

SnapHelper의 정확한 용도는 잘 모르므로 알아서 공부를 해야할 것 같다. 

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

 

SimpleDateFormat에는 문자열로 형식을 전달하는 생성자가 있는데 아래의 예시를 참고하면 될것.

 

Date and Time PatternResult

"yyyy.MM.dd G 'at' HH:mm:ss z" 2001.07.04 AD at 12:08:56 PDT
"EEE, MMM d, ''yy" Wed, Jul 4, '01
"h:mm a" 12:08 PM
"hh 'o''clock' a, zzzz" 12 o'clock PM, Pacific Daylight Time
"K:mm a, z" 0:08 PM, PDT
"yyyyy.MMMMM.dd GGG hh:mm aaa" 02001.July.04 AD 12:08 PM
"EEE, d MMM yyyy HH:mm:ss Z" Wed, 4 Jul 2001 12:08:56 -0700
"yyMMddHHmmssZ" 010704120856-0700
"yyyy-MM-dd'T'HH:mm:ss.SSSZ" 2001-07-04T12:08:56.235-0700
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX" 2001-07-04T12:08:56.235-07:00
"YYYY-'W'ww-u" 2001-W27-3

 

1) SimpleDateFormat와 Date로 현재 시간을 구하는 코드

SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd a hh:mm:ss");
Date date=new Date();
		
System.out.println("now  ("+format.format(date)+")");

 

2) System.currentTimeMillis 메소드를 이용하여 현재 시스템 시간을 구하는 코드

SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd a hh:mm:ss");
Date date=new Date(System.currentTimeMillis());
		
System.out.println("now  ("+format.format(date)+")");

 

3) 반대로 long형태의 millisecond로 반환하는 코드

String str = "2020-01-31 14:20:59";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = null;

try{
	date = format.parse(str);
}catch(ParseException e){}

long timeMillis = date.getTime();
System.out.println("time millis : "+timeMillis);
		

 

위 코드는 2020년 1월 31일 오후 2시 20분 59초를 밀리세컨드로 반환하는데 어떤 시간을 기준으로 반환할까?

 

아래 코드를 실행해보면 답이 나온다.

String str = "1970-01-01 09:00:00";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = null;

try{
	date = format.parse(str);
}catch(ParseException e){}

long timeMillis = date.getTime();
System.out.println("time millis : "+timeMillis);

 

아래와 같이 시간 차이(계산)도 쉽게 구할 수 있다. 

final long daySeconds = 86400L;
String str = "1991-09-21 03:00:00";
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date birthDay = null;

try{
	birthDay = format.parse(str);
}catch(ParseException e){}

long birthDayMillis = birthDay.getTime();
long now = System.currentTimeMillis();
long passed = ((now/1000L) - (birthDayMillis/1000L)) / daySeconds;
System.out.println("개 늙었네 : "+passed);

 

 

위의 소스에서 now나 birthdayMillis는 milliseconds단위이므로 1000을 곱하여 시간초로 바꾸고 서로 빼면 지금까지 흐른 초가 나오겠죠? 그걸 또 하루로 나눠버리면 , 아 참고로 하루는 86400초, 지금까지 흐른 일 수가 나오죠? 

 

4) 현재 SimpleDateFormat의 Pattern 알아보기

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("pattern : "+format.toPattern());

 

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

AsyncTask

1.5버전부터 추가된 AsyncTask는 개발자가 사용자 인터페이스에 대한 비동기적인 작업을 하게 해준다.

UI스레드에서 AsyncTask의 execute를 호출하면 되는데 이 AsyncTask를 물론 구현해주어야한다.

 

아래는 AsyncTask를 사용한 현재 시간 타이머


public class MainActivity extends AppCompatActivity {

    private TextView time;
    private int count=0;
    private SimpleDateFormat format=new SimpleDateFormat("yyyy년 MM월 dd일 a hh:mm:ss");
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        time=(TextView)findViewById(R.id.time);

        new AsyncCounterTask().execute();
    }

    class AsyncCounterTask extends AsyncTask<Void, Void, Integer> {

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            Log.e("__LOG__","onPreExecute");
        }
        @Override
        protected Integer doInBackground(Void... integers) {
            while(count<100){
                try {
                    Thread.sleep(1000);
                }catch(InterruptedException e){}
                //publishProgress 호출 후 바로 onProgressUpdate가 호출됨
                publishProgress();
            }
            Log.e("__LOG__","doInBackground End");
            return count;
        }

        @Override
        protected void onProgressUpdate(Void... values) {
            super.onProgressUpdate(values);
            Log.e("__LOG__","onProgressUpdate");
            long now = System.currentTimeMillis();
            time.setText(format.format(new Date(now)));
        }

        @Override
        protected void onPostExecute(Integer integer) {
            super.onPostExecute(integer);
            Log.e("__LOG__","onPostExecute");
        }
    }
}

 

AsyncTask<Params, Progress, Result>처럼 Generic을 사용한다.

 

Params : 실행 시 작업에 전달되는 값

Progress : 작업의 진행정도를 나타내는 값

Result : 작업의 결과를 나타내는 값

 

모두 사용할 필요는 없고 필요에 따라 사용하면 된다.

 

AsyncTask는 어떤 식으로 동작하게 될까? 다음은 몇안되는 AsyncTask의 메소드들인데 그냥 알아두면 편하다.

 

실행 순서는 onPreExecute -> doInBackground -> [ publishProgress -> onProgressUpdate ] ... -> onPostExecute

(위의 엑티비티를 실행하고 로그를 보면 알 수 있다. 아 참고로 접두사 pre는 ~전에, post는 ~ 후에 라는 점을 알면 기억하기 쉽겠다)

 

onPreExecute와 onPostExecute는 doInBackground 실행전, 실행후 한번씩만 실행이 된다. 그래서 doInBackground에서 반환하는 값이 onPostExecute에 전달이 된다. 

publishProgress를 호출하면 onProgressUpdate가 실행이 되는데 여기서 UI를 업데이트할 수 있다. 

 

여기서 중요한것은 doInBackground는 무조건 Override해야한다. (필자는 doInBackground가 쓰레드의 run과 같은 메소드와 비슷하다고 생각, 물론 자세히 까보면 다르겠지만....)

 

아는 사람은 알겠지만 SimpleDateFormat을 사용하여 날짜와 시간에 대한 format을 지정하고 System.currentMillis()를 이용하여 시스템의 현재 시간을 밀리초 단위(정확히 말하면 1970년 1월 1일부터 지금까지 흐른 밀리초)로 구하여 Date의 인자로 집어넣는다. 

이것을 format 형식으로 출력하려면 SimpleDateFormat의 format 메소드의 인자로 쏙 넣으면 되쥬.

 

그렇게 어려운 소스코드는 아니므로 이해가 될 거라 본다.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

파일 조작에 대한 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

fcntl

파일을 열었는데 그 속성을 바꾸려면 어떻게 할까요? fcntl함수는 이미 열린 파일의 속성들을 변경할 수 있습니다.

#include <unistd.h> 
#include <fcntl.h> 
int fcntl(int fd, int cmd, ... /* arg */ );

 

fd : 파일의 속성을 조회하거나 변경할 파일 디스크립터입니다. 

cmd : 커맨드입니다. cmd 종류는 아래와 같습니다.

cmd 설명
F_GETFL fd에 대한 파일 상태 속성들을 반환값으로 돌려줍니다. O_RDONLY, O_WRONLY, O_RDWR, O_EXEC, O_SEARCH는 개별적으로 판정할 수 없고 ACCMODE라는 마스크를 이용하여 알아낼 수 있습니다. 이는 아래의 예제를 통해서 알아보도록 하지요.
F_SETFL 파일 상태 속성들을 세번째 인수로 받아 설정합니다. O_APPEND, O_NONBLOCK, O_SYNC, O_DSYNC, O_RSYNC, O_FSYNC, O_ASYNC 속성만 변경할 수 있습니다. 읽기, 쓰기 관련 플래그(O_WRONLY, O_RDONLY, O_RDWR)은 조회할 수는 있지만 변경할 수 없음을 유의하세요.
F_GETOWN 현재 SIGIO, SIGURG 신호를 받도록 설정된 프로세스 ID 혹은 프로세스 그룹 ID를 돌려줍니다. 
비동기 입출력과 관련이 있습니다.
F_SETOWN SIGIO와 SIGURG신호를 받도록 시정된 프로세스 ID나 프로세스 그룹 ID를 설정합니다.
F_DUPFD 파일 서술자 fd를 복제합니다. 새 파일 서술자를 함수의 반환값으로 돌려줍니다. 이때 반환된 새 서술자는 FD_CLOEXEC속성이 해제된 상태입니다. 그렇기 때문에 exec류의 함수로 다른 명령을 실행했을때 열린 상태로 남아있습니다.
F_DUPFD_CLOEXEC 파일 서술자를 복제하고 새 파일 서술자에 관련된 FD_CLOEXEC를 설정합니다. 역시 새 파일 서술자가 반환됩니다.
F_GETFD fd에 대한 파일 서술자 플래그들을 반환합니다. 정의되어 있는 파일 서술자 flag는 FD_CLOEXEC입니다.
F_SETFD 서술자 플래그들을 설정합니다. 셋째 인자에 그 플래그를 전달합니다.

위 함수는 cmd에 따라 반환값이 다르고 실패시 -1을 반환하게 됩니다.

 

속성 조회, 변경 예제

아래는 파일의 속성을 조회해보고 O_APPEND 속성을 추가하여 다시 조회하는 예제입니다. 

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>

void printAccessMode(int flags){

        switch(flags & O_ACCMODE){
        case O_RDONLY:
                        printf("read only\n");
                        break;

        case O_WRONLY:
                        printf("write only\n");
                        break;
        case O_RDWR:
                        printf("read write\n");
                        break;
        }

        printf("append %s \n", (flags & O_APPEND) ? "O":"X");
        printf("nonblocking %s \n", (flags & O_NONBLOCK) ? "O":"X");
        printf("\n");
}

int main(){
        int fd = open("test.txt", O_RDONLY|O_CREAT, 0666);

        int flags = fcntl(fd, F_GETFL,0);
        printAccessMode(flags);

        flags |= O_APPEND;

        fcntl(fd, F_SETFL, flags);

        flags = fcntl(fd, F_GETFL, 0);
        printAccessMode(flags);

}

 

주의해야할 점은 파일 서술자 플래그, 파일 상태 플래그를 수정할때는 반드시 먼저 플래그를 얻어오고(GET) 얻어온 값을 통해 변경(SET)해야합니다. 그렇지 않고 바로 SET하면 이전에 설정했던 값은 지워질 수 있습니다. 

 

결과

# ./a.out
read only
append X
nonblocking X

read only
append O
nonblocking X

 

Non-blocking 예제

표준 입력(fd 0)으로 입력을 받는데, 이것을 non-blocking으로 하고 싶을때 fcntl을 통해서 구현할 수 있습니다. 바로 O_NONBLOCK을 통해서 실현할 수 있죠.

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>


int main(){
        char buf[128] = {0,};
        int fd = 0;
        int flags = fcntl(fd, F_GETFL,0);

        flags |= O_NONBLOCK;
        //fcntl(fd, F_SETFL, flags);

        read(fd, buf, sizeof(buf));
        printf("buf : %s \n", buf);
}

 

위의 코드를 그대로 실행하면 우리가 알고 있듯, 입력을 받을때까지 프로그램은 대기하게 됩니다. 사용자가 입력을 하게 되면 그때 입력 내용을 출력하고 프로그램이 끝이 납니다.

# ./a.out
Hello!!
buf : Hello!!

 

이제 주석을 해제하면 사용자 입력을 기다리지 않고 바로 버퍼를 출력하고 끝납니다. 

# ./a.out
buf :

 

이것을 조금만 응용하면 사용자가 입력할 시간을 주고 제한 시간내에 입력이 없으면 그에 따른 로직을 도는 프로그램을 짤 수도 있을 겁니다.

 

CLOEXEC 예제

어떤 파일을 열어서 fd를 얻었고 exec류의 함수로 다른 프로그램을 실행하려고 합니다. 이때 다른 프로그램은 이미 열려진 fd를 사용하지 않음에도 fd를 그대로 가져가게 되죠. 다음의 코드가 그런 현상을 보여줍니다. 

 

파일을 오픈한 후 execl함수로 loop이라는 프로그램을 실행합니다.

#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>

int main(){
        int fd = open("test.txt",O_RDONLY|O_CREAT,0666);
        int flags = fcntl(fd, F_GETFD, 0);
        flags |= FD_CLOEXEC;

        //fcntl(fd, F_SETFD, flags);

        execl("/root/examples/file_test/loop","./loop",0);
}

 

loop프로그램은 다음의 코드로 만들어졌습니다. pid출력하고 무한루프를 돕니다. 별거없죠?

#include <stdio.h>
#include <unistd.h>

int main(){
        printf("loop start pid:%d\n",getpid());
        while(1);
}

 

두개를 컴파일하고 무한 루프 프로그램말고 execl을 호출하는 프로그램을 실행해보도록 하지요.

# ./a.out &
[2] 6808
# loop start pid:6808

 

 

이제 해당 pid의 fd를 보시기 바랍니다. 그러면 loop 프로그램에서 열린 fd를 볼 수 있는데, 맨 아래의 test.txt도 열려있음을 확인할 수 있습니다.

# ls -l /proc/6808/fd
합계 0
lrwx------. 1 root root 64  6월 27 18:54 0 -> /dev/pts/1
lrwx------. 1 root root 64  6월 27 18:54 1 -> /dev/pts/1
lrwx------. 1 root root 64  6월 27 18:54 2 -> /dev/pts/1
lr-x------. 1 root root 64  6월 27 18:54 3 -> /root/examples/file_test/test.txt

 

이 fd를 exec시에 닫고 싶다면 위의 주석을 해제하고 다시 실행해보세요.

#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>

int main(){
        int fd = open("test.txt",O_RDONLY|O_CREAT,0666);
        int flags = fcntl(fd, F_GETFD, 0);
        flags |= FD_CLOEXEC;

        fcntl(fd, F_SETFD, flags);

        execl("/root/examples/file_test/loop","./loop",0);
}

 

그러면 아래와 같이 test.txt가 loop 프로세스에서 닫혀져있음을 알 수 있습니다.

# ./a.out &
[3] 6893
# loop start pid:6893

#
# ls -l /proc/6893/fd
합계 0
lrwx------. 1 root root 64  6월 27 18:57 0 -> /dev/pts/1
lrwx------. 1 root root 64  6월 27 18:57 1 -> /dev/pts/1
lrwx------. 1 root root 64  6월 27 18:57 2 -> /dev/pts/1

 

지금까지 fcntl 설명과 fcntl을 이용해서 파일의 속성을 바꾸는 예제를 보았습니다. 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

디스크 여유 공간과 디스크 사용량을 보기 위해서 리눅스에서는 df, du가 있습니다.

df(Disk Free)

시스템 전체에 마운트 된 디스크의 여유 공간을 출력해줍니다.

파일시스템, 디스크 크기, 사용된 용량, 사용가능한 용량, 사용된 용량의 비율, 마운트된 지점을 순서대로 출력해줍니다.
아무 옵션없이 이렇게 보면 알기가 좀 까다롭죠?

-h Human readable로 사람이 읽기 좋게 출력해줍니다.

-k 위 옵션은 키로바이트 단위로 출력해줍니다.

-i 사용가능한 또는 사용중인 inode에 대한 정보를 출력해줍니다.

 

du(Disk Usage)

디스크 사용량을 알기위한 명령어인데, 뒤 인자에 경로를 붙이지 않는다면 현재 디렉토리에서부터 각 디렉토리의 디스크 사용량을 출력합니다.

 

-h df에서와 같이 사람이 읽기좋게 출력해줍니다.

 

-s 디렉토리를 표시하지 않고 단지 사용량만 알고 싶다면 Summary의 약자인 s옵션을 쓰면 됩니다. 읽기 좋게 h와 같이 써보세요.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

IPSec(IP Security)

IP계층(네트워크 계층)을 안전하게 보호하기 위해서 IPSec이라는 보호 기법을 사용합니다. TLS와 같은 안전한 통신 기법만 있으면 되지, 왜 굳이 IPSec이 필요하느냐에 대한 물음을 갖을 수 있습니다. TLS는 TCP 프로토콜만을 보호합니다. 전송 계층에는 TCP뿐만 아니라 UDP도 있지요. TCP뿐만 아니라 UDP, 혹은 더 상위 계층까지 보호하기 위한다면 더 낮은 계층에서 보호하는 것이 효과적이겠죠. 그래서 IP계층에서 데이터를 보호하는 것입니다. 대부분의 네트워크 응용프로그램은 IP 계층을 사용하기도 하니까 IP계층에서 동작하는 보안, 즉, 페킷에 대한 보안을 제공하는 IP Security(IPSec)이 필요합니다.

 

IP Sec은 그림에서 보는바와 같이 IP계층을 집중적으로 보호합니다.

 

두가지 모드

IPSec에는 두 가지 모드가 있는데, IP의 내용(payload)만을 보호하느냐, 아니면 헤더까지 모두 보호하느냐에 따라서 전자는 전송 모드(Transport Mode), 후자는 터널 모드(Tunnel Model)라고 합니다.

 

전송 모드(Transport Mode)

전송모드는 전송 계층와 네트워크 계층 사이에 전달되는 payload를 보호합니다. 중간에 IPSec 계층이 있기 때문에 IPSec헤더가 붙고, 이후에 네트워크 계층에서는 이것이 모두 상위층에서 보낸 데이터(payload)로 취급이 되므로 IP 헤더가 붙고 아래 계층으로 전달되지요. 

 

 

 

전송모드는 host-to-host( end-to-end)간 데이터 보호가 필요할때 사용이 됩니다. 아래는 전송모드의 데이터 전송 흐름을 보여줍니다.

 

왼쪽 컴퓨터(host)는 IPSec을 적용하여 데이터를 보냅니다. 네트워크를 통해서 오른쪽 컴퓨터로 데이터가 도착합니다. 자, 이 사이에서 다른 사람이 데이터를 가져가도 IPSec에 대한 보호가 이루어져있으므로 볼 수 없고 라우터를 거쳐 두 당사자만 데이터를 보호할 수 있지요. 그래서 종단 간의 보호(End-To-End Protection, E2EP)가 이루어 질 수 있습니다. 

 

 

 

터널 모드(Tunnel Mode)

터널 모드는 IPSec이 IP 헤더를 포함한 IP 계층의 모든 것을 보호합니다. IP 헤더까지 완전히 보호하고 IPSec의 헤더를 추가하였으니 기존의 IP헤더를 볼 수 없기 때문에 새로운 IP 헤더가 추가됩니다. 

 

이 IPSec 헤더와 새로운 헤더는 누가 추가해줄까요? 바로 호스트, 종단이 아닌 그 중간자가 추가해줍니다. 보통 라우터가 되지요. 

 

 

아래는 그 흐름을 보여주는데, 전송모드와는 다르게 호스트 A는 별다른 IPSec의 조취를 취하지 않습니다. 하지만 Router A에서 IPSec을 적용하고 새로운 IP 헤더를 추가합니다. 이 헤더에는 목적지 라우터의 주소가 있어서 Router B로 보냅니다. Router B는 이후에 적절한 조취를 취하고 새로운 IP헤더와 IPSec헤더를 제거한 후 Host B에게 전달합니다. 마치 RouterA, RouterB가 터널 역할을 하는 것 같네요.

 

터널 모드는 보통 두개의 라우터간, 호스트와 라우터간, 라우터와 호스트간에 사용이 되는데, 즉, 송수신자 양쪽 모두가 호스트가 아닌 경우에 사용됩니다.

 

 

두 가지 프로토콜

IPSec은 또 두가지 보안 프로토콜을 제공하는데, 인증에 대해서만 검사하는 인증헤더 프로토콜(AH: Authentication Header Protocol)과 페이로드 전체를 보호하여 기밀성을 제공하는 보안 페이로드 캡슐화(ESP: Encapsulating Security Payload)가 그것들입니다.

 

AH(Authentication Header)

발신지 호스트를 인증하고 IP패킷의 무결성을 보장합니다. 인증을 위해서 해쉬함수와 대칭키가 사용되어 Message Digest를 생성하고 헤더에 삽입합니다. AH는 인증과 무결성을 보장하지만 비밀은 보장해주지 않습니다.

 

Next Header : IPSec 다음에 오는 페이로드의 헤더를 말합니다. TCP인지 UDP인지 또는 ICMP인지 의미합니다.

Payload Length : 인증헤더의 길이를 말하며 4바이트 배수가 됩니다. 

Security Parameter Index : 32비트 보안 매개변수 색인(SPI) 필드는 Security Association에 대한 식별자입니다.

Sequence Number : 32비트 순서번호인데 이것은 replay attack을 방지합니다.

Authentication Data: 헤더를 포함하여 전체 페킷에 대한 데이터를 인증 데이터로 만드는데, 이때 IP 헤더의 변경될 수 있는 데이터는 제외하고 인증데이터를 만들게 됩니다. 예를 들어 TTL같은 변경이 될 수 있는 데이터는 인증 데이터를 만들때 포함하지 않습니다. 만들면 AH의 Authentication Data필드에 삽입됩니다.

 

ESP(Encapsulating Security Payload)

AH가 데이터의 기밀성을 보장할 수 없지만 ESP는 기밀성을 보장할 수 있습니다. 또한 AH가 보장하는 IP패킷의 무결성 등 AH가 제공하는 서비스를 모두 보장할 수 있습니다.

 

ESP 헤더의 각각 필드는 32비트입니다. 

눈에 익은 필드들이 몇개 보이지요? 대부분은 AH의 필드와 유사합니다. 또 payload를 암호화하고 있네요.

 

Authentication Data : AH와는 다르게 인증데이터가 IP헤더를 포함하지 않습니다. ESP헤더까지만 인증데이터로 만들고 ESP Trailer에 붙이게 됩니다.

 

AH와 ESP의 대한 차이는 다음의 표로 간략하게 정리하였습니다.

Services AH ESP
Access Control O O
Message Authentication
(Message Integrity)
O O
Confidentiality X O
Replay Attack Protection O O
Entity Authentication
(Data Source Authentication)
O O

 

접근제어가 보장된다는 것을 알기 위해서는 SAD(Security Association Database), SP(Security Parameters) 등의 용어에 대해서 이해해야하는데, 이것은 다음 포스팅에서 설명하도록 하겠습니다.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

리눅스 프로그래밍에 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

getopt로 명령인자 분리

 

ls -il hello.txt

위와 같은 명령을 구현하고자 할때 명령어에 대한 옵션 분리가 필요합니다. -i 옵션과 -l 옵션이 있고, 위의 명령어에서 hello.txt라는 파일 이름을 줄 수도 있고 안줄 수도 있습니다. 

getopt를 모른다면 이 명령 옵션 인자를 스스로 파싱해서 써야할건데 자신있으신가요? 저는 자신이 없어서 getopt를 설명하고자 합니다.

 

getopt를 사용하기 위해서는 unistd.h 헤더파일을 추가해야합니다.


#include
<unistd.h>

int getopt(int argc, char* const argv[], const char *optstring);

extern char *optarg;
extern int optind, opterr, optopt;

getopt는 명령어를 argv에 있는 명령어를 계속적으로 parsing하는데, 정상적으로 파싱이 되면 optstring에서 지정한 문자열이 반환되고 파싱이 전부되었다면 최종적으로 -1을 반환합니다. 

 

어려운거는 별로 없으니 바로 예제를 보도록 하지요.

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>

void showHelp(const char* thisName){
        printf("usage : %s [OPTION] [MESSAGE]\n"
                        " -h            help\n"
                        " -w [MESSAGE]  write file\n"
                    " -r                read file\n"
                        , thisName);
}

void writeFile(const char* message){
        int fd = open("getopt_file.txt",O_RDWR|O_CREAT,0777);
        write(fd, message, strlen(message));
        close(fd);
}

void readFile(){
        char buf[32]={0,};
        int n;
        int fd = open("getopt_file.txt",O_RDWR|O_CREAT,0777);
        while((n = read(fd, buf, sizeof(buf))) > 0)
                printf("%s",buf);

        close(fd);
}
int main(int argc, char* argv[]){
                char opt;
                opterr = 0;

                if(argc < 2){
                        showHelp(argv[0]);
                        exit(0);
                }

                while((opt = getopt(argc, argv, "hw:r")) != -1){
                        printf("opt:%c, optopt:%d\n", opt, optopt);
                        switch(opt){
                        case 'h':
                                showHelp(argv[0]);
                                break;
                        case 'w':
                                writeFile(optarg);
                                break;

                        case 'r':
                                readFile();
                                break;

                        }
                }
}

 

h는 help 옵션, w는 파일에 write하는 명령 옵션, r은 파일을 읽는데 사용하는 명령 옵션입니다. 

다른건 이해가 가실 것이고 optstring을 보면 w옆에 :이 있네요. w는 다음에는 MESSAGE라는 문자열이 오게 되지요. 이처럼 명령어 옵션 이후에 인자가 필요할때 그 문자 다음에 :을 사용하는데요. 명령 다음 인자는 optarg에 저장이 됩니다.

 

 

컴파일 후 실행하면서 어떻게 실행되는지 보겠습니다.


# gcc getopt_test.c -o getopt_test
# ./getopt_test
usage : ./getopt_test [OPTION] [MESSAGE]
 -h             help
 -w [MESSAGE]   write file
 -r             read file
# ./getopt_test -h
opt:h, optopt:0
usage : ./getopt_test [OPTION] [MESSAGE]
 -h             help
 -w [MESSAGE]   write file
 -r             read file
# ./getopt_test -w "hello"
opt:w, optopt:0
# ./getopt_test -r
opt:r, optopt:0
hello

만약 hwr이외에 옵션을 주면 어떻게 될까요?

 


# ./getopt_test -k
opt:?, optopt:107

알 수 없는 옵션을 주게 되거나 옵션 이후 인자가 필요한데 주지 않으면 ?을 반환하는 것을 알 수 있습니다.

 

이제 optopt, opterr을 보도록 하지요.

optopt는 getopt가 제대로 실행이 되었다면 0이 되는데, 그렇지 않을 경우 그 문자가 들어갑니다.

opterr은 에러 발생시 출력할지 말지를 정합니다. opterr을 1로 해놓으면 getopt에서 에러메시지를 출력하는데 0을 지정하면 출력하지 않습니다.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

 

 

SSL(Secure Socket Layer)

SSL은 1994년 netscape사의 웹 브라우저 보안 프로토콜로 처음 고안되어 1996년까지 버전 3.0까지 발표되었는데, 3.0을 표준화한 것이 TLS라고 합니다. 통상적으로 SSL과 TLS는 같은 의미로 사용됩니다.

 

SSL은 왜 생겨났을까요? 

아마 암호화를 배우신 분이라면 대칭키 암호, 비대칭키 암호를 배우셨을텐데, 대칭키는 빠른 대신 서로간의 공통적으로 알고 있는 키를 공유해야합니다. 대칭적이죠? 그래서 대칭키 암호입니다. 비대칭키는 비대칭적으로 서로 다른 키를 갖고 있어 암복호화를 합니다. 

대칭키암호와 비대칭키 암호에 관한 설명은 지난 포스팅을 참고해주세요.

 

우리는 대칭키를 공유할때 외부로의 노출이 발생하게 됩니다. 이 키를 중간에서 가로채게 된다면? 망하는거지요. 

그렇다면 대칭키는 우리들 머릿속에서 지워버리고 비대칭키로 암호화 통신을 하면 되겠군요. 가능할 수는 있지만 너무 느리고 비효율적입니다. 

그렇다면 처음 대칭키를 교환할때만 비대칭키 암호를 사용하면 어떨까요? 바로 TLS가 그런 역할을 합니다.

TLS에서 제공하는 보안 서비스

1. 기밀성 (이거 북한 어떤 개랑 발음이 똑같네요)

- 대칭키 암호를 사용하게 되면 기밀성을 제공할 수 있습니다. 남들이 데이터를 훔쳐가도 볼 수 없는 비밀을 제공합니다. 

2. 무결성

- 메시지 인증 코드(MAC: Message Authentication Code)를 통해서 메시지 인증을 제공합니다. 위, 변조 여부를 확인할 수 있지요.

3. 인증

- 연결 초기 설정에서 주고 받는 인증서를 통해서 신뢰할 수 있는 개체인지 인증할 수 있습니다.

 

TLS는 전송 계층 위에서 TLS 계층을 따로 두어 동작하게 됩니다. TLS를 사용하는 어플리케이션 프로토콜은 끝에 s가 붙게 되는데 TLS기반의 HTTP는 HTTPS라고 지칭합니다. TLS기반의 FTP는 역시 FTPS라고 부르지요. 

 

TLS의 세부 프로토콜은 다음과 같습니다.

프로토콜 상세 설명
Handshake 양쪽 간에 연결을 설정할때 보안 협상을 위한 프로토콜입니다. 연결 설정 과정은 아래에서 설명합니다.
Change Cipher Spec 보안 파라미터를 변경하거나 적용할때 사용합니다. 예를 들어 대칭키 알고리즘을 변경할때 이 프로토콜이 사용됩니다.
Alert 오류를 전송할때 사용되는 프로토콜입니다.
Application Data 실제 데이터가 전송될때 사용되는 프로토콜입니다.
Record  협상된 보안 파라미터를 이용하여 암,복호화, 무결성 검증 등을 수행하는 프로토콜입니다.

 

상태 유지(Stateful) 프로토콜 

TLS는 세션과 연결별로 상태정보를 유지합니다. TLS는 Full Handshake를 통해서 세션을 생성하고 이 세션 정보를 공유하는 여러 연결을 Abbreviation Handshake를 통해서 성립합니다. 

Full Handshake는 아래에 그림과 같이 설명합니다. Abbrevation Handshake는 세션이 이미 존재할때 사용하는 Handshake방식입니다. 

 

용어가 조금 혼동이 될 수 있는데 연결은 서버와 클라이언트 간 통신의 단위를 말하여 세션은 그 연결의 다수로 이루어지며 세션은 한번 성립되면 다음 연결을 위해서 상태를 유지(session id, negotiated cryptography parameters 등)할 수 있습니다. 예를 들면, 이미 한번의 연결을 하여 암호화 방식, 인증서 교환, session id를 공유했고 할 일이 모두 끝나 연결을 끊었습니다. 이 후 다음 연결에 이 세션에 대한 정보를 이용하여 별도의 번거로운 과정없이 연결을 할 수 있는 겁니다.

 

Handshake

Client → Server :

Client ← Server :

1) Client Hello (Client 안녕)

Client가 서버에 접속할때 몇가지 데이터를 주는데 첫 인사치고는 조금 깁니다. 무엇이 있나 보도록할까요?

- random : 클라이언트는 32바이트 난수값을 전달합니다. 이 랜덤값은 나중에 비밀 데이터를 위해 사용이 됩니다. 비밀 데이터를 master secret이라고 합니다.

- session ID : 세션을 처음 생성할때는 빈값, 이미 생성된 세션이 있다면 그 세션 ID를 전달합니다. 

- cipher suite : 클라이언트가 지원가능한 키 교환 알고리즘, 대칭키 암호 알고리즘, 해시 알고리즘 등을 알려줍니다. 클라이언트와 서버 사이에 갖고 있는 알고리즘이 다 다르겠죠? 그래서 서버는 이 중에 최적의 방식을 선택합니다. 

EX) TLS_RSA_WITH_AES_128_GCM_SHA256 : 보통 이런식인데, 키 교환 알고리즘은 RSA, 대칭키 알고리즘은 AES_128 GCM방식을 사용하고 Hash 알고리즘으로는 SHA256을 사용한다는 것입니다.

 

2) Server Hello (Server 안녕)

사용할 TLS버전, 클라이트, 서버 공통으로 지원가능한 최적의 Cihper suite, 압축 방식 등을 client에게 전달합니다. 이 밖에도 다음 정보를 전달하는데요.

- random : 역시 server도 32바이트 난수를 생성해서 client에게 전달합니다. 역시 나중에 master secret이라는 비밀값을 생성할때 사용되는 재료입니다.

- session ID : 세션 정보를 보냅니다.

 

3) Server certificate

아까 TLS가 인증(TLS의 보안 서비스 3가지 : 기밀성, 무결성, 인증) 서비스를 제공한다고 했습니다. 이 인증서를 이용해서 클라이언트는 서버가 믿을 만한, 신뢰할 만한 서버인지 확인합니다.

 

 

4) Server key exchange

키 교환에 필요한 정보를 제공합니다. 만약 필요하지 않으면 이 과정은 생략이 가능한데, 예를 들어 키교환 알고리즘을 Diffie-Hellman으로 사용한다면 소수, 원시근 등이 필요하므로 이것을 전송합니다.

 

5) Certificate request

서버 역시 클라이언트를 인증해야할때 인증서를 요구할 수 있습니다. 요청하지 않을 수도 있습니다.

 

6) Server hello done

서버의 인사가 끝났네요. 

 

7) Certificate 

방금 전 서버가 요청했던 인증서를 줄 수 있습니다. 요청하지 않았으면 필요없는 과정이네요.

 

8) Client key exchange

키교환에 필요한 정보를 서버에 제공합니다.  이 정보를 pre-master secret이라고 하는데 이게 대칭키에 사용되는 것으로 절대 노출이 되어서는 안됩니다. pre-master secret은 이전에 서버로부터 받은 랜덤값 있었죠? 이것과 클라이언트가 생성한 랜덤값을 조합하여 서버에게 전송합니다. 그냥 보낼까요? 아니죠. 이렇게 민감한 정보는 암호화를 해서 보내야하는데 어떻게 암호화를 할까요? 

이전에 우리는 인증서를 받았습니다. 인증서 안에는 서버의 공개키가 있습니다. 이것으로 서버에게 암호화하여 전송합니다. 

클라이언트는 자기가 생성했으니 이미 가지고 있고, 서버가 무사히 암호화된 pre-master secret을 받았다면 자신의 개인키로 복호화할 수 있습니다. 이제 서로가 pre-master secret을 공유하고 있지요? 이 pre-master secret을 일련의 과정을 거쳐 client/server는 master secret으로 만들게 됩니다.

클라이언트, 서버는 master secret으로 세션에 사용될 키를 생성하게 되는데, 이 키가 바로 대칭키입니다.

 

9) Certificate verify

클라이언트에 대한 Certificate request를 받았다면 보낸 인증서에 대한 개인키를 가지고 있다는 것을 증명합니다. handshake과정에서 주고 받은 메시지 + master secret을 조합한 hash값에 개인키로 디지털 서명하여 전송합니다.

 

10) Change cipher spec

협상된 보안 파라미터를 적용하거나 변경될때 서버에게 알립니다.

 

11) Finished

클라이언트 끝

 

12) Change cipher spec

클라이언트에게 보안 파라미터 변경을 알립니다.

 

13) Finished

서버도 끝

 

14) 통신

이제 주고받은 비밀키를 통해서 안전하게 통신하면 됩니다.

 

이상으로 TLS에 대한 기본적인 개념과 연결과정을 알아보았습니다.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

프로세스와 관련한 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

wait, waitpid 함수는 아래의 header파일을 include해야합니다. 이 두 함수는 부모프로세스가 자식프로세스를 기다리는 공통점이 있습니다만 waitpid는 어떤 자식을 기다릴지 조금 더 구체적으로 지정할 수 있습니다.

이 두 함수의 반환값은 성공시 process id를 반환하고 오류시 -1을 반환합니다. 

#include <sys/types.h>
#include <sys/wait.h>

 

wait 함수

pid_t wait(int *wstatus);

 

성공시 프로세스의 pid를 반환합니다. wstatus는 종료상태를 알 수 있는데, 굳이 종료상태를 알 필요가 없다면 NULL을 전달해주세요.

wstatus를 알 수 있는 방법은 <sys/wait.h>에 정의되어있는 매크로들을 사용하면 됩니다. 아래는 그 매크로들과 설명입니다. 

매크로 설명
WIFEXITED(wstatus) returns  true  if  the  child  terminated normally, that is, by calling exit(3) or
              _exit(2), or by returning from main().
WEXITSTATUS(wstatus) returns the exit status of the child.  This consists of the  least  significant  8
              bits  of  the  status  argument  that  the child specified in a call to exit(3) or
              _exit(2) or as the argument for a return statement in main().  This  macro  should
              be employed only if WIFEXITED returned true.
WIFSIGNALED(wstatus) returns true if the child process was terminated by a signal.
WTERMSIG(wstatus) returns the number of the signal that caused the child process to terminate.  This
              macro should be employed only if WIFSIGNALED returned true.
 WCOREDUMP(wstatus) returns true if the child produced a core dump.  This  macro  should  be  employed
              only if WIFSIGNALED returned true.

              This  macro  is  not  specified  in POSIX.1-2001 and is not available on some UNIX
              implementations (e.g., AIX, SunOS).  Therefore,  enclose  its  use  inside  #ifdef
              WCOREDUMP ... #endif.
WIFSTOPPED(wstatus) returns  true  if  the  child process was stopped by delivery of a signal; this is
              possible only if the call was done using WUNTRACED or  when  the  child  is  being
              traced (see ptrace(2)).
WSTOPSIG(wstatus) returns  the  number  of  the  signal  which caused the child to stop.  This macro
              should be employed only if WIFSTOPPED returned true.
 WIFCONTINUED(wstatus) (since Linux 2.6.10) returns true if the child process was resumed by delivery  of
              SIGCONT

 

그렇다면 이제 예제를 보도록 하겠습니다.

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
        int status, cpid, endpid;

        if((cpid=fork())==0){
                printf("\tchild process:%d\n",getpid());
                sleep(10);
                printf("\tchild end\n");
                exit(10);
        }

        endpid=wait(&status);
        printf("end pid : %d\n",endpid);
        printf("WIFEXITED : %d\n",WIFEXITED(status));
        printf("WEXITSTATUS : %d\n",WEXITSTATUS(status));
        printf("WIFSIGNALED : %d\n",WIFSIGNALED(status));

        printf("\n");
        printf("parent end\n");

}

 

이후 아래와 같이 컴파일하고 정상적인 결과를 보도록 해봅시다.

# gcc process_wait.c
# ./a.out
        child process:5031
        child end
end pid : 5031
WIFEXITED : 1
WEXITSTATUS : 10
WIFSIGNALED : 0

parent end

 

EXITSTATUS는 10임을 알 수 있는데 이는 위 코드에서 exit(10)을 사용했기 때문입니다. 시그널을 받지 않고 정상적인 종료를 한 케이스는 이렇게 나오는군요.

이제 background로 프로세스를 돌리고 kill해서 시그널을 보내보도록 하겠습니다.

# ./a.out &
[4] 5060
#     child process:5061

# kill 5061
# end pid : 5061
WIFEXITED : 0
WEXITSTATUS : 0
WIFSIGNALED : 1

parent end

[4]+  Done                    ./a.out

 

그렇다면 WIFEXITED는 false이고 WIFSIGNALED는 true네요. 이처럼 위의 매크로로 자식 프로세스의 종료 상태를 알 수 있습니다.

그런데 wait은 종료되는 자식 프로세스 중 먼저 종료되는 프로세스가 있다면 바로 호출되어지는데 만약 우리가 특정 자식프로세스만을 기다린다할때는 아래의 waitpid함수를 사용해야합니다.

 

waitpid

pid_t waitpid(pid_t pid, int *wstatus, int options);

역시 호출 성공시 그 프로세스의 pid, 아니라면 -1을 반환합니다. 

pid: 인자로 pid는 기다릴 프로세스 식별자인데 양수일 경우만 해당합니다. 아래는 pid에 따라서 어떻게 동작하는지를 설명합니다.

 

pid 설명
< -1  meaning wait for any child process whose process group ID is equal to the absolute value of pid. 
 -1 meaning wait for any child process. 
0 meaning wait for any child process whose process group ID is equal to that of  the calling process.
> 0  meaning wait for the child whose process ID is equal to the value of pid. 

 

wstatus : wstatus는 위의 wait과 같이 종지상태를 얻어옵니다.

option : option은 조금 더 구체적인 동작을 지정합니다. option의 종류는 아래와 같습니다.

 

OPTION 설명
WNOHANG return immediately if no child has exited. 
WUNTRACED also  return  if  a child has stopped (but not traced via ptrace(2)).  Status for traced children which have stopped is provided even if this option is not specified. 
WCONTINUED (since Linux 2.6.10) also return if a stopped child has been resumed by delivery of SIGCONT. 

 

wait(&status)은 waitpid(-1, &wstatus, 0)를 호출한 것과 같습니다.

 

waitpid 예제

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
        int state, cpid1, cpid2, endpid;

        if((cpid1=fork())==0){
                printf("\tchild1 process:%d\n",getpid());
                sleep(2);
                printf("\tchild1 end\n");
                exit(0);
        }

        if((cpid2=fork())==0){
                printf("\tchild2 process:%d\n",getpid());
                sleep(4);
                printf("\tchild2 end\n");
                exit(0);
        }

        endpid=waitpid(cpid1,&state,0);
        printf("pid : %d\n",endpid);

        endpid=waitpid(cpid2,&state,0);
        printf("pid : %d\n",endpid);


        printf("parent end\n");

}

 

여기서 자식 프로세스를 2개 생성하고 기다리는데 waitpid로 첫번째 자식 프로세스, 두번째 자식 프로세스를 기다립니다. 만약 여기서 두 번째 자식 프로세스를 기다리고 싶지 않다면 두 번째 waitpid를 하지 않으면 되겠죠.

결과는 아래와 같습니다.

#gcc process_waitpid.c
# ./a.out
        child2 process:5614
        child1 process:5613
        child1 end
pid : 5613
        child2 end
pid : 5614
parent end

 

 

만약 두번째 프로세스가 끝났을때는 그대로 종료하지만 작업이 끝나지 않았을때는 A라는 작업을 해야하는 상황이라면 어떻게 해야할까요? 

wait은 계속 자식 프로세스가 끝날때까지 block되었는데 waitpid는 옵션을 사용하면 됩니다. 

 

아래가 그 예제입니다.

 

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
        int state, cpid1, cpid2, endpid;

        if((cpid1=fork())==0){
                printf("\tchild1 process:%d\n",getpid());
                sleep(2);
                printf("\tchild1 end\n");
                exit(0);
        }

        if((cpid2=fork())==0){
                printf("\tchild2 process:%d\n",getpid());
                sleep(4);
                printf("\tchild2 end\n");
                exit(0);
        }

        while(1){
                endpid=waitpid(cpid2,&state,WNOHANG);
                if(endpid==0){  //child not finished if pid = 0
                        printf("child2 not finished\n");
                        sleep(1);
                }else{
                        break;
                }

        }

        printf("parent end\n");

}

 

위 코드에서는 첫번째 자식 프로세스는 기다리지 않습니다. waitpid에 WNOHANG 옵션을 주어서 자식이 종료되었는지 아닌지에 따라 작업을 구분지을 수 있습니다. 이때 waitpid는 지정된 프로세스가 끝나지 않았다면 0을 반환하고 끝났다면 그 pid를 반환하게 됩니다.

결과는 아래와 같습니다.

# gcc process_waitpid2.c
# ./a.out
child2 not finished
        child2 process:5670
        child1 process:5669
child2 not finished
        child1 end
child2 not finished
child2 not finished
        child2 end
parent end

 

waitpid가 wait에서 제공하지 못하는 3가지를 제공합니다.

1. waitpid함수로 특정 pid를 기다릴 수 있습니다. wait은 임의의 자식에 대해서 종료상태를 알려주지요.

2. waitpid는 wait과는 달리 자식이 종료될때까지 기다리지 않아도 됩니다. 바로 호출을 할 수 있습니다.

3. WUNTRACED와 WCONTINUED 옵션을 통해서 작업 제어를 할 수 있습니다.

 

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,