프로테우스로 ATmega128 개발 키트 KUT-128 구현

- 인터럽트 처리를 위한 스위치, LED, FND 4개 정도만 일단 구현함

기본 블링크 예제

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>


int main()
{
	DDRC = 0xff;
	while(1){
		PORTC = 0xf0;
		_delay_ms(500);
		PORTC = 0x0f;
		_delay_ms(500);
	}
}

 

 

기본 FND 예제

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>

int main()
{ 
   char i, val;

   DDRB = 0xff;
   DDRF = 0xf0;
   PORTF = 0b11100000;
   
   while(1){
      val = 0b00000001;
      for(i = 0; i < 7;i++){
	 PORTB = val;
	 _delay_ms(500);
	 val = val << 1;
      }
   }
   
   return 0;
}

 

 

 

 

FND 9999 시뮬레이션 실패?

- FND 4개로 9999찍으려 했지만 _delay_ms를 낮게 설정해서 real time으로 동작되지 않는다.

- 거기다 실제 하드웨어처럼 잔상이 남지 않아 4개의 수가 동시에 나오지 않는다.

 

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>

char seg_pat[16] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07,
		  0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};

void seg4_out(int);
		  
int main()
{ 
	int num = 0;
	
	DDRB = 0xff;
	DDRF = 0b11110000;
	while(1){
		seg4_out(num);
		num++;
		if (num > 9999)
			num = 0;
	}
}

void seg4_out(int num){
	int i, N1000, N100, N10, N1, buf;
	
	N1000 = num / 1000;
	buf = num % 1000;
	N100 = buf / 100;
	buf = buf % 100;
	N10 = buf / 10;
	N1 = buf % 10;
	
	for(i=0;i < 10;i++){
		PORTF = 0b11100000;
		PORTB = seg_pat[N1];
		_delay_ms(10);
		
		PORTF = 0b11010000;
		PORTB = seg_pat[N10];
		_delay_ms(10);
		
		PORTF = 0b10110000;
		PORTB = seg_pat[N100];
		_delay_ms(10);
		
		PORTF = 0b01110000;
		PORTB = seg_pat[N1000];
		_delay_ms(10);
	}
}

 

fnd 2개도 제대로 안된다. 인터럽트랑 같이 쓰고싶었는데

 

 

 

FND, INT 예제

- FND 2개도 못쓰니 그냥 FND 여러개 달린걸 써야할거같긴한데 그러면 원래 KUT-128 보드를 바꾸는거라 어떻게 할지 고민이다.

- 대신 외부 인터럽트로 FND 1개의 숫자 올리는 예제를 구현했다.

- 근데 mega128.h가 아닌 avr/interrupt.h를 사용해서 기존 보던 책과는 조금 다르게 ISR(INT4_ vect)로 인터럽트 정의한다.

- 근데 전역 변수 N1을 선언하고 썻는데 제대로 인터럽트 동작이 안되더라.

 => 아래의 링크를 보니 전역 변수에 volatile을 줬더니 문제없이 동작한다. 찾아보니 최적화 과정에서 지워졌나보다.

https://exploreembedded.com/wiki/AVR_External_Interrupts

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>

char seg_pat[16] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07,
		  0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};

volatile int N1 = 0;
		  
int main()
{
	DDRB = 0xff;
	DDRC = 0xff;
	DDRF = 0b11110000;
	
	EICRB = 0b00000010;
	EIMSK = 0b00010000;
	SREG = 0x80;
	PORTF = 0b11100000;
	
	while(1){
		PORTB = seg_pat[N1];
		PORTC = 0xff;
		_delay_ms(1000);
	}
}

ISR(INT4_vect){
	N1 = (N1 + 1) % 10;
}

 

진도를 더 나가고 싶긴한데, 책을 두고와서 더 나갈수가 없다 ;;

내일 타이머랑 통신으로 넘어가야지

프로테우스 짱짱맨

아두이노, 센서 없어도 시뮬레이션으로 돌려볼수 있다.

아두이노 뿐만 아니라 다른 MCU, 라즈베리파이도 가능

 

 

LCD LM016L 사용하기

- RS 레지스터 선택 : 12번 핀
- E Enable : 쓰기
- 4비트 or 8비트 모드 사용 가능 : 4비트 모드 사용

 


'

 

 

 

초음파 센서 시뮬레이션

 

 

DC 모터를 이용한 선풍기

 

 

 

 

서보모터 제어하기

- SG90 서보모터 : 180도 회전, PW가 1 ~ 2ms 변화-> HIGH 1ms는 -90도, 2ms는 90도

- 이건 잘 동작안하는데, 올바른 서보모터 파츠를 찾아야할거같다.

 

 

포프TV 

2016년 경 내가 처음 코딩 공부하기 시작하면서

잠깐 본적이 있었지만 얼마 전까지만해도 이 유튜버 분이 있는걸 잊고 있었다.

 

어찌저찌하다 보니 유튜브 알고리즘이

정말 오랜만에 이 분 영상을 추천해주길래 보게됬었는데

우리나라 개발자들 중에서 이만큼 통찰력있는 영상을 많이 올리신 분이 계신가 싶다.

 

잠깐 나무위키를 보니 정말 대단한 분이시더라.

 

이전에 학교에서 어린 학생들 가르치는 코딩 강사 사람들이

본인 이해 관계와 이득 때문에 타인에게 날조하고, 누명씌워서 피해자인양 온갖 난리 치는 꼴을 구역질나게 본적이 있었고

가상 머신 CPU, RAM 설정을 몰라서 렉걸리는 가상 머신으로 교육하는 리눅스 강의 하는 겸임 교수

 

심심하면 코딩 부트캠프니, 네카라쿠베니, 자기들 따라오면 연봉 기본 4-5000 받느니 하는 내용들이 너무 많다보니

물론 좋은 분들도 계신건 알면서도

개발자 한다거나 교육한다는 사람들은 겉멋만 들거나, 사기꾼 투성이인가 불신이 들때가 많았었다.

 

 

 

 

 

그러다가 최근 책만 보지 않고,

오지환님이나 널널한 개발자님같은 유튜브 영상을 보던중에

이번에 포프TV 내용을 보면서 국내에 이렇게 까지 통찰력을 주는 주시는 분이 있다는 것에 놀랐다.

 

이전에 책 리뷰하면서 반성글 썻을 때 처럼

사실 내가 제대로 안찾아본거긴 하지만

국내에서 제대로 된 자료를 찾지 못해 해외 유튜버든 자료든 찾어도 이해하는데 한계가 있어서 좌절할 때가 많았다.

 

그러다가 이제서야 포프TV를 보면서

이론, 실습뿐만이 아니라

고민과 생각, 통찰을 할 수 있는 내용들을

이렇게 까지 주요 컨텐츠로 많이 올리시다보니

 

공부 하기 싫을 때 좋은 딴짓 거리라 생각이 든다.

몇년 전에 종종 보던 유튜브 채널인데

 

 

오랜만에 유튜브 알고리즘이 추천해주길래 다시봤다.

 

오제이튜브 오지환님.

 

개발 관련해서 이것 저것 조언해주시는 영상들이 많이 올라오는 편이다.

 

 

 

어떻게 하면 괜찮은 프로그래머가 될지, 진로 상담이나 강의 등등

 

나는 최소한의 코딩 감각은 있긴한데

 

워낙에 적당주의자라 끈기 있으면 있을때도 있고, 없으면 없을때도 있고

공부야 한두번 훑어보다 만게 대부분이고, 조금 봤다 싶은건 잊어먹은것도 많다보니

 

안그래도 소통도 잘못하는데 괜찮은 프로그래머가 되기엔 멀어보인다.

 

오늘 남인천 폴리텍 반 전체로

스마트 테크 코리아에 참관하게 되서 코엑스에 다녀왔는데,

 

이전에 3년 전쯤에 IOT 박람회인가 그 때 잠깐 방문하고, 정말 오랜만에 왔다.

 

여기 주제가 로보 테크랑 뭐 몇가지가 있었는데 그냥 편한 마음으로 구경한게 다라 그냥 사진, 영상 찍은거 몇개 올리고 말란다.

 

 

코엑스 건물 안 

 

 

 

 

시작할때 쯤되니 바글바글하다.

 

 

언제되면 이런걸 진짜 볼수있을까 싶긴한데

무인 자판기들

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

지나가다 보니 치킨도 나눠주고 있던데

 

매니퓰레이터로 튀긴 치킨이니까 로봇 치킨..?

 

 

 

 

혼자서 이리저리 다니던 서빙 로봇

 

 

 

 

 

 

 

 

 

 

예전에 인터넷으로 물건 사고 팔때 어떻게 빨리 배송받을 수 있는지 유튜브 정리한 영상을 본적이 있는데

잘 기억은 나진 않는다.

풀필먼트 창고였나? 그런 이름이었던거같은데

 

예전에 본 다큐에서는 바닥을 기어다니는 AGV가 창고에서 물건 가지고 다녔다면

요즘에는 이렇게 아래에 넣어둿다가 꺼내는 식인것 같았다.

 

 

 

 

 

 

 

 

로봇 관련 매체에서 한번쯤은 꼭 나오는 로봇 카페

여기서 실제로 처음봤는데

주문해서 음료 받을수 있게 되어있더라

 

 

 

주문 한 레모네이드

 

 

 

 

내가 주문한 레모네이드를 전달해주는 과정

 

 

 

 

 

 

 

 

로봇은 아니지만 홀로그램 제품도 소개하고 있었다.

기회 되면 이런걸 배워서 좀 만들어보고 싶다.

 

 

홀로그램 영상이 회전하면서 볼수 있는데,

옆면 유리에서도 보일지 궁금해서 봤지만 옆면은 안보이고 정면에만 보였다.

 

 

대충 밥먹고 잘 구경하다 왔다.

아 이력서 쓰고 공부해야되는데 귀찮다.

시간 조금 여유있으니까 의욕이 너무안생기네

최근 공부하다가 뭐더라.. 프로테우스 책 보면서 시뮬레이션 돌리고
이력서 썻다가 시험보던중에 아
 
내가 공부하는데 질리던 시점이였다.
예전이였다면 그대로 공부하지 않았겠지만
 
지금은 내가 공부를 지속하는건 고문같이 느껴지니 
잠시 다른 곳으로 눈을 돌릴 시점이라 생각되었다.
그런 이유로 이력서, 자격증 준비를 한거기도하고
 
 
아 이력서를 쓴다고 하는데 내가 어떻게 살아왔는지 전체적으로 쭉 정리하는 시간을 가졌었다.
기업에서 내가 어떻게 살아왔는지 관심없다는건 알고 있긴했지만 한번 블로그에 올려볼까 싶기도하고 전체적으로 글로 정리해보고 싶었기 때문이었다.
이렇게 적으면 무의식적으로는 더 내려놓을수 있을까 싶기도 했고
공부하기 싫으니 자기전에 이불킥하는 상상에 빠지듯이 나를 되돌아보는 글을 쓰는데 빠졌다.
 
그건 그렇고
평소 여러 유튜버 분들의 이야기를 봤다가 
오늘 재미있는 유튜버분을 찾았는데
 
감성대디 데니스님이시다.
 
 

 

이 분 영상을 보면 호구 잡히지 않는법이나

여러 사람을 만나면서 겪은 일들 자신만의 통찰을 잘 전달해주고 계신다.

주제야 다 모든 사람들이 겪고 고민해봤을 일이라 흥미를 가질 내용들이 꽤 많고

나한탠 가장 이해가 되지 않고 취약한 부분이라 꽤 재밌게 봤다.

 

평소에도 내가 잘못 됬을지도 모른다는 생각을 기본적으로 갖고 지내기 때문에 

공부하는 것 외에는 가능한 자기 확신을 하려고 하지는 않는데

 

 

내가 정말 이상한것인지,

내가 피해망상에 시달리는건지

공격을 받는 것인지

잘 구분이 되지 않는다.

 

내가 왜 이 말을 썻지. 분명 뭔가 생각난게 있었는데

그냥 이때 무슨 생각이 지나갔는데, 그냥 지나가서 날리긴 아까워서 이렇게 끝낸다.

 

 

 

 

이 책을 완전히 다본건 아니지만

네트워크 파트나 몇가지 부분만 빼면 거의 다 봤다.

 

이책은 원래 이만우 님인가? 아 이만우 님의 임베디드 리눅스 os 만들기 책을 보면서 나빌로스 만들기를 진행하다가

리눅스 시스템에 대해서 좀 봐야겠다 싶어서 이것 저것 찾아보다가

가장 넓고 얕게 배우는 책인것 같아서 보게 되었다.

 

리눅스 구조와 제공하는 다양한 API, 프로세서, 네트워크까지

보통 다른 리눅스 책에서는 스케쥴링이나 시그널 처리 같은 것들을 깊이 다루지만

이 책에서는 일부 영역에 깊이 하기보다는 가볍게 볼수 있도록 깊지 않고 가장 넓게 볼수있는 책이었다.

 

대강 당장 궁금했던 리눅스 구조나 개념들에 대해서 거의 다 살펴봤고

네트워크 파트나 make에 대한 내용도 더 진도나가고는 싶었지만

몇일간(길지도 않았지만) 잠깐 하면서 당장 리눅스에 대해서 궁금했던 부분도 어느정도 해소됬고,

계속 이걸 하기에는 동기부여가 잘안되서

 

이제 다음으로 곧 3d 프린터 운용기능사 실기 시험이있어서 실기시험을 준비하면서

지난번에 하다만 나빌로스를 진행해볼까 싶다.

 

그래도 이 책을 보고 공부하면서 간단한 디버깅 방식이나 시스템 콜, 가상 메모리가 뭔지, 디렉터리 구조 등 

이전보다는 약간이나마 더 정리하면서 도움됬던거 같다.

 

나빌로스 만들기를 진행할수도 있고, 결국에는 이런걸 공부해서 간단한 프로토 타이핑을 해보는게 목표라

이용성 교수님이 유튜브에 올리신 프로테우스로 센서를 만들고 시뮬레이션을 돌려보거나,

'실습과 그림으로 배우는 리눅스 구조' 혹은 '디버깅을 통해 배우는 리눅스 커널의 구조와 원리' 이런 책을 볼까 싶다.

 

최근에서야 책을 많이 읽으면서 알게 된게, 이런 책들이 다 리눅스를 다루고있기는 하지만 각 책마다의 컨셉이 있다는 점과

바로 내용이 많은 책을 보기 보다는 당장 나한태 너무 어렵지도 않고, 잘 전달하는지 그러니까 나한태 가장 잘 맞아보이는 책을 골라서 보는게 중요한걸 느꼈다.

 

 당장 아무생각없이 산 책이 나한태 너무 어렵거나 쉬워서 안 맞는데 돈아깝다고 보다가 재미없어서 포기했던 적이 여러번 있었는데 그럴때 능동적으로 나한태 조금 쉬운 책을 보고나서 준비 되었을때 조금 어려운 다음 단계 책을 봤었더라면 그렇게 쉽게 흥미를 읽지 않았을까 싶은 생각이 든다. 공부 순서가 중요한 만큼 책 보는 순서도 중요한거같다. 

 

내가 이번에 본 '모두를 위한 리눅스 프로그래밍'이라면 리눅스 초심자(명령어 같은건 조금은 아는) 리눅스 프로그래밍 전반을 넓고 얕게 배운다면,

'실습과 그림으로 배우는 리눅스 구조'는 다른 책들보다 추상적인 리눅스 내부 구조를 이해하기 좋게 꽤 많은 그림과 예제코드를 제공하고 있었다.

'디버깅을 통해 배우는 리눅스 커절 구조와 원리'이 책은 아직 구입하지 않아서 미리보기만 봤지만, 보통 임베디드시스템 교재하면 비싸고 구하기 힘든 atmega나 stm 교육용 보드 구입을 전제로 하고 있는것에 비해 여기서는 가장 쉽게 구할수 있는 교육용 플랫폼인 라즈베리 파이를 사용한다는 점이 좋아보이고, 나한태 필요한 디버깅 연습을 하면서 '모두를 위한 리눅스'보다는 조금 더 깊이 보는데 도움될거같다.

 

그리고 요즘 책보는데 아낌없이 투자하면서 보고있는데, 진작에 이랬어야 했다는 생각이 든다. 그 동안 돈이 아깝다는 이유로 원서 pdf를 보거나 무료 강의 같은걸로 공부해 왔었는데, 돈이 아깝다고 국내 서적을 보지 않고 부족한 영어 실력으로 원서를 보려고 하다 금방 흥미를 읽었던 적이 정말 많았다.

 

 가뜩이나 영어를 그렇게 잘하는것도 아니면서 모르는 내용을 원서로 공부하려하다가 포기하기를 반복해놓고, 국내에는 어떤 책들이 있는지 찾아보지도 않아놓고 윤성우의 c, 소켓 예전에 한두번 본거가지고 우리나라에는 코딩 문법만 가리키는 기본서밖에 없어서 우리나라에는 볼만한 책이 없다느니 하는 생각을 가지고 있었다.

 

 내가 나한태 필요한게 무엇인지 당장에 힘들어서 고민하지도 필요한걸 자세히 찾지 않다보니까, 대충 보이는 대부분의 IT 서적들이 국비학원생처럼 양산된 코딩 문법 강사들이 쓴 기초책 밖에 없어보였고, 해외에서 번역해온 전문 서적도 엉터리로 번역된게 많았었다.

 

 하기사 내가 책을 좀 찾아보려 했던게 3 ~ 4년 전인데 그 땐 지금처럼 프로그래밍 관련 수학이나 인공지능, ROS, 컴퓨터 비전책이 그렇게 많지 않아서 좋아보이는 책이 표윤석 박사님의 거북이 책, 오일석 교수님의 머신비전/컴퓨터비전 등 10개도 넘지 않을 정도로 그렇게 없었던거같다.

 

 수학 책은 컴퓨터 과학 영역을 고려해서 나온 책도 별로 없었고, 인공지능 책도 3분 딥러닝 텐서플로맛인가? 지금처럼 얼마 없긴했고, 그나마 전문적인 제목으로 무슨 무슨 프로젝트니 하는 packt 책들은 하나같이 번역기 돌린것같은 내용 뿐이었으니 우리나라에는 엉터리 책뿐이야!라고 생각해놓고 제대로 공부하는거 없이 시간이 지금까지 와버리고 말았다. 적어도 리눅스 시스템 프로그래밍 분야는 꽤 괜찬은 책이 많았는데 이쪽을 볼 생각을 못하고, 여유있지도 못했고

 

 아무튼 요즘은 정말 친절하게 쓴 책들이 많아져서 도움받을데 없이 혼자 삽질하고 있는 나한태는 이전보다는 공부하기 좋아진거같다.

 

컴퓨터 구조 요약

- CPU - RAM, HDD/SSD, IO 장치

 

멀티 태스크와 가상 CPU

- 한 순간에 CPU는 하나의 프로세서만 처리 가능. 시간을 잘게 나누어 여러 프로세스를 돌아가며 처리하여 다중 작업 수행

- 가상 CPU : 멀티 태스크 작업을 위해 하나의 CPU를 시간을 나눠 스케줄링을 통해 여러 작업을 동시 수행하는 것

 

가상 메모리 : 부족한 1차 메모리 공간을 보완하고, 프로세서로부터 OS를 보호하기 위한 방법.

- 각 프로세서는 2^32 = 4gb 정도의 가상 메모리 공간을 가짐, 4kb 고정 크기 페이지로 나뉘어짐

- 각 가상 메모리는 코드 영역(프로그램), 데이터 영역(정적변수), 힙(동적할당), 스택(매개변수, 지역변수) 영역으로  구분  

- 프로세서는 메모리 관리 유닛 MMU/운영체제에 의해 가상 메모리 주소가 1차+ 2차 메모리 물리 메모리 주소다 맵핑

   -> 실제 물리주소는 OS가 관리하여 프로세서가 타 프로세서의 공간을 읽고쓰기는 것을 방지 => OS 보호

- 각 프로세서는 큰 가상 메모리 공간을 가져도 실제 사용하는 곳만 1차 메모리다가 할당. 쓰지 않는 건 2차 메모리서 보관

- page in(swap in), page out(swap out) : 2차 메모리에 있는 걸 1차메모리로 옮겨와 사용(page in), 반대의 경우 (page out)

- IPC 프로세서간 통신 : OS가 두 ps간 한 가상 주소를 같은 물리 주소로 맵핑시 ps끼리 메모리 공유 = 서로RW가능 = 통신

- memory mapped file : 파일을 메모리에 맵핑 -> 메모리 RW = 파일 RW (시스템 콜 : mmap)

 *nand2tetris 에선 memory mapped io , io장치 입출력을 메모리에 맵핑하고 그 매모리 값을 읽고쓰면서 제어함.

 

메모리 API

#include <stdlib.h>

- void *malloc(size_t size); : size만큼 동적 메모리 공간(힙) 할당. 시작 주소 void *로 반환

- void *calloc(size_t nmemb, size_t size); : nmemb x size 를 힙에 할당, malloc과 차이는 초기값 0 설정

- void *realloc(void *ptr, size_t size) : malloc or calloc으로 할당된 공간을 축소하거나 늘림.

- void free(void *ptr); : 동적할당 해제

 

빌드 과정

- 전처리 : 헤더 파일 인클루드, 매크로 처리 

- 컴파일 : 소스 코드 -> 어셈블리 파일

- 어셈블 : 어셈블리 파일 -> 오브젝트 바이너리(ELF, a.out <- aseember output)

- 링킹 : 오브젝트 바이너리와 필요한 라이브러리 연결하여 최종 실행파일 or 라이브러리(*.a or *.so) 만듬

 

라이브러리

- 정적 라이브러리 : 컴파일시 포함, *.a(리눅스)/*.lib(윈도우) 파일

- 공유/ 동적 라이브러리 : 빌드 시 체크, 링크 로더가 런타임 시작 시 포함. *.so(리눅스), *.dll(윈도우)

 -> 보통 동적 링크 사용

- file 명령어 : 어떤 플랫폼 실행파일인지, 어떻게 링크 됬는지 확인 가능

- ldd 명령어 : 어떤 lib가 링크되고 링크 로더가 뭔지확인, 아래의 경우 libc.so.6가 링크됨. 링크 로더는 /ld-linux-x86-64.so.2

 

프로세스 생성, 명령어 호출, 대기 API

#include <unistd.h>

- pid_t fork(void) : 자식 프로세스 생성. 부모는 자식의 pid 반환, 자식은 0반환. 이후 자식은 exec로 타 작업 수행

- int exec(const char *path) : path 명령어 수행, 반환이 없으나 실패시 -1, errno에 에러코드 등록

  1. 변형 :  execl, execlp, execle 등 존재

  2. l은 실행 인자를 가변으로 전달(마지막엔 NULL), v는 벡터 실행 인자 전달

  3.  e는 마지막에 환경 변수 추가, p는 프로그램을 환경변수 PATH보고 탐색(없으면 절대/상대경로로 찾음)

  4.  ex) execl("/bin/cat", "hello.c", "ls.c", NULL);

#include <sys/types.h>

#include <sys/wait.h>

- pid_t wait(int *status) : 자식 프로세스 중 아무거나가 끝날때까지 기다림. status에는 exit()인자인 종료코드와 종료방법 플래그합친거

- pid_t waitpid(pid_t pid, int *status, int options) : 지정한 pid의 자식이 종료될때까지 기다림. 

 -> 아래는 종료 방법 매크로 

  1. WIFEXITED(status) wait if exited : exit로 종료한 경우 0 아님 -> exit로 종료시 0이 아닌 값이 반환

  2. WEXITSTATUS(status) wait exit status : exit 종료시 종료코드 반환

  3. WIFSIGNALED(status) wait if signaled : 시그널로 종료시 0 아님

  4. WTERMSIG(status) wait term signal?: 시그널 종료 시 시그널 번호 반환

 

프로세스 활용 예제

- spawn.c : 첫째 인자는 실행 프로그램 경로, 둘째 인자는 전달할 인자로 해당 프로그램 실행하는 예제

 

 

프로세스 사이클

#include <stdlib.h>

- void exit(int status) : status는 종료코드(리눅스서 0 성공, 1에러,딴데선 다를수있음), stdio 버퍼 해제(_exit()는 시스템콜)

- 좀비 프로세스 : 종료되었지만 커널 상 남은 ps, fork 시 무조건 wait 사용 or 이중 fork or sigaction으로 해결

  1. 자식 ps가 exit로 끝나면 부모 ps서 wait으로 결과 받음

  2. 부모 ps서 wait이 없거나 늦게 호출할경우 커널이 자식 ps의 상태 보관

  3. 자식은 다끝났는데, 자식 상태를 계속 보관 -> 늘어나면 문제

- 이중 fork

  1. 부모 ps가 fork -> 자식 ps 생성

  2. 자식 ps fork-> 손자 ps 생성한 형태

  3. 자식 ps 종료시 wait로 손자 종료 코드 못받으므로 손자는 정리됨(좀비 ps x)

 

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

int main(int argc, char *argv[])
{
    pid_t pid;

    if (argc != 3){
        fprintf(stderr, "Usage : %s <command> <arg>\n", argv[0]);
        exit(1);
    }
    pid = fork();
    if (pid < 0){
        fprintf(stderr, "fork(2) failed\n");
        exit(1);
    }
    // child process
    if (pid == 0){
        execl(argv[1], argv[1], argv[2], NULL);
        perror(argv[1]);
        exit(99);
    }
    else{
        int status;

        waitpid(pid, &status, 0);
        printf("child (PID=%d) finished; ", pid);
        if (WIFEXITED(status))
            printf("exit, status =%d\n", WEXITSTATUS(status));
        else if (WIFSIGNALED(status))
            printf("signal, sig=%d\n", WTERMSIG(status));
        else
            printf("abnormal exit\n");
        exit(0);   
    }
}

 

 

파이프

- 프로세스 끼리 연결한 스트림. 부모, 자식 ps간에 pipe로 연결

 #include <unistd.h>

- int pipe(int fds[2]) : 두 ps  파일 디스크립터 fds[0]은 읽기 fds[1]은 쓰기 전용

- 파일디스크립터복제 : int dup(int oldfd), int dup2(int oldfd, int newfd) - 복제로 생성한 fds반환, 새 fds는 기존 fds계속반영

- 3번 파이프를 0번으로 옮기기 : 1. close(0) : 0번 비우기, 2. dup2(3, 0) : fds 3을 fds0 복제, 3. close(3) : 기존 fds 3 해제

#include <stdio.h>

- 위 시스템 콜 파이프는 쓰기 힘듦, stdio서 쉬운거 제공

- FILE *popen(const char *command, const char *mode) : command 프로그램 실행, 파이프 연결, 스트림 반환

- int pclose(FILE *stream) : popen으로 fork, exec 한 자식 ps에 대해 wait 수행,  파일 스트림 해제

 

 

프로세스 관계

1. 부모, 자식 관계 : pstree로 조회 가능, 최 상위 systemd가 커널이 가장 먼저 실행하는 모든 ps 시작점

#include <sys/types.h>

#include <unistd.h>

-> pid_t getpid(void), pid_t getppid(void) : 자식, 부모 pid 취득

2. 타 ps 정보 조회 : ps 파일 시스템 /proc에서 확인 or pstree or ps

3. 프로세스 그룹 & 세션 : 프로세서 세션 - 단말 단위, 프로세스 그룹 - 파이프로 연결된 ps들 단위

 + ps 그룹 리더 : 처음 ps 그룹 만든 ps (최상위 부모) -> PID =PPID

 + ps 세션 리더 : 처음 세션 만든 프로세스, PID = SID(세션 ID)

4. 데몬 PS : 단말이 없는 ps -> 서버 동작용(단말 접속과 무관하게 동작함)

#include <unistd.h>

-> int setpgid(pid_t pid, pid_t pgid) : 프로세스 그룹 설정, 현재 ps로 새 psg 만들 경우 pid와 pgid 둘다 0 설정

-> pid_t setsid(void) : 새 세션 생성, 현재 ps가 세션 리더 * 현재 ps가 psg 리더인경우 세션 리더 설정 불가

 


1. pstree 예시 : WSL 터미널에서 해서 그런가 ps도 거의없고 보통 환경이랑 좀 다르다.

2. /proc에서 보는 ps 상태

3. ps 그룹, 세션 조회 - ps j

4. 모든 프로세스 및 데몬 프로세스 조회 : 데몬은 tty가 없어 ?로 표기됨

 

 

시그널

- 단말(사용자) 혹은 커널이 ps에 통지시 사용(인터럽트와 유사)

- 시그널 전달 시 동작

 1. 무시 (SIGCHLD 시그널, 자식 ps 종료시 전달) -> 무시 대신 시그널을 어떻게 처리(catch)할지 설정도 가능함 

 2. ps 종료 : ctrl + c으로 sigint 발생

 3. core덤프 작성후 종료  : sigsegv, 접근 불가한 메모리 접근시 세그먼테이션 폴트 발생, 코어 덤프 작성(메모리 스냅샷)

 

 

시그널 처리 API

#include <signal.h>

- int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact) : sig에대한 시그너 핸들러 act 지정, oldact는 예전핸들러 NULL해도됨

struct sigaction{
	/* sa_handler 혹은 sa_sigaction 둘중 하나만 씀*/
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t*, void*);
    sigset_t sa_mask;
    int sa_flags;
}

- act에 아래의 구조체 사용 해서 시그널 액션 등록. 한번 설정한 시그너 핸들러는 계속 유지, sa_flags에 SA_RESTART 추가시 시스템 콜 재시작(보통 사용), sa_mask로 차단할(블록할) 시그널 지정가능-핸들러 처리중엔 타 sig방지sigemptyset() 

//sigaction 예시
#include <signal.h>

typdef void (*sighandler_t)(int);

sighandler_t trap_signal(int sig, sighandler_t handler){
	struct sigaction act, old;
    
    act.sa_handler = handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_RESTART;
    if (sigaction(sig, &act, &old) < 0)
    	return NULL;
    return old.sa_handler;
}

 

#include <sys/types.h>

#include <signal.h>

- int kill(pid_t pid, int sig) : 시그널 전송 함수, 해당 pid를 가진 ps에 sig 전달, 이름이 sendsig가 맞으나 오래되서 그럼

- ctrl + c : sig int 전달하여 종료

 

 

 

디렉터리 처리

#include <unistd.h>

- char *getcwd(char *buf, size_t bufsize); : 현재 ps의 경로 출력

- int chdir(const char *path) : ps의 현재디렉터리 변경

 

 

환경변수 처리

- ps에 의해 전파되는 전역변수

- ex : PATH(명령어 찾을 경로), TERM(사용 단말), LANG(사용자 로케일), LOGNAME(로그인명), TEMP(임시파일경로), DISPLAY(x 윈도우 용 기본 디스플레이)

- 전역변수 environ(char** 타입)으로 접근 가능

#include <stdio.h>
#include <stdlib.h>

extern char **environ;

int main(int argc, char *argv[])
{
    char **p;

    for(p = environ; *p; p++){
        printf("%s\n", *p);
    }
    exit(0);
}

#include <stdlib.h>

- char *getenv(const char *name) : 해당 이름의 env 가져오기

- chat *putenv(const char *string) : 환경변수 등록 string은 "이름=값"형태

 

 

 

로그인 처리

1. systemd서 단말 갯수만큼 getty 명령어 수행

2. getty는 사용자 입력 기다림 -> login 명령어 시작

3. login cmd로 사용자 인증 -> 셀 시작

- getty 하는일 : 터미널을 open, read해서 사용자 정보 입력받고 login exec -> dup로 0~2번 fds를 새단말과 연결&login반복

- login 하는일 : 사용자 인증, /etc/passwd에 암호 저장(요즘은 보안을 위해 셰도 패스워드 사용)

- 셸 시작 : execl("/bin/sh", "-sh", ...); : 이 명령어로 로그인 셸 실행

https://jhnyang.tistory.com/110

 

 

grep 명령어 

 

- ./grep 정규표현식_패턴 파일명 ...
 
 
정규표현식 규칙
. : 한 문자
* : 선형 패턴 0회 이상 반복
? : 생략가능
\ : 메타문자

 

libc에서 제공하는 정규표현식 API
#include <sys/types.h>
#include <regex.h>
 
int regcomp(regex_t *reg, const char *pattern,
     int flags); : pattern에 든 정규표현식 문자열을 regex_t로 반환, 결과는 reg에
void regfree(regex_t *reg); : regcomp로 확보한 메모리를 해제
int regexec(const regex_t *reg, const char *string,
    size_t nmatch, regmatch_t pmatch[], int flags); : regex_t(패턴) + 문자열로 패턴찾음, 성공시 0 아니면 다른 값 반환
size_t regerror(int errcode, const regex_t *reg,
    char *msgbuf, size_t msgbuf_size); : regcomp서 에러발생시 에러 코드 반환, 이 에러코드를 메시지로 변환

 

 

grep 명령어 구현 코드 및 결과


static void do_grep(regex_t *pat, FILE *src);

int main(int argc, char *argv[])
{
    regex_t pat;
    int err;
    int i;
    
    if (argc < 2)
    {
        fputs("no pattern \n", stderr);
        exit(1);
    }

    err = regcomp(&pat, argv[1], REG_EXTENDED | REG_NOSUB | REG_NEWLINE);
    if (err != 0){
        char buf[1024];
        regerror(err, &pat, buf, sizeof buf);
        puts(buf);
        exit(1);
    }
    if (argc == 2){
        do_grep(&pat, stdin);
    }
    else {
        for (i = 2; i < argc; i++){
            FILE *f;
            
            f = fopen(argv[i], "r");
            if (!f){
                perror(argv[i]);
                exit(1);
            }
            do_grep(&pat, f);
            fclose(f);
        }
    }
    regfree(&pat);
    exit(0);
}


static void do_grep(regex_t *pat, FILE *src)
{
    char buf[4096];

    while (fgets(buf, sizeof buf, src))
    {
        if (regexec(pat, buf, 0, NULL, 0) == 0){
            fputs(buf, stdout);
        }
    }
}

 

 

리눅스 디렉터리 구조

- 최상위 디렉터리인 루트 /를 시작으로하며, 디렉터리 트리라고도 함.

 

 

/ : 최상위

/bin :  시스템 실행 파일(명령어) 보관  <-> /usr/bin : 일반 사용자를 위한 실행파일

/sbin : 관리자용 명령어

/lib : 시스템  라이브러리 디렉터리       <->  /usr/lib : 유저 라이브러리 디렉터리

/usr : 여러 컴퓨터에서 같이 쓸수있는 sw 파일 보관, /usr/bin, /usr/sbin, /usr/lib, /usr/share

/usr/src : 시스템 실행파일/리눅스 커널 소스코드 보관, 사용자가 직접 작성한건 보관 x

/usr/include : 시스템/커널 헤더파일 보관

/usr/share : 서로 다른 아키텍처에서도 사용가능한(공유가능한) 파일들 존재 ex) man, info 같은 다큐먼트

   -> /usr/share/man 

/usr/local : /usr는 배포판이 관리, /usr/local은 시스템 관리자(사용자) 본인이 관리

/var : 자주 바뀌는거 ex) 로그, 프로세스ID

/etc : 시스템 설정 파일

/dev : 디바이스 파일

/proc : 프로세스 파일 시스템에 의해 프로세스가 파일로 표현됨

/sys : sysfs로 시스템 관련 정보 보관(프로세스가 /proc에 있어 그 외 정보) ex) 디바이스, 디바이스 드라이버

/boot : 리눅스 커널 vmlinuz 보관

/root : su/root의 디렉터리

/tmp, /var/tmp : 임시 파일

 

 

 

 

mkdir, rmdir 함수 API

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

int mkdir(const char *path, mode_t mode); : path로 한 dir 만듬, mode는 권한 지정
- mkdir 에러 종류
ENONET - 상위 디렉터리가 없는경우
ENOTDIR - 상위 디렉터리가 디렉터리가아니라 파일인경우
EEXIST - 이미 존재하는경우
EPERM : 변경 권한이없는경우
#include <unistd.h>
int rmdir(const char *path);
 
 
umask
 
mode_t umask(mode_t mask); : ps의 umask를 mask로 변경, 이전의 umask 반환, 절대 실패 x
 * umask : ps 속성으로 8진수 보통 022, mode(파일,dir 권한)가 그대로 사용되지 않고 umask 적용된 결과가 사용됨.
        open(), mkdir()시 mode & ~umask한게 실제 권한으로 사용됨  
                  rwx rwx rwx
    mode     777 : 111 111 111
    umask   022 : 000 010 010

    real       755 : 111 101 101

 

 

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>

int main(int argc, char *argv[]){
    int i;

    if (argc < 2){
        fprintf(stderr, "%s : no argument\n", argv[0]);
        exit(1);
    }
    for (i=1; i < argc; i++){
        if (mkdir(argv[i], 0777) <0){
            perror(argv[i]);
            exit(1);
        }
    }
    exit(0);
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int main(int argc, char *argv[]){
    int i;
    if (argc < 2){
        fprintf(stderr, "%s : no arguments\n", argv[0]);
        exit(1);
    }
    for (i = 1 ; i < argc; i++){
        if (rmdir(argv[i]) < 0){
            perror(argv[i]);
            exit(1);
        }
    }
    exit(0);
}

 

 

 

 

 

 

 

stdio

- 시스템 콜 함수인 read(), write()의 대안으로 바이트 단위를 읽던것과는 달리 독자적인 버퍼를 사용해 커널 스트림 줄/여러바이트 단위로 빠르게 입출력 가능

- FILE *fopen(const char *path, const char *mode) : open()과 유사하나 FILE 타입을 반환

- int fclose(FILE *stream), int fgetc(FILE *stream), int fputc(int c, FILE *stream);

- 줄단위 입출력 : 1줄 혹은 size -1만큼 읽고 쓰기 fgets(), fputs() 

- 고정 길이 입출력 : size_t fread(void *buf, size_t size, size_t nmeb, FILE * stream) - size x nmemb 바이트 읽어 buf쓰기

 * stdio에서 제공하는 fread, fwrite -> 시스템 콜 read, write랑 비슷하지만 stdio꺼라 다양한 환경서 사용 가능

- 파일 오프셋 처리 : fseek, fseeko(후자는 64비트 고려한 오프셋), ftell, ftello - 파일 오프셋 반환, rewind - 오프셋 처음으로

- file 타입 : int fileno(FILE *stream) 해당 파일 스트림의 디스크립터 반환, FILE *fdopen(int fd, const char *mode) 파일디스크립터로 스트림 포인터 반환

- 버퍼링 : fflush(FILE *stream) - 버퍼 데이터 write

- EOF, 에러 : feof(FILE *stream) - 스트림 EOF 플래그 가져옴, EOF 도달시 0이아닌 값나옴. ferror 스트림 에러 플래그 가져옴, 에러와 EOF 구분 불가시 사용, clearerr 에러와 EOF 플래그를 지움 -> 이런 상황 발생시 클리어하여 다시 읽기 가능 

 

 

head

- 처음 몇줄만 출력하는 명령어

- 버전 1 : 파일 지정 x

#include <stdio.h>
#include <stdlib.h>

static void do_head(FILE *f, long nlines);

int main(int argc, char * argv[])
{
    if (argc != 2)
    {   
        fprintf(stderr, "Usage: %s n\n", argv[0]);
        exit(1);
    }   
    do_head(stdin, atol(argv[1]));
    exit(0);
}

static void do_head(FILE *f, long nlines)
{
    int c;
    if (nlines <= 0) return;
    while ((c = getc(f)) != EOF) {
        if (putchar(c) <0 ) exit(1);
        if (c == '\n'){
            nlines--;
            if (nlines == 0) return;
        }
    }    
}

- 버전 2 : 파일 지정 o + 여러개

#include <stdio.h>
#include <stdlib.h>

static void do_head(FILE *f, long nlines);

int main(int argc, char * argv[])
{
    long nlines;

    if (argc < 2)
    {
        fprintf(stderr, "Usage: %s [file file ...]\n", argv[0]);
        exit(1);
    }
    nlines = atol(argv[1]);
    if (argc == 2)
        do_head(stdin, nlines);
    else{
        int i;

        for (i = 2; i < argc; i++){
            FILE *f;
            f = fopen(argv[i], "r");
            if (!f){
                perror(argv[i]);
                exit(1);
            }
            do_head(f, nlines);
            fclose(f);
        }
    }
    exit(0);
}

static void do_head(FILE *f, long nlines)
{
    int c;
    if (nlines <= 0) return;
    while ((c = getc(f)) != EOF) {
        if (putchar(c) <0 ) exit(1);
        if (c == '\n'){
            nlines--;
            if (nlines == 0) return;
        }
    }
}

옵션 파싱 : 짧은 옵션 get opt()

- 짧은 옵션 : -p, -a

- 긴 옵션 : --version, --help

- int getopt(int argc, char * const argv[], const char *optdecl) : 유닉스 OS 옵션해석 API, 짧은 옵션만 인식, 잘못된 옵션은 ?반환,

extern char *optarg;

extern int optind, opterr, optopt;

- 파라미터를 받지 않는 옵션으로 -a, -t, -x가 있는 경우 optdecl는 atx(순서 무관)

- 파라미터를 받는 옵션이 있는 경우 옵션문자 다음에 콜론 : 사용(f가 옵션받을시) : af:tx or atxf: or f:atx

- getopt 관련 전역변수 : optarg(현재옵션 파라미터), optind(현재 옵션인덱스), opterr(참인경우 에러 표시), optopt(현재 처리중인 옵션)

#include <unistd.h>

int main(int argc, char *argv[])
{
	int opt;
    while ((opt = getopt(argc, argv, "af:tx")) != -1)
    {
    	switch (opt)
        {
        	case 'a':
            	//a일떄 코드
                break;
           	case 'f':
            	//f일떄 코드
                break;
            .....
            case '?':
            	//잘못된 옵션이 전달될 때 코드
            	break;
        }
    }
    // 프로그램 본체
}

옵션 파싱 2 : 긴 옵션 getopt_long

#define _GNU_SOURCE

#icnlude <getopt.h>



int get_long(int argc, char * const argv[], const char *optdecl, const struct option *longoptdecl, int *longindex);

struct option{
	const char *name;
    int has_arg;
    int *flags;
    int val;
};

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

 

 

옵션 처리가 가능한 head

#include <stdio.h>
#include <stdlib.h>
#define _GNU_SOURCE
#include <getopt.h>

static void do_head(FILE *f, long nlines);
#define DEFUALT_N_LINES 10

static struct option longopts[] = {
    {"lines", required_argument, NULL, 'n'},
    {"help", no_argument, NULL, 'n'},
    {0, 0, 0, 0}
};

int main(int argc, char * argv[])
{
    int opt;
    long nlines = DEFUALT_N_LINES;


    while ((opt = getopt_long(argc, argv, "n:", longopts, NULL)) != -1){
        switch (opt){
            case 'n': // -n == --lines
                nlines = atol(optarg);
                break;
            case 'h':
                fprintf(stdout, "Usage: %s [-n LINES] [FILE ...]\n", argv[0]);
                exit(0);
            case '?':
                fprintf(stdout, "Usage: %s [-n LINES] [FILE ...]\n", argv[0]);
                exit(0);
        }
    }
    
    if (optind == argc){
        do_head(stdin, nlines);
    }
    else {
        int i;

        for (i = optind; i < argc; i++){
            FILE *f;

            f = fopen(argv[i], "r");
            if (!f){
                perror(argv[i]);
                exit(1);
            }
            do_head(f, nlines);
            fclose(f);
        }
    }
}

static void do_head(FILE *f, long nlines)
{
    int c;
    if (nlines <= 0) return;
    while ((c = getc(f)) != EOF) {
        if (putchar(c) <0 ) exit(1);
        if (c == '\n'){
            nlines--;
            if (nlines == 0) return;
        }
    }        
}

 

 

 

gdb로 디버깅하기

- 위 코드의     while ((opt = getopt_long(argc, argv, "n:", longopts, NULL)) != -1){ 를

    while ((opt = getopt_long(argc, argv, "n", longopts, NULL)) != -1){로 바꾸자(n:에서 :를 뺏다)

 -> :을 뺏으므로 이 옵션은 파라미터를 받지 않는다는 의미. 실제로는 이옵션이 있을때 몇 줄인지 받아야함.

int main(int argc, char * argv[])
{
    int opt;
    long nlines = DEFUALT_N_LINES;


    while ((opt = getopt_long(argc, argv, "n", longopts, NULL)) != -1){
        switch (opt){
            case 'n': // -n == --lines
                nlines = atol(optarg);
                break;
            case 'h':
                fprintf(stdout, "Usage: %s [-n LINES] [FILE ...]\n", argv[0]);
                exit(0);
            case '?':
                fprintf(stdout, "Usage: %s [-n LINES] [FILE ...]\n", argv[0]);
                exit(0);
        }
    }

- gdb 실행 : gdb ./바이너리

- 옵션을 줘서 프로그램 시작 : run -n 5

 -> stdlib(libc)의 __GI_____strtol_l_internal 함수에서 세그먼테이션 폴트 발생

- bactrace 함수 호출 과정 보기 : __GL____strtol_l_internal에서 뒤로 돌아가면서 어떻게 함수들이 호출됬는지 볼 수 있음

 -> head3_debug.c 의 24번째 줄에서 strtol_c로 넘어감 -> 24번째 줄이 문제이므로 보기

- frame(backtrace 해당 줄 보기) : frame 1로 본 24번째 줄은 다음과 같음

- list : 해당 줄 위아래 5줄씩 주위를 보기

- print {변수명} : 해당 변수 값 보기

 -> 원래 optarg에는 -n 5를 하면서 5가 들어갔어야 하나 21번줄에 파라미터가 있음을 의미하는 n: 가아닌 파라미터가 없다는 의미인 n이 들어가 optarg에 파라미터 5가 입력되지않음.

- quit : gdb 종료

+ Recent posts