728x90
반응형

운영체제의 구조

사용자와 하드웨어 사이에서 자원을 관리하는 계층으로, 커널, 사용자 인터페이스, 시스템 콜, 파일 시스템, 드라이버 등으로 구성됨.

커널: 프로세스, 메모리, 저장장치를 관리함.
사용자 인터페이스: 사용자는 운영 체제의 커널에 직접 접근할 수 없고, 사용자 인터페이스(GUI, CLI)를 통해 접근 가능함.
EX) GUI: 파일 관리자, CLI: cd 명령어
시스템 콜: 프로그램이 운영체제의 커널에 직접 요청을 보내어 하드웨어나 시스템 자원에 접근할 수 있도록 하는 인터페이스
드라이버: 하드웨어와 커널 사이의 인터페이스
=> 운영체제가 하드웨어 장치를 제어하고 통신할 수 있도록 도와주는 소프트웨어

728x90
반응형

'운영체제 > 1. 운영체제란?' 카테고리의 다른 글

1.2 운영체제의 역사  (0) 2024.09.23
1.1 운영체제란?  (0) 2024.09.23
728x90
반응형

운영체제의 역사

  1. 1940년
    에니악: 특정 명령에 맞는 스위치와 배선을 연결하여 프로그래밍을 함.
    문제 1) 30톤짜리 기계에 많은 스위치와 배선을 이용하기 때문에 인력이 많이 필요하고 시간이 오래 걸림.
  2. 1950년도 초반
    직접회로(IC): 진공관과 전선으로 만들어진 논리 회로를 아주 작은 크기로 만든 전자회로
    => 스위치와 배선 작업을 하는 것보다 훨씬 편해짐
    동작 방식: 펀치 카드를 이용해서 프로그래머가 카드에 구멍을 뚫어 프로그래밍을 하면 컴퓨터가 카드를 읽어 계산을 하고 결과가 프린터로 출력이 됨.
    문제 1) 프로그래머가 작성한 펀치 카드(프로그램)를 오퍼레이터가 컴퓨터에 카드를 넣고 그 결과를 프로그래머에게 전달함. 이런 과정이기에 오퍼레이터의 오버헤드가 너무 컸음.
    문제 2) 입출력 작업을 하는 동안에는 CPU를 사용할 수 없음
  3. 1950년도 중후반
    싱글스트림 배치시스템: 프로그래머가 오퍼레이터에게 펀치 카드 여러 개를 한 번에 전달하고, 컴퓨터는 여러 개의 프로그램을 순서대로 실행해서 결과도 한 번에 확인가능하도록 함
    => 작업이 끝날 때마다 일일이 오퍼레이터가 결과를 꺼내고 다시 다른 프로그램을 실행시키는 과정이 사라져서 CPU 사용량이 올라감.
    I/O 디바이스 컨트롤러: 입출력 중에도 CPU가 계산할 수 있도록 함. 입출력 작업이 끝나면 CPU에게 인터럽트 신호를 주고, 인터럽트를 받은 CPU는 다시 처리를 진행함.
    문제 1) CPU와 입출력을 분리했지만, 입출력에도 CPU를 기다려야 하는 작업이 존재함
            => 입력 처리 작업의 경우 입력이 완료될 때까지 기다려야 함.
  4. 1960년도
    시분할 시스템: 메모리에 여러 프로그램을 올려놓고 시간을 나누어서 번갈아 가면서 프로그램을 실행함.
    UNIX: 멀티프로그래밍, 다중 사용자, 파일 시스템을 구현한 운영체제
    문제 1) 메모리에 여러 프로그램이 올라와서 작업을 진행하기에 메모리 침범 이슈가 발생함
    문제 2) 기존에는 프로그램이 하나라고 가정하고 개발해서 바로 하나의 메모리 위치만 알면 됐는데, 프로그램이 여러 개가 돼서 각 프로그램의 메모리 위치를 알 수가 없어짐.
            => 베이스 레지스터를 사용하여 해결함.
    베이스 레지스터: 메모리에서 프로그램이 사용하는 데이터와 명령어의 시작 주소를 저장하는 레지스터
728x90
반응형

'운영체제 > 1. 운영체제란?' 카테고리의 다른 글

1.3 운영체제의 구조  (0) 2024.09.23
1.1 운영체제란?  (0) 2024.09.23
728x90
반응형

운영체제란?

사용자의 하드웨어, 시스템 리소스를 제어하고 프로그램에 대한 일반적 서비스를 지원하는 시스템 소프트웨어이다.

운영체제 사용하는 곳

개인 컴퓨터: Windows, MacOS
대형 컴퓨터, 서버: 유닉스, 리눅스
스마트폰, 태블릿: 안드로이드, IOS
내비게이션, 냉장고, 세탁기 등등: 임베디드 운영체제

사실 운영체제가 없어도 잘 동작하기는 함
=> 하지만 유연하지 못함(처음 설계한 대로만 동작함)

EX) 옛날 유선 전화기는 전화만 가능했지만, 운영체제가 존재하는 현대의 휴대폰은 애플리케이션을 설치해 기능을 추가할 수 있음.


운영체제가 하는 일

  1. 프로세스 관리
    인터넷을 켜놓고 노래를 들으면서 게임을 할 수 있음(전부 동시에 할 수 있음)
    => 만약 운영체제가 관리를 하지 않는다면 특정 기능이 CPU를 독차지해서 기능들이 실행되지 않을 수 있음.
  2. 메모리 관리
    모든 프로그램은 메모리에 올라와서 동작하기에 이를 관리함.
  3. 하드웨어 관리
    운영체제는 사용자의 하드웨어에 대한 직접적인 접근을 막음. 사용자가 하드디스크에 데이터를 저장할 때, 하드디스크의 특정 영역에 바로 저장하지 못하게 하고, 운영체제가 판단해서 적절한 위치에 저장함.
  4. 파일 시스템 관리
    파일들의 효율적인 저장과 관리를 함.
728x90
반응형

'운영체제 > 1. 운영체제란?' 카테고리의 다른 글

1.3 운영체제의 구조  (0) 2024.09.23
1.2 운영체제의 역사  (0) 2024.09.23
728x90
반응형

필드 선택 : SELECT

SELECT ANIMAL_ID,

테이블 선택 : FROM

FROM ANIMAL_INS

필드 조건 지정 : WHERE

WHERE INTAKE_CONDITION = "Sick"

필드 조건 추가 지정 : AND, OR, NOT

WHERE INTAKE_CONDITION = "Sick" AND NAME IS NOT NULL

필드 선택하여 정렬 : ORDER BY ~ ASC
ASC는 생략 가능

ORDER BY id

필드 선택하여 역정렬 : ORDER BY ~ DESC

ORDER BY ANIMAL_ID DESC

개수로 변환 : COUNT
중복 제거 : DISTINCT
조회한 필드 이름 변환 : AS

SELECT COUNT(DISTINCT NAME) AS count

개수 제한 : LIMIT

LIMIT 1

NULL 인지 확인 : IS, IS NOT

WHERE AGE IS NULL

최대, 최소 값 : MAX, MIN

MAX(PRICE) AS MAX_PRICE
MIN(PRICE) AS MIN_PRICE

필드 지정하여 따로 처리하기 : GROUP BY, HAVING 

SELECT NAME, COUNT(NAME) AS COUNT
FROM ANIMAL_INS
WHERE NAME IS NOT NULL
GROUP BY NAME
HAVING COUNT(NAME) > 1
ORDER BY NAME

포함된 글자 찾기(includes) : LIKE
% => 모든 경우, _ => 하나는 존재함 
el이 포함된 경우 => LIKE "%el%"
el로 시작하는 경우 -=> LIKE "el%"
el로 끝나는 경우 -=> LIKE "%el"
el뒤에 한 글자만 오는 경우 => LIKE "%el_"

LOWER(NAME) LIKE "%el%"

시간 포멧(format) 변경 : DATE_FORMAT
Y => 2017, y => 17
M => March, m => 03
D => 1st, d => 01

DATE_FORMAT(HIRE_YMD, "%Y-%m-%d") AS HIRE_YMD

조회 결과가 null인 경우 디폴트값 채워주기 : IFNULL

IFNULL(FREEZER_YN, "N") AS FREEZER_YN

조회 결과를 삼항 연산자처럼 처리하기 : IF

=> IF(조건, 참인 경우의 값, 참이 아닌 경우의 값)

IF(FREEZER_YN IS NULL, "N", FREEZER_YN) AS FREEZER_YN

문자열 자르기 : LEFT, MID, RIGHT, SUBSTRING
SUBSTRING(필드, 시작 위치(1이 맨 앞), 길이)

LEFT(PRODUCT_CODE, 2) AS CATEGORY

SUBSTRING(PRODUCT_CODE, 1, 2)

CONCAT(LEFT(tlno,3), '-', MID(tlno,4,4),'-', RIGHT(tlno,4)) AS 전화번호

문자열 합치기: CONCAT

SELECT USER_ID, NICKNAME, CONCAT(CITY, " ", STREET_ADDRESS1, STREET_ADDRESS2) AS 전체주소, 
FROM USED_GOODS_USER

문자열 중간에 삽입: INSERT

INSERT(INSERT(U.TLNO, 8, 0, '-'), 4, 0, '-') AS 전화번호

필드에 여러 조건 처리 : CASE

SELECT ORDER_ID, PRODUCT_ID, DATE_FORMAT(OUT_DATE, "%Y-%m-%d") AS OUT_DATE, 
    CASE 
        WHEN DATE_FORMAT(OUT_DATE, "%m-%d") <= "05-01"  
            THEN '출고완료'
        WHEN DATE_FORMAT(OUT_DATE, "%m-%d") > "05-01"  
            THEN '출고대기'
        ELSE '출고미정'
    END AS 출고여부
FROM FOOD_ORDER 
ORDER BY ORDER_ID

일치하는 단어 여러 개 찾기 : IN
=> OR LIKE를 여러 번 사용한 것과 같은 효과

WHERE CAR_TYPE IN ("세단", "SUV")

정규표현식 : REGEXP
| => LIKE "%가죽시트%" 를 여러개 한 것과 같은 효과 => 포함된 단어 여러 개 찾기

WHERE NOT AI.SEX_UPON_INTAKE REGEXP('Spayed|Neutered')
    AND AO.SEX_UPON_OUTCOME REGEXP('Spayed|Neutered')

다른 테이블 함께 조회 : join
inner, left, right => 교집합,  왼쪽 원만 포함, 오른쪽 원만 포함

SELECT B.CATEGORY AS CATEGORY, SUM(SALES) AS TOTAL_SALES
FROM BOOK_SALES AS BS
LEFT JOIN BOOK AS B USING(BOOK_ID)

JOIN 문에서 WHERE : ON, USING
ON => WHERE 처럼 사용

LEFT JOIN BOOK AS B ON B.BOOK_ID = BS.BOOK_ID

 USING => 괄호로 쉽게 사용

LEFT JOIN BOOK AS B USING(BOOK_ID)

반올림 : ROUND

SELECT ROUND(SUM(DAILY_FEE) / COUNT(DAILY_FEE)) AS AVERAGE_FEE

내림 : TRUNCATE
group by와 함께 사용하여 가격대 별로 조회가 가능함

SELECT TRUNCATE(SUM(DAILY_FEE) / COUNT(DAILY_FEE)) AS AVERAGE_FEE

SELECT TRUNCATE(PRICE, -4) AS PRICE_GROUP, COUNT(PRODUCT_CODE) AS PRODUCTS
FROM PRODUCT 
GROUP BY PRICE_GROUP

 

 

추가, 수정, 삭제 관련

테이블 추가 : INSERT INTO

INSERT INTO table_name (column1, column2, column3, ...)
VALUES (value1, value2, value3, ...);

테이블 수정 : UPDATE ~ SET 

UPDATE table_name
SET column1 = value1

테이블 삭제 : DELETE

DELETE FROM table_name

 

시간 관련

DATETIME 요일 차이 : DATEDIFF

DATEDIFF('2021-12-16', '2020-01-01')

DATETIME에서 year, month, day, hour, month, second 추출
이름 그래도 함수가 존재하여 사용하면 됨

WHERE YEAR(APNT_YMD) = "2022" AND MONTH(APNT_YMD) = "05"

시간 구간 별로 조회 : GROUP BY, HOUR

SELECT HOUR(DATETIME) AS HOUR, COUNT(DATETIME) AS COUNT
FROM ANIMAL_OUTS
GROUP BY HOUR(DATETIME)
HAVING HOUR >= 9 AND HOUR <= 19
ORDER BY HOUR

 

기타 등등

서브 쿼리 => 서브 쿼리가 필요한 경우가 상당히 있다. 

EX) 첫번째 쿼리는 정상 동작하지 않는다. group_by는 조회된 내용 중 제일 상단에 있는 것을 조회할 뿐이어서 FAVORITES의 최대값으로 조회를 한다고 해도 매칭되는 REST_ID, REST_NAME이 조회되지는 않는다.
그렇기에 2번째 쿼리 처럼 서브쿼리를 통해 조회를 해야한다. 

SELECT FOOD_TYPE, REST_ID, REST_NAME, MAX(FAVORITES) AS FAVORITES
FROM REST_INFO 
GROUP BY FOOD_TYPE
ORDER BY FOOD_TYPE DESC
SELECT FOOD_TYPE, REST_ID, REST_NAME, FAVORITES AS FAVORITES
FROM REST_INFO 
WHERE (FOOD_TYPE, FAVORITES)
IN (
    SELECT FOOD_TYPE, MAX(FAVORITES) AS FAVORITES
    FROM REST_INFO
    GROUP BY FOOD_TYPE
)
ORDER BY FOOD_TYPE DESC

 

728x90
반응형
728x90
반응형

예전에 csv로 저장되어 있던 내용을 데이터베이스로 옮기는 작업을 진행했었다.
여기서 회사 정보가 있는데, 이 회사들의 ID(숫자)는 채용 공고, 이미지 등등 다른 정보들과 관계가 있는 상황이었다.
데이터베이스로 옮기면서 id 바꾸게 되면 다른 정보들도 그에 맞춰서 동기화를 해야 하니 번거롭다는 생각이 들었다.
그렇기에 회사 ID를 autoincrement로 만들고 기존 csv의 회사 ID를 그냥 직접 대입하며 테이블에 추가했다.
아래는 당시 코드이다.

스키마
데이터 삽입

이후에 회사 정보를 추가하면 id는 어떻게 되는 걸까?라는 생각이 먼저 들었다.
그때 처음 든 생각은 autoincrement로 설정했으니 1,2,..., 319, 321과 같은 경우라면 알아서 
비어있는 320이나 다음으로 제일 큰 숫자인 322로 채워주지 않을까였다.
하지만 회사를 추가하니 companyId를 2로 삽입하여 중복된 id라고 에러가 나왔다.

autoincrement의 시퀸스가 자동이 아니라 직접 저장한 값들은 기억을 못 해서 생긴 문제였다.
그렇기에 이런 경우에는 아래 코드처럼 autoincrement 시퀸스를 직접 업데이트해줘야 한다.
아래는 postgresql, prisma 코드이다.

  await prisma.$executeRaw`SELECT setval(
    pg_get_serial_sequence('"company"', 'company_id'),
    coalesce(max(company_id) + 1, 1),
    false
  ) FROM "company"`

 

참고: https://github.com/prisma/prisma/discussions/5256

 

Invalid create() Unique constraint failed on the fields: (`id`) · prisma prisma · Discussion #5256

Hello I have this model: model Somename { id Int @id @default(autoincrement()) name String } I get this error when trying to create an entry for the model: Invalid prisma.somename.create() Unique c...

github.com

728x90
반응형
728x90
반응형

AWS를 사용해 보니 내가 어떤 것을 사용하고 있는지 보이지가 않아서 답답할 때가 있습니다.

간단한 서버를 배포하는데도 이런데, 훨씬 복잡한 구조라면 어떻게 기억을 할까...라는 생각이 들 때

이름만 알고 있던 Terraform이 생각났고, 이런 기술들을 Iac라고 한다는 것을 알게 되었습니다.

aws에서는 CloudFormation와 CDK가 있는데 오늘은 CDK를 사용해서 간단하게 CI/CD를 구현해보려고 합니다.

 

그전에 우선 CDK 같은 기술들이 나오기까지의 과정을 살펴봅시다.

1. 관리 콘솔로 리소스 만들기

AWS 콘솔과 같이 수동으로 한 땀 한 땀 작업하여 인프라를 구성하는 방식입니다.

처음에는 모두 이렇게 하겠지만 정말 많은 수의 인프라를 구축해야 한다면 거의 불가능한 방식이 아닐까라는 생각이 듭니다...

2. 코드 형태의 명령형 인프라

AWS CLI, SDK 등을 이용하여 스크립트를 작성하여 운영 및 관리를 하는 방식입니다.

하지만 아직 리소스 관리와 구성은 수동으로 이루어지기에 오류 발생 가능성이 높고 반복적인 작업에 시간이 많이 소요됩니다.

3. 코드로서의 선언적 인프라 -> IaC(Infrastructure as Code)

AWS CloudFormation, Terraform를 이용하여 인프라스트럭처를 코드로 정의하고 관리하여, 리소스를 프로그래밍 방식으로

생성 및 업데이트하는 방식입니다. 하지만 서비스 별로 JSON, YAML 템플릿을 구성하는 방법과, 옵션에 관한 러닝 커브가 있습니다.

4. AWS CDK(Cloud Development Kit)

CDK는 주요 aws 서비스를 명령어로 정의하는 높은 수준의 객체지향 추상화를 지원합니다. 제가 이해한 바로는 CDK는

CloudFormation을 좀 더 고도화한 기술이고, CDK로 정의한 인프라는 CloudFormation 템플릿으로 변환하여 배포할 수 있습니다.

Typescript, Python, Java와 같은 많이 사용되는 프로그래밍 언어를 지원하기에 루프 및 조건문 같은 코드 논리를 자연스럽게

표현하며 인프라를 직접 지정할 수 있습니다. 

 

CDK를 사용해 CI/CD 파이프라인 구현

우선 github token을 생성해야 합니다.

1. 아래 경로로 들어가 Generate new token -> Generate new token(classic)을 선택합니다.

https://github.com/settings/tokens

 

GitHub: Let’s build from here

GitHub is where over 100 million developers shape the future of software, together. Contribute to the open source community, manage your Git repositories, review code like a pro, track bugs and fea...

github.com

 

2. Note에 원하는 이름을 넣어주고, repo와 admin:repo_hook 권한을 체크해 줍니다.

 

3. 생성된 토큰 값을 복사하고 AWS Secrets Manager에서 새 암호를 저장합니다.

Step 1) 다른 유형의 보안 암호 -> 일반 텍스트 -> 복사한 토큰 값을 넣어주고 다음을 누릅니다.

Step 2) 암호 이름을 github-token으로 넣어줍니다. 이 이름이 아니면 추가로 설정을 해야 하는 것으로 보입니다.

Step 3, 4) 넘어가줍니다.

 

그 후 터미널에서 아래 명령어들을 실행합니다.

sudo npm install -g typescript aws-cdk-lib
alias cdk="npx aws-cdk"

mkdir cdk-cicd
cd cdk-cicd
cdk init app --language typescript

 

만약 위 과정에서 에러가 나왔다면 aws와 연결이 안 되어있기 때문일 것입니다.

아래 명령어로 aws cli를 설치한 후 aws configure 명령어를 통해 연동을 해야 합니다.

curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg ./AWSCLIV2.pkg -target /
aws --version

aws configure

 

IAM 사용자에 있는 access_key(유저 ID), secret access key와 region(ap-northeast-2), output format(json)을

차례대로 넣어주면 됩니다.

 

그 후 cdk-cicd 폴더에서 아래 명령어들을 실행합니다.

esbuild는 람다를 실행할 때 필요하기 때문에 설치해 줍니다.

npm install
npm install esbuild

 

그럼 아래와 같은 구조로 파일이 생성됩니다.

bin: NestJS의 main.ts 같은 느낌입니다.

cdk.out: CloudFormation에 적용될 JSON이 저장되는 공간입니다.

lib: 사실상 메인 폴더이고 여기서 infrastructure를 정의합니다.

test: jest 테스트용 폴더입니다.

 

이제 main 브랜치에 push가 되면 CI/CD가 시작되어 간단한 람다 함수를 테스트하도록 코드를 작성해 보겠습니다.

코드에서 보이는 Stage와 Stack은 아래의 의미를 가집니다.

Stage: CDK에서 배포 단계를 나타내는 개념입니다. 예를 들어 develop, test, prod 같은 이름으로 지정할 수 있습니다.

Stack: CDK에서 인프라 리소스의 논리적 그룹을 나타내는 단위입니다. 스택은 하나 이상의 AWS 리소스를 정의하고,

그룹핑, 배포 및 관리를 쉽게 할 수 있도록 도와줍니다.

 

lib/cdk-cicd-stack.ts

소스 코드 변경을 감지하고 CI/CD 파이프라인을 실제로 실행시켜 주는 코드입니다.

import * as cdk from "aws-cdk-lib";
import {
  CodeBuildStep,
  CodePipeline,
  CodePipelineSource,
  ShellStep,
} from "aws-cdk-lib/pipelines";
import { Construct } from "constructs";
import { PipelineStage } from "./PipelineStage";

export class CdkCicdStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const pipeline = new CodePipeline(this, "MyPipeline", {
      pipelineName: "MyPipeline",
      synth: new ShellStep("Synth", {
        // input: CodePipelineSource.gitHub('kimjinho1/cdk-cicd-practice', 'cdk-cicd-practice'),
        input: CodePipelineSource.gitHub("kimjinho1/cdk-cicd-practice", "main"),
        commands: ["cd cdk-cicd", "npm ci", "npx cdk synth"],
        primaryOutputDirectory: "cdk-cicd/cdk.out",
      }),
    });

    const testStage = pipeline.addStage(
      new PipelineStage(this, "PipelineTestStage", {
        stageName: "test",
      })
    );

    testStage.addPre(
      new CodeBuildStep("unit-tests", {
        commands: ["cd cdk-cicd", "npm ci", "npm test"],
      })
    );
  }
}

위 코드에서 input 부분은 본인의 레포지터리 이름과 브랜치 이름을 넣어주셔야 합니다.

input: CodePipelineSource.gitHub("kimjinho1/cdk-cicd-practice", "main"),

 

lib/PipelineStage.ts

CodePipeline 스테이지를 구성하는 코드입니다.

import { Stage, StageProps } from "aws-cdk-lib";
import { Construct } from "constructs";
import { LambdaStack } from "./LambdaStack";

export class PipelineStage extends Stage {
  constructor(scope: Construct, id: string, props: StageProps) {
    super(scope, id, props);

    new LambdaStack(this, "LambdaStack", {
      stageName: props.stageName,
    });
  }
}

 

lib/LambdaStack.ts

Lambda 함수를 정의하고 생성하는 코드입니다.

import { Stack, StackProps } from "aws-cdk-lib";
import { Runtime } from "aws-cdk-lib/aws-lambda";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
import { Construct } from "constructs";
import { join } from "path";

interface LambdaStackProps extends StackProps {
  stageName?: string;
}

export class LambdaStack extends Stack {
  constructor(scope: Construct, id: string, props: LambdaStackProps) {
    super(scope, id, props);

    new NodejsFunction(this, "hello-lambda", {
      runtime: Runtime.NODEJS_18_X,
      handler: "handler",
      entry: join(__dirname, "..", "lambda", "hello.ts"),
      environment: {
        STAGE: props.stageName!,
      },
    });
  }
}

 

lambda/hello.ts

실행되는 람다 코드입니다.

async function handler(event: any, context: any) {
  return {
    statusCode: 200,
    body: "Hello AWS",
  };
}

export { handler };

 

test/cdk-cicd.test.ts

람다 함수를 테스트하는 코드입니다.

import { handler } from "../lambda/hello";

describe("Hello describe test suite", () => {
  test("handler should return 200", async () => {
    const result = await handler({}, {});
    expect(result.statusCode).toBe(200);
  });
});

 

이제 cdk deploy 명령어를 실행하면 설정한 AWS 서비스들이 CloudFormation으로 자동으로 세팅이 됩니다.

cdk deploy

CodePipeline 내부에서 실행된 결과를 확인할 수 있습니다. 생각보다 시간이 오래 걸립니다. 4분? 정도

CodePipeline
unit-tests

 

친숙한 프로그래밍 언어로 인프라를 구축할 수 있다는 점이 개발자 친화적이어서 아주 유망한 기술인 것 같습니다만,

그전에 aws에 대해 조금 더 친숙해지고 나서 활용해야겠다는 생각이 들었습니다.

아무리 러닝 커브가 내려갔다고 해도 쉽지는 않은 것 같습니다...

그리고 의아한 점은 CodePipeline이 생각보다 엄청 느리다는 점입니다. 이렇게 간단한 것을 실행하는데도 5분 정도가 걸립니다.

현재 진행하고 있는 프로젝트에서 CodePipeline을 사용할 생각이었는데, github actions과 비교해봐야 할 것 같습니다.

728x90
반응형
728x90
반응형

클라이언트와 서버

클라이언트: 웹 사용자의 인터넷이 연결된 장치들과 이런 장치들에서 사용 가능한 크롬 같은 웹 브라우저

서버:  웹페이지, 사이트, 앱 등을 저장하는 컴퓨터

클라이언트와 서버

웹 -> 도로
클라이언트 -> 우리의 집
서버 -> 상점
라고 생각하고 생각하고 아래 글을 이해하면 쉽습니다.

웹에서 실제로 무슨 일이 일어날까?

우리가 브라우저에 웹 주소를 입력하는 것은 우리가 상점에 걸어가는 것과 비슷합니다.

1. 브라우저는 DNS 서버로 가서 웹사이트 서버의 실제 ip 주소를 찾습니다.

EX) 우리가 네이버 지도로 상점의 주소를 찾는다.

2. 브라우저가 서버에게 웹사이트의 사본을 보내달라는 HTTP 요청 메시지를 서버로 전송합니다. 
메시지, 클라이언트와 서버 사이에 전송되는 모든 데이터는 TCP/IP 연결을 통해서 전송됩니다.

EX) 상점에 가서 상품을 주문한다.

3. 이 메시지를 받은 서버는 클라이언트의 요청을 승인하고 "200 OK" 메시지를 클라이언트에게 전송합니다.
"200 OK"는 넌 웹사이트를 볼 자격이 있어! 라는 의미입니다. 그 후 서버는 웹사이트의 파일들을 데이터 패킷이라 불리는
작은 덩어리들로 브라우저에 전송을 합니다.

EX) 상점에서 우리가 주문한 상품을 전달받고, 우리는 그 상품을 집으로 가져간다.

4. 브라우저는 이 데이터 패킷들을 완전환 웹 사이트로 조립하고 우리에게 보여줍니다.

EX) 상품이 우리 집 문 앞에 도착한다.

 

인터넷 연결: 웹에서 데이터를 보내고 받게 해준다.

EX) 집과 상점 사이의 거리

TCP/IP: Transmission Control Protocol(전송 제어 규약) / Internet Protocol(인터넷 규약)
-> 데이터가 어떻게 웹을 돌아다닐지 정의하는 통신 규약

EX) 주문을 하고, 상점에 가고, 우리가 상품을 살 수 있게 해주는 운송 장치(우리의 다리 또는 자동차 등등)와 같습니다.

DNS: Domain Name System Servers(도메인 이름 시스템 서버) -> 웹사이트를 위한 주소록

우리가 63.245.217.105 같은 ip 주소를 직접 기억하는 것은 쉽지가 않기에 DNS가 발명되었습니다. DNS는 우리가
브라우저에 입력하는 웹주소 예를 들어 naver.com 같은 웹사이트를 실제 ip 주소에 맞춰주는 특별한 서버입니다.

HTTP: Hypertext Transfer Protocol(하이퍼텍스트 전송 규약)은 클라이언트와 서버가 서로 통신할 수 있게 하기 위한 언어를 정의하는 규약? 약속 같은 친구입니다. 

EX) 우리가 상품을 주문하기 위해 사용하는 언어와 같습니다.

packet: 패킷

기본적으로 웹에서 데이터가 전송될 때, 아주 작은 덩어리들로 전송됩니다. 그렇기에 다양한 웹 사용자들이 동시에 같은
웹 사이트를 다운로드할 수 있습니다. 만약 데이터가 하나의 큰 덩어리로 전송된다면, 한 번에 한 명의 사용자만 다운로드가
가능해져서 매우 비효율적이게 됩니다.

 

Reference

https://developer.mozilla.org/ko/docs/Learn/Getting_started_with_the_web/How_the_Web_works#패킷_설명

 

웹의 동작 방식 - Web 개발 학습하기 | MDN

*'웹의 동작 방식'*은 여러분의 컴퓨터나 폰의 웹 브라우저 안에서 웹페이지를 볼 때 무슨 일이 발생하는지에 대한 간소화된 개념을 제공할 것입니다.

developer.mozilla.org

 

728x90
반응형
728x90
반응형

허용 함수를 다 안다는 전제로 글을 작성했습니다. 모르면 아래 글 보기.

https://jinho-study.tistory.com/1138

 

42 서울 pipex 정리 2 (access, dup2, execve, perror, strerror)

이어서 나머지 함수들을 알아보자. https://jinho-study.tistory.com/1137 pipex 정리 1 ( 프로세스, fork, pipe, wait, waitpid) 개요 pipex는 우리가 만들 pipex 프로그램을 위와 같은 방식으로 돌렸을 때, 아래 명령어

jinho-study.tistory.com

 

전체적으로 봤을 때 로직은 아래와 같이 생각보다 간단하다. 실제로 파일 3개면 구현 가능하다.

int	main(int ac, char **av, char **envp)
{
	int		i;

	if (ac != 5)
		에러 출력 후 종료
	i = -1;
	while (++i < 2)
	{
		if (i == 0)
       		명령어 파싱 -> 첫 번째 프로세스 실행(infile을 stdin으로 받고 명령어 실행)
		else if (i == 1)
			명령어 파싱 -> 두 번째 프로세스 실행(첫 번째 프로세스 실행 결과를 stdin으로 
            받고 명령어 실행 결과를 outfile에 저장)
	}
	포크된 프로세스 만큼 wait를 해준다. 멘덴토리의 경우에는 2번
	i = -1;
	while (++i < 2)
		wait(NULL);
}

다만 여기서 알고 넘어가야 될게 2가지가 있다. minishell 내용 같긴 한데 pipex 하면서 알고 간다면

minishell 할 때 아주 큰 도움이 된다.

1 ) 쉘에서 명령어들은 병렬로 돌아간다. 

예를 들어 sleep 3 | sleep 3은 6초를 기다릴 것 같지만, 실제로는 병렬로 돌기에 3초를 기다린다.

병렬이더라도 cat | cat 같은 경우에는 애초에 앞에서 입력이 들어와야 넘어가기 때문에 프로그램이 끝나지 않는다. 

2) 파이프 개수만큼 fork 되는 것이 아니라 파이프 개수 + 1 만큼 fork 된다.

실행되는 명령어가 2개(ls | cat)라서 pipe가 한 개라고 fork를 한번 하지 않는다.

실제로 bash에서 그냥 exit은 당연히 꺼지지만 exit | exit 은 bash가 꺼지지 않는다. 

bash 꺼짐
bash 안 꺼짐

그냥 exit은 fork가 안되고 바로 실행돼서 bash가 꺼지지만, exit | exit의 경우에는 fork가 2번 실행되어서

프로세스가 3개라 bash가 꺼지지 않는다. 이러한 구조를 가지는 것은 자식 프로세스에서 명령어를 수행하고

부모(main) 프로세스에서 모든 자식 프로세스를 기다리게 하기 위함이 아닐까?라고 작성자는 이해했다. 

 

각 프로세스 별로 구현해야 되는 것들을 순서대로 쭉 나열해 보면

1) 첫 번째 프로세스 

1.1) infile 읽고 pipe 세팅

자식 프로레스)

infile을 stdin으로 dup2, fd[1]을 stdout으로 dup2 해준다.

여기서 fd[0]만 사용 안 하니까 fd[0]만 close 하는 경우가 많은데, 둘 다 close 해줘야 된다.

fd[0]만 close 하면 yes | head -1 같은 테스트의 경우 프로그램이 끝나지 않는다.

부모 프로레스)

fd[0]을 stdin으로 dup2 해준다. 여기서 fd[0]은 자식 프로세스에서의 명령어 실행 결과가 된다.

결국 첫 번째 명령어의 실행 결과가 stdin으로 들어와 있는 상태라고 생각하면 된다.

여기서도 마찬가지로 fd[0], fd[1] 둘 다 close 해준다.

static void	dup_child_1(char **av, int *fd)
{
	int	infile;

	infile = open(av[1], O_RDONLY);
	if (infile == -1)
		perror_exit("infile error");
	if (dup2(infile, STDIN_FILENO) == -1)
		perror_exit("dup2 error");
	if (dup2(fd[1], STDOUT_FILENO) == -1)
		perror_exit("dup2 error");
	close(fd[0]);
	close(fd[1]);
}

static void	child_process_1(char **av, char **envp)
{
	int		fd[2];
	pid_t	pid;

	if (pipe(fd) == -1)
		perror_exit("pipe error");
	pid = fork();
	if (pid == -1)
		perror_exit("fork error");
	if (pid == 0)
	{
		dup_child_1(av, fd);
		execute(av[2], envp);
	}
	if (dup2(fd[0], STDIN_FILENO) == -1)
		perror_exit("dup2 error");
	close(fd[0]);
	close(fd[1]);
}

1.2) 명령어 파싱 후 실행

env 실행 결과

메인에서 인자로 받는 envp 안에는 이런 식으로 환경변수들이 저장되어 있다.

여기서 우리가 필요한 것은 PATH=/Users/jinhokim/.brew/bin:/Users/jinhokim/.brew/bin:/usr/local/bin:/usr/bin:/

bin:/usr/sbin:/sbin:/usr/local/munki이다. PATH= 뒤의 문자열을 : 기준으로 split 하게 되면 

["/bin", "/usr/bin", ...] 같은 결과가 되는데 총 6개의 경로가 나오게 된다. 이 경로들 안에 명령어 파일들이 들어있다.

예를 들어 ls는 /bin/ 안에 들어있는데 그렇기에 쉘에서 /bin/ls를 실행하면 ls가 실행된다.

/bin/ls

이제 저 6개의 경로와 첫 번째 명령어를 ft_strjoin한 결과를 access 함수를 통해 실행 가능한 파일인지 확인하고

없는 명령어의 경우에는 에러 출력, 있는 명령어의 경우에는 그냥 execve를 실행해주면 된다.

// path -> ["/bin", "/usr/bin", ...]
while (path[i])
	{
	// joined_cmd = "/ls"
	// ret_cmd = "/bin/ls"
        ret_cmd = ft_strjoin(path[i++], joined_cmd);
		if (access(ret_cmd, X_OK) != -1)
		{
			free(joined_cmd);
			return (ret_cmd);
		}
		free(ret_cmd);
	}
	free(joined_cmd);
	return (NULL);
}

 

2) 두 번째 프로세스 

2.1) outfile에 실행 결과 저장

자식 프로레스)

outfile을 생성하고 stdout으로 dup2 해준다. 그러고 위에 설명했던 명령어 파싱 후 실행을 반복하면 된다.

stdout이 outfile에 연결되어 있기 때문에 이 프로세스에서 표준 출력되는 내용들은 모두 outfile 안에 저장된다.

부모 프로레스)

아무것도 할 필요가 없다.

static void	child_process_2(char **av, char **envp)
{
	pid_t	pid;
	int		outfile;

	pid = fork();
	if (pid == -1)
		perror_exit("fork error");
	if (pid == 0)
	{
		outfile = open(av[4], O_RDWR | O_CREAT | O_TRUNC, 0644);
		if (outfile == -1)
			perror_exit("outfile error");
		if (dup2(outfile, STDOUT_FILENO) == -1)
			perror_exit("dup2 error");
		execute(av[3], envp);
	}
}

 

 

728x90
반응형
728x90
반응형

이어서 나머지 함수들을 알아보자. 

https://jinho-study.tistory.com/1137

 

pipex 정리 1 ( 프로세스, fork, pipe, wait, waitpid)

개요 pipex는 우리가 만들 pipex 프로그램을 위와 같은 방식으로 돌렸을 때, 아래 명령어와 똑같이 동작하도록 구현해야 되는 과제이다. 즉 infile 파일을 읽고 명령어 2개를 실행한 결과를 outfile에

jinho-study.tistory.com

 

함수 정리

1. access

#include <unistd.h>
int access(const char *pathname, int mode);

access 함수는 파일의 권한을 확인하고 성공하면 0 실패하면 -1을 반환한다.

mode에 따라 확인하는 내용이 달라지는데 mode는 아래 4가지가 있다.

R_OK(파일 존재, 읽기 권한), W_OK(파일 존재, 쓰기 권한), X_OK(파일 존재, 실행 권한), R_OK(파일 존재)

우리는 이 함수를 써서 우리가 입력한 명령어가 실제로 있는 명령어인지 확인할 수 있다.

예시 1) 파일이 있으면 Success, 없으면 Fail이 출력된다.

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

int main(void)
{
	char *pathname = "./test.txt";
	if ( access(pathname, R_OK | W_OK) == 0)
		printf("읽고 쓰기 가능\n");
	else
		printf("권한이 없거나 파일이 없음");
}

 

2. dup2

#include <unistd.h>
int dup2(int fd1, int fd2)

dup2 함수는 디스크립터를 변경해주는 함수이다. fd2가 fd1을 가리키게 하고 기존 fd2는 close 된다.

아래 함수는 pipex 과제 중 만든 함수인데, 정상적으로 동작할 시 std_in을 표준 입력으로 std_out을 표준 출력으로 바꿔준다.

static void	pipe_control(int close_fd, int std_in, int std_out, t_info *info)
{
	close(close_fd);
	if (dup2(std_in, STDIN_FILENO) == -1)
		invalid_exit_opt(info, "STDIN dup2 error", 1);
	if (dup2(std_out, STDOUT_FILENO) == -1)
		invalid_exit_opt(info, "STDOUT dup2 error", 1);
	close(std_in);
	close(std_out);
}

pipex 하면서 보기는 쉽지 않은 경우이긴 하지만 stdin에 dup2를 쓰고 close를 한 경우 stdin이 죽어서 계속 입력으로 

EOF를 받게 된다. minishell 정리할 때도 작성하겠지만 아래와 같은 식으로 stdin, stdout을 dup 함수를 통해 기억해두고

마지막에 dup2를 사용해 stdin, stdout을 되돌려 줄 수 있다. 작성자는 이 에러 때문에 minsihell 할 때 10시간을 버렸다.

int	main(int ac, char **av, char **envp)
{
	int		stdin_dup;
	int		stdout_dup;

	stdin_dup = dup(0);
	stdout_dup = dup(1);
	//~~~
	//process
	//~~~
	dup2(stdin_dup, 0);
	dup2(stdout_dup, 1);
	close(stdin_dup);
	close(stdout_dup);
}

 

3. execve

#include <unistd.h>
int execve(const char *filename, char *const argv[], char *const envp[])

execve 함수는 exec 계열 함수 중 하나로 프로세스를 하나 생성해서 명령어를 실행시키고 자신을 종료시킨다.

성공 시에는 종료가 되기 때문에 실패 시에만 -1을 반환한다.

예시 1) Running ls with execve 출력 후 ls 명령어가 실행된다.

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

int	main(int argc, char **argv, char **envp)
{
	char *arg[2] = {"ls"};
	printf("Running ls with execve\n");
	execve("/bin/ls", arg, envp);
	printf("execve failed to run ls\n");
}

 

아래는 에러를 출력해주는 함수들인데 우리 프로그램에서 에러를 출력할 때 쉘과 거의 똑같이 출력하고 싶다면 사용하면 된다.

마음대로 출력할 거라면 그냥 fd_putstr 같은 함수를 사용하면 된다.

4. perror

#include <stdio.h>
void perror(const char* str);

perror 함수는 전역 변수 errno에 해당하는 에러 메시지를 출력해준다. str이 NULL이 아닐 시 str도 출력해준다.

예시 1) myfile이 없을 시 Could not open data file: No such file or directory가 출력된다.

#include <stdio.h>
#include <stdlib.h>
 
int main(void)
{
   FILE *fh;
 
   if ((fh = fopen("myfile", "r")) == NULL)
      perror("Could not open data file");
}

 

5. strerror

#include <string.h>
char* strerror(int errnum);

strerror 함수는 errnum의 값을 통해 발생했던 에러에 맞는 에러 메시지를 반환해준다.

예시 1) myfile이 없을 시 No such file or directory, 2가 출력된다.

#include <errno.h>
#include <stdio.h>
#include <string.h>

int main()
{
	FILE* fh;

	if ((fh = fopen("myfile", "r")) == NULL)
		printf("%s, %d\n", strerror(errno), errno);
	return 0;
}
728x90
반응형

'42 SEOUL > pipex' 카테고리의 다른 글

42 서울 pipex Mandatory  (0) 2022.12.19
42 서울 pipex 정리 1 ( 프로세스, fork, pipe, wait, waitpid)  (0) 2022.09.06
728x90
반응형

개요

pipex는 우리가 만들 pipex 프로그램을 위와 같은 방식으로 돌렸을 때, 아래 명령어와 똑같이 동작하도록 구현해야 되는 과제이다.

즉 infile 파일을 읽고 명령어 2개를 실행한 결과를 outfile에 저장해야 된다. 여기서 이 작대기 |가 파이프다.

우리는 이 과제를 통해 프로세스와 프로세스 간 통신을 할 때 사용하는 pipe에 대해 공부해야 한다.

 

프로세스(process)란?

프로그램은 컴퓨터에서 실행될 때 특정 작업을 수행하는 일련의 명령어들의 집합이다. 이 프로그램의 명령어와

정적 데이터가 메모리에 적재되면 프로세스가 된다. 간단히 말하면 프로세스는 실행 중인 프로그램을 의미한다.

우리가 작업 관리자를 켜면 잔뜩 나오는 것들이 프로세스다.

 

함수 정리

1. fork

#include <unistd.h>
pid_t fork(void);

fork 함수는 프로세스를 복사해준다. 기존의 프로세스를 부모 프로세스, 복사된 프로세스를 자식 프로세스라고 부른다.

fork 함수는 프로세스의 고유 id인 pid를 반환한다. fork 함수 실행 실패 시 -1을 반환하고 자식 프로세스를 생성하지 않는다.

성공 시에는 부모 프로세스에는 자식 프로세스 피드 값을, 자식 프로세스에는 0이 반환한다.

예시 1)

코드만 보면 if, else문이 다 돌아가는 게 말이 안 되는 것 같지만 부모, 자식 프로세스에서 각각 main문이 돌아가기 때문에 2번 출력된다.

int	main(void)
{
	pid_t pid = fork();

	if (pid == 0)
	{
		printf("Hello from Child\n");
		printf("%d %d\n", pid, getpid());
	}
	else
	{
		printf("Hello from Parent\n");
		printf("%d %d\n", pid, getpid());
	}
}

/*
실행 결과
Hello from Parent
48280 48279
Hello from Child
0 48280
*/

예시 2) 자식과 부모 프로세스는 데이터와 상태가 다르기에 변수의 변화가 영향을 끼치지 않는다.

int	main(void)
{
	int x = 1;
	if (fork() == 0)
    	printf("Child has x = %d\n", ++x);
	else
		printf("Parent has x = %d\n", --x);
}

/*
실행 결과
Parent has x = 0
Child has x = 2
*/

 

2. pipe

#include <unistd.h>
int pipe(int fd[2]);

pipe 함수는 파이프를 생성하고 2개의 디스크립터(읽기 전용, 쓰기 전용)를 생성해준다. 실패했을 경우에는 -1을 반환한다.

생성한 2개의 디스크립터와 파이프를 사용해 아래와 같은 방식으로 부모 프로세스를 자식 프로세스를 통신시킬 수 있다.

 

open 함수를 사용할 때와 마찬가지로 디스크립터를 사용하기에 pipe 함수 역시 사용하지 않는 디스크립터는 close 함수로 닫아줘야 한다.

부모에서 자식으로 일반통행 방식이기 때문에 부모 프로세스는 읽는 쪽을,  자식 프로세스는 쓰는 쪽을 닫아주면 된다.

예시 1) pipe 생성

int	main(void)
{
	int fd[2];
	
	pipe(fd);
	printf("fd[0]: %d fd[1]: %d\n", fd[0], fd[1]);
}

/*
실행 결과
fd[0]: 3 fd[1]: 4
*/

예시 2) pipe를 사용한 부모와 자식 프로세스 간 통신

#define MAX_BUF 1024

int main()
{
	int fd[2];
	pid_t pid;
	char buf[MAX_BUF];

	if (pipe(fd) < 0)
		printf("pipe error\n");
	pid = fork();
	if (pid < 0)
	{
		printf("fork error\n");
		exit(1);
	}
	if (pid == 0)
	{
		close(fd[1]);
		read(fd[0], buf, MAX_BUF);
		printf("Child got message : %s\n", buf);
	}
	else
	{
		close(fd[0]);
		ft_strcpy(buf, "Massage from Parent");
		write(fd[1], buf, ft_strlen(buf));
	}
}

/*
실행 결과
Child got message : Massage from Parent
*/

 

3. wait

#include <sys/wait.h>
pid_t  wait(int *statloc);

wait 함수는 자식 프로세스가 종료되었을 때 자식 프로세스의 pid값을 반환하고, 오류가 났을 때는 -1을 반환합니다.

wait 함수를 사용하면 부모 프로세스를 자식 프로세스가 종료될 때까지 기다리게 할 수 있습니다. 

만약 자식 프로세스가 종료되었는데 부모 프로세스가 계속 돌아가고 있다면 자식 프로세스는 좀비 프로세스 상태가 되는데 

wait 함수를 사용해 이를 방지할 수도 있다.

예시 1) 부모 프로세스가 자식 프로세스가 끝난 후 다시 돌아가는 것을 확인할 수 있다.

int main()
{
	int stat, stat_res;

    if ((fork()) == 0)
        printf("Child Hi\n");
    else
    {
		stat_res = wait(&stat);
		printf("Parent Hi\n");
		printf("wait 인자값: %d\nwait 반환값: %d\n", stat, stat_res);
        wait(NULL);
        printf("Child Bye\n");
    }
    printf("Process Bye\n");
    return 0;
}

/*
실행 결과
Child Hi
Process Bye
Parent Hi
wait 인자값: 0
wait 반환값: 57724
Child Bye
Process Bye
*/

 

아래 WIF 매크로를 사용해서 stat의 정보를 확인할 수 있다.

WIFEXITED(status): 자식 프로세스가 정상적으로 종료했으면 참을 반환한다.

WEXITSTATUS(status): 자식 프로세스의 종료 상태를 반환한다. 이 매크로는 WIFEXITED가 참을 반환했을 때만 써야 한다.

WIFSIGNALED(status): 자식 프로세스가 시그널로 종료되었으면 참을 반환한다.

WIFSTOPPED(status): 자식 프로세스가 정지된 상태라면 참을 반환한다

예시 2) 자식 프로세스가 정상적으로 종료됐으므로 WIFEXITED의 조건문이 실행된다.

int main()
{
	int pid, status;

	printf("Parent: %d\n", getpid());
	pid = fork();
	if (pid == 0)
	{
		printf("Child %d\n", getpid());
		sleep(2);
		return (0);
	}
	waitpid(pid, &status, 0);
	if (WIFSIGNALED(status))
		printf("Error\n");
	else if (WEXITSTATUS(status))
		printf("Exited Normally\n");
	else if (WIFEXITED(status))
		printf("Parent: %d\n", getpid());
	return 0;
}

 

4. waitpid

#include <sys/wait.h>
pid_t  waitpid(pid_t pid, int *statloc, int options);

waitpid 함수는 wait 함수와 거의 똑같은데, 옵션을 추가해 동작 방식을 변경할 수 있다.

아래는 옵션들(3번째 인자 종류)이다. 인자가 0일 경우에는 wait 함수와 동일하게 작동한다.

WNOHANG: 기다리는 PID가 종료되지 않아서 즉시 종료 상태를 회수할 수 없는 상황일 때 0을 반환

WUNTRACED: 중단된 자식 프로세스의 상태를 반환

WCONTINUED: 중단되었다가 재개된 자식 프로세스의 상태를 반환

에시 1) WNOHANG 사용

#include <time.h>

int main()
{
	pid_t pid;
	int status;
	time_t t;

	pid = fork();
	if (pid < 0)
		perror("fork error");
	else if (pid == 0)
	{
		sleep(3);
		exit(1);
	}
	else do
	{
		pid = waitpid(pid, &status, WNOHANG);
		if (pid == -1)
			perror("wait error");
    	else if (pid == 0)
		{
			time(&t);
			printf("child is still running at %s", ctime(&t));
			sleep(1);
		}
    	else
		{
			if (WIFEXITED(status))
				printf("child exited with status of %d\n", WEXITSTATUS(status));
			else
				puts("child did not exit successfully");
    	}
	} while (pid == 0);
}

/*
child is still running at Wed Sep  7 12:27:32 2022
child is still running at Wed Sep  7 12:27:33 2022
child is still running at Wed Sep  7 12:27:34 2022
child exited with status of 1
*/

사실 이 과제에서 WNOHANG을 사용했다는 것은 좋은 징조가 아니다. dup2 후 파이프를 전부 다 닫아주면

WNOHANG을 사용하지 않아도 잘 넘어가진다. 파이프를 잘 안 닫았을 경우 yes | head -1이나 

/dev/urandom | head -1 같은 경우에 head 한 결과가 출력도 안되고 프로그램이 끝나지도 않는다. 

728x90
반응형

'42 SEOUL > pipex' 카테고리의 다른 글

42 서울 pipex Mandatory  (0) 2022.12.19
42 서울 pipex 정리 2 (access, dup2, execve, perror, strerror)  (2) 2022.09.09
728x90
반응형

소집해제 후 1년 9개월 만에 42로 돌아왔다. 과제들이 상당히 많이 바뀐 것 같다. 

netwhat, ft_server, ft_service 등등이 사라지고 ft_printf가 쉬워졌고, 다른 과제들이 많이 추가됐다.

예전에는 과제들 하나하나가 배경 지식 하나도 없이 처음부터 너무 깊게 들어가야 되는 느낌이었는데

지금은 서클 순서대로 개념을 확장시키면서 공부할 수 있도록 과제들이 배치가 된 것 같다. 과제 별로 어떤 것을 공부해야 

하는지도 예전보다 뚜렷하게 보이는 것 같아서 참 좋다. Ex) pipex -> 프로세스, push_swap -> 자료구조 알고리즘

다만 norminette가 바뀌어서 기존 코드 다 수정하는 건 조금 귀찮긴 했다. libftㅜㅜ

일단 지금 하고 있는 pipex 과제부터 정리하려고 한다. get_nextline, ft_printf는 exam02 볼 때 정리할 것 같다.

728x90
반응형

'42 SEOUL > 잡담' 카테고리의 다른 글

42 서울 본과정 시작  (0) 2020.10.06
728x90
반응형

오늘은 프로그래머스에서 진행되는 코테 모의고사 2차 문제를 풀어봤다. 난이도가 조금 쉬운 것 같다.

1차도 풀긴 했는데 시험 종료하면 문제랑 코드를 못 본다는 사실을 몰랐어서 그냥 종료해버렸다ㅜ 

https://career.programmers.co.kr/competitions/2627

 

코딩테스트 실전 대비 모의고사

접수   22년 07월 13일 10:00 ~ 08월 23일 23:59 테스트   22년 07월 13일 10:00 ~ 08월 23일 23:59

career.programmers.co.kr

 

1번

1

설명할 필요도 없을 듯 하다. python의 combinations를 쓰면 조합을 바로 구할 수 있다.

from itertools import combinations

def solution(number):
    return len([s for s in combinations(number, 3) if sum(s) == 0])

 

2번

2

처음에 그냥 리스트를 쪼개고 set 함수를 사용해서 풀었는데 시간 초과가 나왔다

EX) len(set(topping[:i]) == len(set(topping[i:])

딕셔너리를 사용해서 해결했다. 역시 빠르다 우리 사전!

from collections import Counter, defaultdict

def solution(topping):
    front, back = defaultdict(int), dict(Counter(topping))
    ans = 0
    
    for t in topping:
        front[t] += 1
        back[t] -= 1
        if back[t] == 0:
            del back[t]
        if len(front) == len(back):
            ans += 1

    return ans

 

3번

3

문제의 글만 보면 군인들이 목적지로 향하는 상황이지만

우리가 문제를 풀 때는 목적지가 군인들을 찾아간다고 생각하는 것이 풀기 쉽다.

목적지 기준으로 bfs 알고리즘을 사용해서 다른 모든 위치까지의 거리를 구하면 사실상 끝이다.

from collections import deque

def solution(n, roads, sources, destination):
    graph = [[] for _ in range(n+1)]
    ans = []
    
    for s, e in roads:
        graph[s].append(e)
        graph[e].append(s)

    check = [-1 for _ in range(n+1)]
    check[destination] = 0
    q = deque([destination])
    while q:
        i = q.popleft()
        for node in graph[i]:
            if check[node] == -1:
                check[node] = check[i]+1
                q.append(node)

    for s in sources:
        ans.append(check[s])

    return ans
728x90
반응형
728x90
반응형

거리와 탐색 관련된 문제이기 때문에 BFS를 사용해서 풀었다.

 

BFS 풀이 

마지막에 check_li.count(max(check_li))로 한 번에 제일 먼 노드의 개수를 구할 수 있다.

from collections import deque

def solution(N, edge):
    graph = [[] for _ in range(N+1)]
    q = deque([1])
    check_li = [-1]*(N+1)
    check_li[1] = 0
    
    for s, e in edge:
        graph[s].append(e)
        graph[e].append(s)
    
    while q:
        node = q.popleft()
        for n in graph[node]:
            if check_li[n] == -1:
                q.append(n)
                check_li[n] = check_li[node] + 1
    
    return check_li.count(max(check_li))
728x90
반응형
728x90
반응형

간단한 DFS, BFS 문제이다. DFS, BFS를 안 사용하고도 풀어봤다.

 

DFS 풀

def dfs(n, numbers, target, i):
    if i == len(numbers):
        return 1 if n == target else 0
    return dfs(n + numbers[i], numbers, target, i+1) + dfs(n - numbers[i], numbers, target, i+1)

def solution(numbers, target):
    return dfs(0, numbers, target, 0)

생각해보니 굳이 dfs 함수를 만들 필요도 없었다.

아래와 같은 방식으로 numbers 리스트의 앞에 값을 빼가면서, target이 0이 되는지 확인하는 방법도 있다.

def solution(numbers, target):
    if not numbers:
        return 1 if target == 0 else 0
    return solution(numbers[1:], target + numbers[0]) + solution(numbers[1:], target - numbers[0])

 

BFS 풀이

from collections import deque

def solution(numbers, target):
    q = deque([(0, 0)])
    ans = 0

    while q:
        n, i = q.popleft()
        if i == len(numbers):
            if n == target:
                ans += 1
        else:
            q.append([n + numbers[i], i+1])                
            q.append([n - numbers[i], i+1])                
        
    return ans

 

풀이 DFS, BFS 사용 X

def solution(numbers, target):
    res_li = [0]
    for n in numbers:
        li = []
        for res in res_li:
            li.append(res + n)
            li.append(res - n)
        res_li = li
    
    return res_li.count(target)
728x90
반응형
728x90
반응형

맨 앞 스킬부터 순서대로 나오는지 확인해야 되기 때문에, 큐를 사용해서 풀었다.

첫 번째 풀이는 파이썬의 for-else문을 몰라서 check 변수를 사용한 것이다.

두 번째 풀이는 for-else문을 사용했다.

 

풀이 for-else X

1. 스킬 순서를 담는 큐를 만들어준다.

2. 스킬 순서에 포함되는 스킬이 나왔는데 선행 스킬이 아니면 불가능한 스킬트리이므로 check = 0을 해준다.

3. 반대로 선행 스킬이 맞다면 스킬 순서 큐를 popleft 해준다.

4. ans += check(1: 올바른 스킬트리, 0: 불가능한 스킬트리) 

def solution(skill, skill_trees):
    ans = 0
    for skill_tree in skill_trees:
        q = list(skill)
        check = 1
        for s in skill_tree:
            if s in q and s != q.pop(0):
                check = 0
                break
        ans += check
        
    return ans

 

풀이 for-else O

for 문이 break 등으로 중간에 빠져나오지 않고 끝까지 실행됐을 경우 else문이 실행된다.

check 변수가 필요 없어지기에 코드 한 줄이 줄었다.

from collections import deque

def solution(skill, skill_trees):
    ans = 0
    for skill_tree in skill_trees:
        q = deque(skill)
        for s in skill_tree:
            if s in q and s != q.popleft():
                break
        else:
            ans += 1
        
    return ans
728x90
반응형

+ Recent posts