2025년 3월 19일 수요일

리눅스 커널 파일 시스템 [2] 코드 분석 - generic_file_read_iter()

Page cache와 관련된 코드를 함께 먼저 보기 위해 generic_file_read_iter() 부터 분석한다.

이전 게시물


mm/filemap.c

generic_file_read_iter() 정의

먼저 이 함수 위 주석을 보자.

/**
 * generic_file_read_iter - generic filesystem read routine
 * @iocb: kernel I/O control block
 * @iter: destination for the data read
 *
 * This is the "read_iter()" routine for all filesystems
 * that can use the page cache directly.
 *
 * The IOCB_NOWAIT flag in iocb->ki_flags indicates that -EAGAIN shall
 * be returned when no data can be read
without waiting for I/O requests
 * to complete; it doesn't prevent readahead.
--> IOCB_NOWAIT flag가 있으면 prefetch를 막지 않는다고 함.
--> "I/O requests가 complete 되는 것을 기다리지 않고" -EAGAIN이 리턴된다는 것임.
--> Prefetch (readahead) 던져 놓고 리턴될거야~ 라는 것인 듯.
 *
 * The IOCB_NOIO flag in iocb->ki_flags indicates that no new I/O
 * requests shall be made for the read or for readahead.  When no data
 * can be read, -EAGAIN shall be returned.  When readahead would be
 * triggered, a partial, possibly empty read shall be returned.
--> IOCB_NOIO flag는 read/readahead를 위한 I/O request가 없을 거라는 것임.
--> 데이터를 읽을 수 없을 때 -EAGAIN이 return 됨.
--> readahead가 trigger될 수도 있지만 empty read가 return 되야함.
 *
 * Return:
 * * number of bytes copied, even for partial reads
 * * negative error code (or 0 if IOCB_NOIO) if nothing was read
 */


ssize_t
generic_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
size_t count = iov_iter_count(iter);
ssize_t retval = 0;
if (!count)
return 0; /* skip atime */
if (iocb->ki_flags & IOCB_DIRECT) {
                --> ext4에서 넘어왔다면 IOCB_DIRECT가 ext4_dio_read_iter() 를 불렀을 것이다. 
                --> 이쪽을 타지 않음.
...
}
return filemap_read(iocb, iter, retval);
}


역시 함수 위 주석을 먼저 보자.
/**
 * filemap_read - Read data from the page cache.
 * @iocb: The iocb to read.
 * @iter: Destination for the data.
 * @already_read: Number of bytes already read by the caller.
 *
 * Copies data from the page cache.  If the data is not currently present,
 * uses the readahead and read_folio address_space operations to fetch it.
 *
 * Return: Total number of bytes copied, including those already read by
 * the caller.  If an error happens before any bytes are copied, returns
 * a negative error number.
 */

[작성 예정]

2023년 6월 2일 금요일

[C++ 20] Study 004 - 새로 알게된 것, 생소한 개념

본 Study 문서들은 '전문가를 위한 C++, Marc Gregoire 저' 책을 보고 작성하였다.

생소한 개념에 대해서 페이지 번호를 적도록 하겠다. 하나 하나 자세히 작성하면서 보니 너무 느리게 보게 된다.

C++은 C와 달리 (C의 특정 컴파일러는 매개변수를 받지 않는 경우 void를 적어야 할 수 있다.) 매개변수를 받지 않는 경우 void를 적지 않아도 된다.

auto 리턴 타입 - 90p

__func__: 현재 함수의 이름

attribute - 91p

[[nodiscard]] - 92p

[[maybe_unused]] - 93p

[[noreturn]] - 93p

[[deprecated]] - 95p

[[likely]], [[unlikely]] - 95p

배열 크기 구하기 - size_t arraySize { std::size(myArray) }; - 97p

C++에서 배열 사용 - std::array나 std::vector 사용 추천 - 98p

템플릿 - 12장

optional - 101p

구조적 바인딩 - 102p


제네릭 프로그래밍 - 재사용 가능한 코드 작성 - 6장 -->  이를 지원하기 위한 핵심 요소가 템플릿 - 12장

design principle of generality

객체: 데이터와 동작을 하나로 묶은 것

value에 대한 매개변수화를 확장 --> type에 대한 매개변수화가 템플릿이다 - 622p --> 나중에 볼 것

[C++ 20] Study 003 - switch statement, enum class 범위 지정, 3방향 비교 연산자

이 글 부터는 html로 작성하지 않고 코드를 대충 쓸 예정이다. 속도가 안 나온다.

                                                                                    
The code will be writeen by courier style
                                                                                    

일단 작성하고 나서 마지막에 코드형식을 바꾸도록 하자.

switch 문에 지정할 수 있는 표현식은 결괏값이 정수 타입이거나, 정수 타입으로 변환할 수 있는 타입이거나, 열거 타입이거나, 강타입 열거타입이어야 한다.

enum class mode { a, b, c };

mode md { a };
switch (md) {
    using enum mode;
    case a:
        // do something;
        [[fallthrough]]
        /* 컴파일러가 경고문을 띄우지 않게 함. 의도적인 fallthrough 임을 컴파일러에게 알린다. 어트리뷰트 중 하나임. */
    case b:
    case c:
        // do something;
        break;
}

위와 같이 using enum을 switch 문 안에서만 사용하도록 할 수 있다. 또한 switch 문에서도 초기자를 사용할 수 있다.

다음으로 3방향 비교 연산자(three-way comparison operator)에 대해 알아보자. <=> 로 표현되는 이 연산자는 표현식의 평가 결과가 같은지, 큰지, 작은지 알려준다. 정수 타입의 경우 strong_ordering, 부동소수점 타입의 경우 partial_ordering, 직접 정의한 타입에 대해 weak_ordering이 있다. 예시는 다음과 같다.

int i { 1 };
strong_ordering result { i <=> 0 };
if (result == strong_ordering::equal) { cout << "equal" << endl; };
if (result == strong_ordering::greater) { cout << "greater" << endl; };
if (result == strong_ordering::less) { cout << "less" << endl; };

named comparison function으로 비교결과를 알 수도 있다.

int i { 1 };
strong_ordering result { i <=> 0 };
if (is_eq(result)) { cout << "equal" << endl; };
if (is_gt(result)) { cout << "greater" << endl; };
if (is_lt(result)) { cout << "less" << endl; };



2023년 6월 1일 목요일

[C++ 20] Study 002 - enum 대신 enum class 사용, 모듈 만들어서 export, import 하기. 초기자(initializer)

enum 대신 enum class를 사용하라. enum class는 strongly typed enumeration type으로서 값이 정수로 변환되지 않고, 열거 타입 값의 scope가 자동으로 확장되지 않아 상위 스코프의 이름과 같은 이름을 사용하더라도 충돌이 생기지 않는다.


enum class PieceType {King, Queen, Rook};

PieceType piece { PieceType::King };

다음으로 모듈을 만들어보자. 모듈을 만들기 위해 모듈 파일을 만든다. 확장자는 cpp나 ixx로 하면 된다 (둘의 차이는 아직 모르겠음. MSVC 기준). 솔루션 탐색기에서 모듈 파일 우클릭 --> 속성 클릭 --> C/C++ --> 고급 --> 컴파일 옵션을 C++모듈 코드로 컴파일(/interface)로 변경한다.

모듈 파일의 내용은 다음과 같다.

// newModule.ixx
export module newModule;
메인 파일에서 해당 모듈을 import 한다. 메인 파일의 속성에서 컴파일 옵션은 C++ 모듈 내부 파티션으로 컴파일(/internalPartition) 이다.

// main.cpp
import newModule;

C++ 17에서는 초기자(initializer)가 소개되었다. if statement 안에서만 쓰이는 변수를 만들 수 있다 (붙어있는 else if 와 else 문에서도 쓸 수 있다.). 다음과 같이 사용한다.


if (int i{ 1 }; i > 0) cout << "larger than one" << endl;

2023년 5월 31일 수요일

[C++ 20] Study 001 - visual C++에서 모듈 임포트 (module import), 균일 초기화 (uniform initialization), std::numeric_limits 클래스 템플릿 기초

C++20부터 추가된 모듈 기능을 이용하기. visual C++를 사용한다. 모듈 import를 사용하기 위해 다음을 수행해야한다.

1. main.cpp 파일을 만들고 속성 페이지에 들어간다. Alt + p, p
2. "C/C++ --> 언어"에서 "C++ 언어 표준"을 "ISO C++ 20 표준(/std:c++20)"으로 바꾼다.
3. "실험용 C++ 표준 라이브러리 모듈 사용"을 "예(/experimental:module)"로 바꾼다.
4. "C/C++ --> 고급"으로 들어간다.
5. "컴파일 옵션"을 "C++ 모듈 내부 파티션으로 컴파일(/internalPartition)"으로 변경한다.

import <iostream>;
// 기존에는 include <iostream>
// 세미콜론을 끝에 붙여야 한다.

어떤 모듈을 사용하고 싶다면 import 문으로 불러온다. C++ 표준 라이브러리에서 제공하는 모든 기능은 모듈로 제공된다. 직접 모듈을 정의하는 것도 가능하다.
C 표준 라이브러리 헤더는 import로 불러오지 못할 수 있음. #include <abc.h>와 같이 작성한다.

용어정리
directive: 전처리기에 전달할 사항 표현. # 문자로 시작함.

초기화 시 기존 대입 문법 대신 균일 초기화(unifrom initalization)를 사용하는 것이 바람직하다.


// 기존 대입 문법 초기화
int initialized = 1;
// 균일 초기화 사용
int initialized_by_ui { 1 };
// long long 형의 경우 LL을 붙인다.
long long longlong_var { 135LL };
// unsigned 의 경우 U를 붙인다. ULL
unsigned long long ulonglong_var { 135ULL };
// bool 타입은 true나 false로 초기화
bool bool_var { true };
// 문자열이 아닌 바이트를 다루기 위해서는 
// 헷갈리지 않게 std::byte를 사용하면 좋다.
std::byte byte_var { 20 };

숫자 경곗값과 특별한 부동소수점수를 구할 때 std::numeric_limits 클래스 템플릿을 활용할 수 있다.


cout << numeric_limits<int>::max();
cout << numeric_limits<int>::min();
cout << numeric_limits<int>::lowest();

// 특별한 부동소수점수: NaN (Not a Number) 인지 
// 혹은 무한인지 알아내기
std::isnan();
std::isinf();
std::numeric_limits<double>::infinity

외워두면 좋은 간단한 연산자 우선순위. 위에 있을수록 더 높은 우선순위임을 뜻한다.


++ -- (사후 증가/감소)
! ++ -- (사전 증가/감소)
/ * %
+ -
// 다음 순서대로 비트 연산
// 비트 연산의 우선순위가 낮음을 인지할 것
<< >>
&
^
|
= += -= *= /= %= &= |=^= <<= >>=

2023년 5월 29일 월요일

[C++] Study - namespace, reference 기본

이름공간(namespace)

std: C++ 표준 라이브러리의 모든 함수, 객체 등이 정의된 namespace

중복된 이름을 가진 객체를 구분하기 위해 쓰임.


참조자(reference): 변수나 상수를 가리키는 포인터 이외의 방식


int a1 = 2;
int& a2 = a1;

a2 = 4;
// a1 값과 a2의 값은 4로 똑같다.


포인터와의 차이점: 정의 시에 누구의 참조인지 명시해야 함. 참조 대상은 변할 수 없음. 레퍼런스는 메모리 상에 존재하지 않을 수 있음.


int x;
int& y = x;
int& z = y;	// int& z = x; 와 동일
// y와 z는 모두 x의 참조자임.
예를 들어 cin은 레퍼런스로 값을 받아들인다.

상수도 레퍼런스를 가질 수 있지만 상수는 변하는 값이 아니기 때문에 const로 선언 시에만 레퍼런스를 가질 수 있다.


const int& ref = 5;
레퍼런스 배열은 존재할 수 없지만 배열의 레퍼런스는 가능하다.


// 다음은 불가능
// int& ref[2] = {arr1, arr2};
// 다음은 가능
int arr[2] = {1, 2};
int(&ref)[2] = arr;

ref[1] = 5;