From 89baa542ffed17dfc86b96a65309f0ecb1970c26 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 21 Sep 2025 14:45:11 +0000 Subject: [PATCH 01/52] chore: add vscode C/C++ include settings --- .vscode/c_cpp_properties.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .vscode/c_cpp_properties.json diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 00000000..85d50b18 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,17 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + "macFrameworkPath": [], + "compilerPath": "/usr/bin/gcc", + "intelliSenseMode": "linux-gcc-x64", + "cStandard": "c17", + "cppStandard": "c++17" + } + ], + "version": 4 +} From e7fefe3a3adac2504a3b8ba45654f07d652baced Mon Sep 17 00:00:00 2001 From: nukopy Date: Mon, 22 Sep 2025 00:12:18 +0900 Subject: [PATCH 02/52] chore: add clang-format setting file --- .clang-format | 246 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..b68024fe --- /dev/null +++ b/.clang-format @@ -0,0 +1,246 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + AlignFunctionPointers: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseColons: false +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowBreakBeforeNoexceptSpecifier: Never +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAdjacentStringLiterals: true +BreakAfterAttributes: Leave +BreakAfterJavaFieldAnnotations: false +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: false +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +KeepEmptyLinesAtEOF: false +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: BinPack +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakScopeResolution: 500 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +PPIndentWidth: -1 +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SkipMacroDefinitionBody: false +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterPlacementOperator: true + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +... + From 87d101c28e949c6eb377c26abf50b693c9ebae3e Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 21 Sep 2025 15:19:07 +0000 Subject: [PATCH 03/52] chore: add clang-format setting --- .vscode/settings.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..8e91d6c0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "editor.formatOnSave": true, + "clang-format.executable": "/usr/bin/clang-format", + "[c]": { + "editor.defaultFormatter": "xaver.clang-format", + } +} From 37add5906a9cb48cd4d635ded2db9bff6aa83401 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 21 Sep 2025 15:19:35 +0000 Subject: [PATCH 04/52] feat: add empty files to impl network device --- driver/dummy.c | 0 driver/dummy.h | 0 net.c | 0 net.h | 0 test/step1.c | 0 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 driver/dummy.c create mode 100644 driver/dummy.h create mode 100644 net.c create mode 100644 net.h create mode 100644 test/step1.c diff --git a/driver/dummy.c b/driver/dummy.c new file mode 100644 index 00000000..e69de29b diff --git a/driver/dummy.h b/driver/dummy.h new file mode 100644 index 00000000..e69de29b diff --git a/net.c b/net.c new file mode 100644 index 00000000..e69de29b diff --git a/net.h b/net.h new file mode 100644 index 00000000..e69de29b diff --git a/test/step1.c b/test/step1.c new file mode 100644 index 00000000..e69de29b From 65da91d24875e1f97e3a76be8ec0f6ec60bea99e Mon Sep 17 00:00:00 2001 From: nukopy Date: Mon, 22 Sep 2025 00:25:51 +0900 Subject: [PATCH 05/52] chore: add format command --- Makefile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index f5f40c10..714b4578 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -APPS = +APPS = -DRIVERS = +DRIVERS = OBJS = util.o \ @@ -21,7 +21,7 @@ endif .SUFFIXES: .SUFFIXES: .c .o -.PHONY: all clean +.PHONY: all clean format all: $(APPS) $(TESTS) @@ -36,3 +36,6 @@ $(TESTS): %.exe : %.o $(OBJS) $(DRIVERS) test/test.h clean: rm -rf $(APPS) $(APPS:.exe=.o) $(OBJS) $(DRIVERS) $(TESTS) $(TESTS:.exe=.o) + +format: + find . -name "*.c" -o -name "*.h" | xargs clang-format -i --sort-includes From 5e61a1a25ec261df16624b6d2b1458effd7576a4 Mon Sep 17 00:00:00 2001 From: nukopy Date: Mon, 22 Sep 2025 00:26:05 +0900 Subject: [PATCH 06/52] chore: format all with `make format` --- platform/linux/platform.h | 32 +--- test/step0.c | 12 +- test/test.h | 26 ++- util.c | 335 ++++++++++++++++++-------------------- util.h | 96 +++++------ 5 files changed, 225 insertions(+), 276 deletions(-) diff --git a/platform/linux/platform.h b/platform/linux/platform.h index 0b8bce2c..e1dd9107 100644 --- a/platform/linux/platform.h +++ b/platform/linux/platform.h @@ -1,25 +1,17 @@ #ifndef PLATFORM_H #define PLATFORM_H +#include #include #include -#include /* * Memory */ -static inline void * -memory_alloc(size_t size) -{ - return calloc(1, size); -} +static inline void *memory_alloc(size_t size) { return calloc(1, size); } -static inline void -memory_free(void *ptr) -{ - free(ptr); -} +static inline void memory_free(void *ptr) { free(ptr); } /* * Mutex @@ -29,22 +21,16 @@ typedef pthread_mutex_t mutex_t; #define MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER -static inline int -mutex_init(mutex_t *mutex) -{ - return pthread_mutex_init(mutex, NULL); +static inline int mutex_init(mutex_t *mutex) { + return pthread_mutex_init(mutex, NULL); } -static inline int -mutex_lock(mutex_t *mutex) -{ - return pthread_mutex_lock(mutex); +static inline int mutex_lock(mutex_t *mutex) { + return pthread_mutex_lock(mutex); } -static inline int -mutex_unlock(mutex_t *mutex) -{ - return pthread_mutex_unlock(mutex); +static inline int mutex_unlock(mutex_t *mutex) { + return pthread_mutex_unlock(mutex); } #endif diff --git a/test/step0.c b/test/step0.c index 80f28c3f..59f9f548 100644 --- a/test/step0.c +++ b/test/step0.c @@ -1,12 +1,10 @@ -#include "util.h" #include "test.h" +#include "util.h" -int -main(void) -{ - debugf("Hello, World!"); +int main(void) { + debugf("Hello, World!"); - debugdump(test_data, sizeof(test_data)); + debugdump(test_data, sizeof(test_data)); - return 0; + return 0; } diff --git a/test/test.h b/test/test.h index 0cc02a54..c8fcbafd 100644 --- a/test/test.h +++ b/test/test.h @@ -3,32 +3,26 @@ #include -/* Scope of Internet host loopback address. see https://tools.ietf.org/html/rfc5735 */ +/* Scope of Internet host loopback address. see + * https://tools.ietf.org/html/rfc5735 */ #define LOOPBACK_IP_ADDR "127.0.0.1" #define LOOPBACK_NETMASK "255.0.0.0" #define ETHER_TAP_NAME "tap0" -/* Scope of EUI-48 Documentation Values. see https://tools.ietf.org/html/rfc7042 */ +/* Scope of EUI-48 Documentation Values. see https://tools.ietf.org/html/rfc7042 + */ #define ETHER_TAP_HW_ADDR "00:00:5e:00:53:01" -/* Scope of Documentation Address Blocks (TEST-NET-1). see https://tools.ietf.org/html/rfc5731 */ +/* Scope of Documentation Address Blocks (TEST-NET-1). see + * https://tools.ietf.org/html/rfc5731 */ #define ETHER_TAP_IP_ADDR "192.0.2.2" #define ETHER_TAP_NETMASK "255.255.255.0" #define DEFAULT_GATEWAY "192.0.2.1" const uint8_t test_data[] = { - 0x45, 0x00, 0x00, 0x30, - 0x00, 0x80, 0x00, 0x00, - 0xff, 0x01, 0xbd, 0x4a, - 0x7f, 0x00, 0x00, 0x01, - 0x7f, 0x00, 0x00, 0x01, - 0x08, 0x00, 0x35, 0x64, - 0x00, 0x80, 0x00, 0x01, - 0x31, 0x32, 0x33, 0x34, - 0x35, 0x36, 0x37, 0x38, - 0x39, 0x30, 0x21, 0x40, - 0x23, 0x24, 0x25, 0x5e, - 0x26, 0x2a, 0x28, 0x29 -}; + 0x45, 0x00, 0x00, 0x30, 0x00, 0x80, 0x00, 0x00, 0xff, 0x01, 0xbd, 0x4a, + 0x7f, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x08, 0x00, 0x35, 0x64, + 0x00, 0x80, 0x00, 0x01, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x30, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5e, 0x26, 0x2a, 0x28, 0x29}; #endif diff --git a/util.c b/util.c index bd7697be..aba7d5ba 100644 --- a/util.c +++ b/util.c @@ -1,13 +1,13 @@ -#include +#include +#include +#include #include #include +#include #include #include -#include -#include -#include -#include #include +#include #include "platform.h" @@ -17,61 +17,61 @@ * Logging */ -int -lprintf(FILE *fp, int level, const char *file, int line, const char *func, const char *fmt, ...) -{ - struct timeval tv; - struct tm tm; - char timestamp[32]; - int n = 0; - va_list ap; - - flockfile(fp); - gettimeofday(&tv, NULL); - strftime(timestamp, sizeof(timestamp), "%T", localtime_r(&tv.tv_sec, &tm)); - n += fprintf(fp, "%s.%03d [%c] %s: ", timestamp, (int)(tv.tv_usec / 1000), level, func); - va_start(ap, fmt); - n += vfprintf(fp, fmt, ap); - va_end(ap); - n += fprintf(fp, " (%s:%d)\n", file, line); - funlockfile(fp); - return n; +int lprintf(FILE *fp, int level, const char *file, int line, const char *func, + const char *fmt, ...) { + struct timeval tv; + struct tm tm; + char timestamp[32]; + int n = 0; + va_list ap; + + flockfile(fp); + gettimeofday(&tv, NULL); + strftime(timestamp, sizeof(timestamp), "%T", localtime_r(&tv.tv_sec, &tm)); + n += fprintf(fp, "%s.%03d [%c] %s: ", timestamp, (int)(tv.tv_usec / 1000), + level, func); + va_start(ap, fmt); + n += vfprintf(fp, fmt, ap); + va_end(ap); + n += fprintf(fp, " (%s:%d)\n", file, line); + funlockfile(fp); + return n; } -void -hexdump(FILE *fp, const void *data, size_t size) -{ - unsigned char *src; - int offset, index; - - flockfile(fp); - src = (unsigned char *)data; - fprintf(fp, "+------+-------------------------------------------------+------------------+\n"); - for(offset = 0; offset < (int)size; offset += 16) { - fprintf(fp, "| %04x | ", offset); - for(index = 0; index < 16; index++) { - if(offset + index < (int)size) { - fprintf(fp, "%02x ", 0xff & src[offset + index]); - } else { - fprintf(fp, " "); - } +void hexdump(FILE *fp, const void *data, size_t size) { + unsigned char *src; + int offset, index; + + flockfile(fp); + src = (unsigned char *)data; + fprintf(fp, "+------+-------------------------------------------------+------" + "------------+\n"); + for (offset = 0; offset < (int)size; offset += 16) { + fprintf(fp, "| %04x | ", offset); + for (index = 0; index < 16; index++) { + if (offset + index < (int)size) { + fprintf(fp, "%02x ", 0xff & src[offset + index]); + } else { + fprintf(fp, " "); + } + } + fprintf(fp, "| "); + for (index = 0; index < 16; index++) { + if (offset + index < (int)size) { + if (isascii(src[offset + index]) && isprint(src[offset + index])) { + fprintf(fp, "%c", src[offset + index]); + } else { + fprintf(fp, "."); } - fprintf(fp, "| "); - for(index = 0; index < 16; index++) { - if(offset + index < (int)size) { - if(isascii(src[offset + index]) && isprint(src[offset + index])) { - fprintf(fp, "%c", src[offset + index]); - } else { - fprintf(fp, "."); - } - } else { - fprintf(fp, " "); - } - } - fprintf(fp, " |\n"); - } - fprintf(fp, "+------+-------------------------------------------------+------------------+\n"); - funlockfile(fp); + } else { + fprintf(fp, " "); + } + } + fprintf(fp, " |\n"); + } + fprintf(fp, "+------+-------------------------------------------------+------" + "------------+\n"); + funlockfile(fp); } /* @@ -79,83 +79,74 @@ hexdump(FILE *fp, const void *data, size_t size) */ struct queue_entry { - struct queue_entry *next; - void *data; + struct queue_entry *next; + void *data; }; -void -queue_init(struct queue_head *queue) -{ - queue->head = NULL; - queue->tail = NULL; - queue->num = 0; +void queue_init(struct queue_head *queue) { + queue->head = NULL; + queue->tail = NULL; + queue->num = 0; } -void * -queue_push(struct queue_head *queue, void *data) -{ - struct queue_entry *entry; - - if (!queue) { - return NULL; - } - entry = memory_alloc(sizeof(*entry)); - if (!entry) { - return NULL; - } - entry->next = NULL; - entry->data = data; - if (queue->tail) { - queue->tail->next = entry; - } - queue->tail = entry; - if (!queue->head) { - queue->head = entry; - } - queue->num++; - return data; +void *queue_push(struct queue_head *queue, void *data) { + struct queue_entry *entry; + + if (!queue) { + return NULL; + } + entry = memory_alloc(sizeof(*entry)); + if (!entry) { + return NULL; + } + entry->next = NULL; + entry->data = data; + if (queue->tail) { + queue->tail->next = entry; + } + queue->tail = entry; + if (!queue->head) { + queue->head = entry; + } + queue->num++; + return data; } -void * -queue_pop(struct queue_head *queue) -{ - struct queue_entry *entry; - void *data; +void *queue_pop(struct queue_head *queue) { + struct queue_entry *entry; + void *data; - if (!queue || !queue->head) { - return NULL; - } - entry = queue->head; - queue->head = entry->next; - if (!queue->head) { - queue->tail = NULL; - } - queue->num--; - data = entry->data; - memory_free(entry); - return data; + if (!queue || !queue->head) { + return NULL; + } + entry = queue->head; + queue->head = entry->next; + if (!queue->head) { + queue->tail = NULL; + } + queue->num--; + data = entry->data; + memory_free(entry); + return data; } -void * -queue_peek(struct queue_head *queue) -{ - if (!queue || !queue->head) { - return NULL; - } - return queue->head->data; +void *queue_peek(struct queue_head *queue) { + if (!queue || !queue->head) { + return NULL; + } + return queue->head->data; } -void -queue_foreach(struct queue_head *queue, void (*func)(void *arg, void *data), void *arg) -{ - struct queue_entry *entry; +void queue_foreach(struct queue_head *queue, + void (*func)(void *arg, void *data), void *arg) { + struct queue_entry *entry; - if (!queue || !func) { - return; - } - for (entry = queue->head; entry; entry = entry->next) { - func(arg, entry->data); - } + if (!queue || !func) { + return; + } + for (entry = queue->head; entry; entry = entry->next) { + func(arg, entry->data); + } } /* @@ -171,80 +162,66 @@ queue_foreach(struct queue_head *queue, void (*func)(void *arg, void *data), voi static int endian; -static int -byteorder(void) { - uint32_t x = 0x00000001; +static int byteorder(void) { + uint32_t x = 0x00000001; - return *(uint8_t *)&x ? __LITTLE_ENDIAN : __BIG_ENDIAN; + return *(uint8_t *)&x ? __LITTLE_ENDIAN : __BIG_ENDIAN; } -static uint16_t -byteswap16(uint16_t v) -{ - return (v & 0x00ff) << 8 | (v & 0xff00 ) >> 8; +static uint16_t byteswap16(uint16_t v) { + return (v & 0x00ff) << 8 | (v & 0xff00) >> 8; } -static uint32_t -byteswap32(uint32_t v) -{ - return (v & 0x000000ff) << 24 | (v & 0x0000ff00) << 8 | (v & 0x00ff0000) >> 8 | (v & 0xff000000) >> 24; +static uint32_t byteswap32(uint32_t v) { + return (v & 0x000000ff) << 24 | (v & 0x0000ff00) << 8 | + (v & 0x00ff0000) >> 8 | (v & 0xff000000) >> 24; } -uint16_t -hton16(uint16_t h) -{ - if (!endian) { - endian = byteorder(); - } - return endian == __LITTLE_ENDIAN ? byteswap16(h) : h; +uint16_t hton16(uint16_t h) { + if (!endian) { + endian = byteorder(); + } + return endian == __LITTLE_ENDIAN ? byteswap16(h) : h; } -uint16_t -ntoh16(uint16_t n) -{ - if (!endian) { - endian = byteorder(); - } - return endian == __LITTLE_ENDIAN ? byteswap16(n) : n; +uint16_t ntoh16(uint16_t n) { + if (!endian) { + endian = byteorder(); + } + return endian == __LITTLE_ENDIAN ? byteswap16(n) : n; } -uint32_t -hton32(uint32_t h) -{ - if (!endian) { - endian = byteorder(); - } - return endian == __LITTLE_ENDIAN ? byteswap32(h) : h; +uint32_t hton32(uint32_t h) { + if (!endian) { + endian = byteorder(); + } + return endian == __LITTLE_ENDIAN ? byteswap32(h) : h; } -uint32_t -ntoh32(uint32_t n) -{ - if (!endian) { - endian = byteorder(); - } - return endian == __LITTLE_ENDIAN ? byteswap32(n) : n; +uint32_t ntoh32(uint32_t n) { + if (!endian) { + endian = byteorder(); + } + return endian == __LITTLE_ENDIAN ? byteswap32(n) : n; } /* * Checksum */ -uint16_t -cksum16(uint16_t *addr, uint16_t count, uint32_t init) -{ - uint32_t sum; - - sum = init; - while (count > 1) { - sum += *(addr++); - count -= 2; - } - if (count > 0) { - sum += *(uint8_t *)addr; - } - while (sum >> 16) { - sum = (sum & 0xffff) + (sum >> 16); - } - return ~(uint16_t)sum; +uint16_t cksum16(uint16_t *addr, uint16_t count, uint32_t init) { + uint32_t sum; + + sum = init; + while (count > 1) { + sum += *(addr++); + count -= 2; + } + if (count > 0) { + sum += *(uint8_t *)addr; + } + while (sum >> 16) { + sum = (sum & 0xffff) + (sum >> 16); + } + return ~(uint16_t)sum; } diff --git a/util.h b/util.h index ea4e491d..c48e1a33 100644 --- a/util.h +++ b/util.h @@ -1,8 +1,8 @@ #ifndef UTIL_H #define UTIL_H -#include #include +#include #include /* @@ -28,34 +28,38 @@ * Time */ -#define timeval_add_usec(x, y) \ - do { \ - (x)->tv_sec += y / 1000000; \ - (x)->tv_usec += y % 1000000; \ - if ((x)->tv_usec >= 1000000) { \ - (x)->tv_sec += 1; \ - (x)->tv_usec -= 1000000; \ - } \ - } while(0); - -#define timespec_add_nsec(x, y) \ - do { \ - (x)->tv_sec += y / 1000000000; \ - (x)->tv_nsec += y % 1000000000; \ - if ((x)->tv_nsec >= 1000000000) { \ - (x)->tv_sec += 1; \ - (x)->tv_nsec -= 1000000000; \ - } \ - } while(0); +#define timeval_add_usec(x, y) \ + do { \ + (x)->tv_sec += y / 1000000; \ + (x)->tv_usec += y % 1000000; \ + if ((x)->tv_usec >= 1000000) { \ + (x)->tv_sec += 1; \ + (x)->tv_usec -= 1000000; \ + } \ + } while (0); + +#define timespec_add_nsec(x, y) \ + do { \ + (x)->tv_sec += y / 1000000000; \ + (x)->tv_nsec += y % 1000000000; \ + if ((x)->tv_nsec >= 1000000000) { \ + (x)->tv_sec += 1; \ + (x)->tv_nsec -= 1000000000; \ + } \ + } while (0); /* * Logging */ -#define errorf(...) lprintf(stderr, 'E', __FILE__, __LINE__, __func__, __VA_ARGS__) -#define warnf(...) lprintf(stderr, 'W', __FILE__, __LINE__, __func__, __VA_ARGS__) -#define infof(...) lprintf(stderr, 'I', __FILE__, __LINE__, __func__, __VA_ARGS__) -#define debugf(...) lprintf(stderr, 'D', __FILE__, __LINE__, __func__, __VA_ARGS__) +#define errorf(...) \ + lprintf(stderr, 'E', __FILE__, __LINE__, __func__, __VA_ARGS__) +#define warnf(...) \ + lprintf(stderr, 'W', __FILE__, __LINE__, __func__, __VA_ARGS__) +#define infof(...) \ + lprintf(stderr, 'I', __FILE__, __LINE__, __func__, __VA_ARGS__) +#define debugf(...) \ + lprintf(stderr, 'D', __FILE__, __LINE__, __func__, __VA_ARGS__) #ifdef HEXDUMP #define debugdump(...) hexdump(stderr, __VA_ARGS__) @@ -63,10 +67,9 @@ #define debugdump(...) #endif -extern int -lprintf(FILE *fp, int level, const char *file, int line, const char *func, const char *fmt, ...); -extern void -hexdump(FILE *fp, const void *data, size_t size); +extern int lprintf(FILE *fp, int level, const char *file, int line, + const char *func, const char *fmt, ...); +extern void hexdump(FILE *fp, const void *data, size_t size); /* * Queue @@ -75,40 +78,31 @@ hexdump(FILE *fp, const void *data, size_t size); struct queue_entry; struct queue_head { - struct queue_entry *head; - struct queue_entry *tail; - unsigned int num; + struct queue_entry *head; + struct queue_entry *tail; + unsigned int num; }; -extern void -queue_init(struct queue_head *queue); -extern void * -queue_push(struct queue_head *queue, void *data); -extern void * -queue_pop(struct queue_head *queue); -extern void * -queue_peek(struct queue_head *queue); -extern void -queue_foreach(struct queue_head *queue, void (*func)(void *arg, void *data), void *arg); +extern void queue_init(struct queue_head *queue); +extern void *queue_push(struct queue_head *queue, void *data); +extern void *queue_pop(struct queue_head *queue); +extern void *queue_peek(struct queue_head *queue); +extern void queue_foreach(struct queue_head *queue, + void (*func)(void *arg, void *data), void *arg); /* * Byteorder */ -extern uint16_t -hton16(uint16_t h); -extern uint16_t -ntoh16(uint16_t n); -extern uint32_t -hton32(uint32_t h); -extern uint32_t -ntoh32(uint32_t n); +extern uint16_t hton16(uint16_t h); +extern uint16_t ntoh16(uint16_t n); +extern uint32_t hton32(uint32_t h); +extern uint32_t ntoh32(uint32_t n); /* * Checksum */ -extern uint16_t -cksum16(uint16_t *addr, uint16_t count, uint32_t init); +extern uint16_t cksum16(uint16_t *addr, uint16_t count, uint32_t init); #endif From 3266de4f1f38e28e89939bbf11a88254a44769d1 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 21 Sep 2025 15:29:18 +0000 Subject: [PATCH 07/52] chore: add format rule --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 8e91d6c0..8d5a70fe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "editor.formatOnSave": true, + "editor.formatOnPaste": false, "clang-format.executable": "/usr/bin/clang-format", "[c]": { "editor.defaultFormatter": "xaver.clang-format", From 474f9d1b6ddf43f89eca4120ac21f8f47b2545bf Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 21 Sep 2025 15:29:52 +0000 Subject: [PATCH 08/52] feat: add empty implementation for network device --- driver/dummy.c | 17 ++++++++++++++ driver/dummy.h | 8 +++++++ net.c | 33 ++++++++++++++++++++++++++ net.h | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ test/step1.c | 19 +++++++++++++++ 5 files changed, 140 insertions(+) diff --git a/driver/dummy.c b/driver/dummy.c index e69de29b..ac3a516e 100644 --- a/driver/dummy.c +++ b/driver/dummy.c @@ -0,0 +1,17 @@ +#include +#include +#include + +#include "net.h" +#include "util.h" + +#define DUMMY_MTU UINT16_MAX /* maximum size of IP datagram */ + +static int dummy_transmit(struct net_device *dev, uint16_t type, + const uint8_t *data, size_t len, const void *dst) {} + +static struct net_device_ops dummy_ops = { + .transmit = dummy_transmit, +}; + +struct net_device *dummy_init(void) {} diff --git a/driver/dummy.h b/driver/dummy.h index e69de29b..6a4fe9ec 100644 --- a/driver/dummy.h +++ b/driver/dummy.h @@ -0,0 +1,8 @@ +#ifndef DUMMY_H +#define DUMMY_H + +#include "net.h" + +extern struct net_device *dummy_init(void); + +#endif diff --git a/net.c b/net.c index e69de29b..34d5c995 100644 --- a/net.c +++ b/net.c @@ -0,0 +1,33 @@ +#include +#include +#include + +#include "platform.h" + +#include "net.h" +#include "util.h" + +/* NOTE: if you want to add/delete the entries after net_run(), you need to + * protect these lists with a mutex. */ +static struct net_device *devices; + +struct net_device *net_device_alloc(void) {} + +/* NOTE: must not be call after net_run() */ +int net_device_register(struct net_device *dev) {} + +static int net_device_open(struct net_device *dev) {} + +static int net_device_close(struct net_device *dev) {} + +int net_device_output(struct net_device *dev, uint16_t type, + const uint8_t *data, size_t len, const void *dst) {} + +int net_input_handler(uint16_t type, const uint8_t *data, size_t len, + struct net_device *dev) {} + +int net_run(void) {} + +void net_shutdown(void) {} + +int net_init(void) {} diff --git a/net.h b/net.h index e69de29b..09b41d8c 100644 --- a/net.h +++ b/net.h @@ -0,0 +1,63 @@ +#ifndef NET_H +#define NET_H + +#include +#include + +#ifndef IFNAMSIZ +#define IFNAMSIZ 16 +#endif + +#define NET_DEVICE_TYPE_DUMMY 0x0000 +#define NET_DEVICE_TYPE_LOOPBACK 0x0001 +#define NET_DEVICE_TYPE_ETHERNET 0x0002 + +#define NET_DEVICE_FLAG_UP 0x0001 +#define NET_DEVICE_FLAG_LOOPBACK 0x0010 +#define NET_DEVICE_FLAG_BROADCAST 0x0020 +#define NET_DEVICE_FLAG_P2P 0x0040 +#define NET_DEVICE_FLAG_NEED_ARP 0x0100 + +#define NET_DEVICE_ADDR_LEN 16 + +#define NET_DEVICE_IS_UP(x) ((x)->flags & NET_DEVICE_FLAG_UP) +#define NET_DEVICE_STATE(x) (NET_DEVICE_IS_UP(x) ? "up" : "down") + +struct net_device { + struct net_device *next; + unsigned int index; + char name[IFNAMSIZ]; + uint16_t type; + uint16_t mtu; + uint16_t flags; + uint16_t hlen; /* header length */ + uint16_t alen; /* address length */ + uint8_t addr[NET_DEVICE_ADDR_LEN]; + union { + uint8_t peer[NET_DEVICE_ADDR_LEN]; + uint8_t broadcast[NET_DEVICE_ADDR_LEN]; + }; + struct net_device_ops *ops; + void *priv; +}; + +struct net_device_ops { + int (*open)(struct net_device *dev); + int (*close)(struct net_device *dev); + int (*transmit)(struct net_device *dev, uint16_t type, const uint8_t *data, size_t len, const void *dst +; +}; + +extern struct net_device *net_device_alloc(void); +extern int net_device_register(struct net_device *dev); +extern int net_device_output(struct net_device *dev, uint16_t type, + const uint8_t *data, size_t len, const void *dst); + +extern int net_input_handler(uint16_t type, const uint8_t *data, size_t len, + struct net_device *dev); + +extern int net_run(void); +extern void net_shutdown(void); +extern int net_init(void); + +#endif diff --git a/test/step1.c b/test/step1.c index e69de29b..e17f6e5e 100644 --- a/test/step1.c +++ b/test/step1.c @@ -0,0 +1,19 @@ +#include +#include +#include + +#include "net.h" +#include "util.h" + +#include "driver/dummy.h" + +#include "test.h" + +static volatile sig_atomic_t terminate; + +static void on_signal(int s) { + (void)s; + terminate = 1; +} + +int main(int argc, char *argv[]) {} From 622801b48829e3597016e4fbd6f0aefe92324934 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 21 Sep 2025 16:21:49 +0000 Subject: [PATCH 09/52] fix: syntax error --- net.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/net.h b/net.h index 09b41d8c..9fce4c3c 100644 --- a/net.h +++ b/net.h @@ -24,14 +24,18 @@ #define NET_DEVICE_STATE(x) (NET_DEVICE_IS_UP(x) ? "up" : "down") struct net_device { - struct net_device *next; + struct net_device *next; // pointer to the next net_device unsigned int index; char name[IFNAMSIZ]; - uint16_t type; + uint16_t + type; // type of the net_device (defined in net.h as NET_DEVICE_TYPE_XXX) + // ------------------------------------------------ + // varies by the type of the net_device uint16_t mtu; uint16_t flags; uint16_t hlen; /* header length */ uint16_t alen; /* address length */ + // ------------------------------------------------ uint8_t addr[NET_DEVICE_ADDR_LEN]; union { uint8_t peer[NET_DEVICE_ADDR_LEN]; @@ -44,8 +48,8 @@ struct net_device { struct net_device_ops { int (*open)(struct net_device *dev); int (*close)(struct net_device *dev); - int (*transmit)(struct net_device *dev, uint16_t type, const uint8_t *data, size_t len, const void *dst -; + int (*transmit)(struct net_device *dev, uint16_t type, const uint8_t *data, + size_t len, const void *dst); }; extern struct net_device *net_device_alloc(void); From 3750cb1218986f448b664a81d3961291c1946002 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 21 Sep 2025 16:22:50 +0000 Subject: [PATCH 10/52] chore: add LSP setting --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 8d5a70fe..b08c5430 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,7 @@ "editor.formatOnSave": true, "editor.formatOnPaste": false, "clang-format.executable": "/usr/bin/clang-format", + "clangd.path": "/usr/bin/clangd", "[c]": { "editor.defaultFormatter": "xaver.clang-format", } From 02abb876b342cad31f9acd7c14ed24b88e5276e6 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 21 Sep 2025 17:02:48 +0000 Subject: [PATCH 11/52] chore: add editor settings --- .vscode/settings.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index b08c5430..25e2d155 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,7 @@ "clangd.path": "/usr/bin/clangd", "[c]": { "editor.defaultFormatter": "xaver.clang-format", + "editor.tabSize": 2, + "editor.indentSize": "tabSize", } } From 3ddb2be0cfb03a1828ac4a2a0d9d7af98708f038 Mon Sep 17 00:00:00 2001 From: nukopy Date: Fri, 26 Sep 2025 00:28:18 +0900 Subject: [PATCH 12/52] chore: add comments about net_device --- net.h | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/net.h b/net.h index 9fce4c3c..2ee3f3d9 100644 --- a/net.h +++ b/net.h @@ -23,31 +23,49 @@ #define NET_DEVICE_IS_UP(x) ((x)->flags & NET_DEVICE_FLAG_UP) #define NET_DEVICE_STATE(x) (NET_DEVICE_IS_UP(x) ? "up" : "down") +// ネットワークデバイスの定義 +// ref: +// https://docs.google.com/presentation/d/1ID6ggxASfc_1bWiJfDy1IFKIwzxvfYy8rUBrWYTRFj8/edit?slide=id.gd328c3072b_0_1161#slide=id.gd328c3072b_0_1161 struct net_device { - struct net_device *next; // pointer to the next net_device + // 次のデバイスへのポインタ + struct net_device *next; unsigned int index; char name[IFNAMSIZ]; - uint16_t - type; // type of the net_device (defined in net.h as NET_DEVICE_TYPE_XXX) - // ------------------------------------------------ - // varies by the type of the net_device - uint16_t mtu; + + // デバイスの種別(net.h に NET_DEVICE_TYPE_XXX として定義) + uint16_t type; + + // ----- デバイスの種別によって変化する値 --------------- + uint16_t mtu; // デバイスの MTU (Maximum Transmission Unit) の値 uint16_t flags; uint16_t hlen; /* header length */ uint16_t alen; /* address length */ // ------------------------------------------------ + + // デバイスのハードウェアアドレス + // - デバイスによってアドレスサイズが異なるので大きめのバッファを用意 + // - アドレスを持たないデバイスでは値は設定されない uint8_t addr[NET_DEVICE_ADDR_LEN]; union { uint8_t peer[NET_DEVICE_ADDR_LEN]; uint8_t broadcast[NET_DEVICE_ADDR_LEN]; }; + + // デバイスドライバに実装されている関数が + // 設定された struct net_device_ops へのポインタ struct net_device_ops *ops; + + // デバイスドライバが使用するプライベートなデータへのポインタ void *priv; }; +// デバイスドライバに実装されている関数へのポインタを格納 struct net_device_ops { + // optional int (*open)(struct net_device *dev); + // optional int (*close)(struct net_device *dev); + // required: 送信関数 transmit は必須 int (*transmit)(struct net_device *dev, uint16_t type, const uint8_t *data, size_t len, const void *dst); }; From 8440b3cb5d17b7f64ed1abcceb4d69e2870af881 Mon Sep 17 00:00:00 2001 From: nukopy Date: Thu, 25 Sep 2025 15:30:09 +0000 Subject: [PATCH 13/52] feat: generate and register network device --- net.c | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/net.c b/net.c index 34d5c995..d67ef04e 100644 --- a/net.c +++ b/net.c @@ -9,12 +9,54 @@ /* NOTE: if you want to add/delete the entries after net_run(), you need to * protect these lists with a mutex. */ +// デバイスリスト(リストの先頭を指すポインタ) static struct net_device *devices; -struct net_device *net_device_alloc(void) {} +struct net_device *net_device_alloc(void) { + // pointer to new net_device + struct net_device *dev; + + // デバイス構造体のサイズのメモリを確保 + // - memory_alloc() で確保したメモリ領域は 0 で初期化されている + // - メモリが確保できなかったらエラーとして NULL を返す + // - メモリの確保と解放には memory_alloc() と memory_free() を使う + dev = memory_alloc(sizeof(*dev)); + if (!dev) { + errorf("memory_alloc() failure"); + return NULL; + } + + return dev; +} /* NOTE: must not be call after net_run() */ -int net_device_register(struct net_device *dev) {} +// デバイスの登録 +int net_device_register(struct net_device *dev) { + // (関数の初回実行時だけ動く)デバイスのインデックス番号の初期化 + // note: 関数内で static な変数を一度だけ初期化し、関数呼出し間で保持できる。 + // note: 関数に副作用を入れることになる。 + // note: マルチスレッド環境でのデータ競合に注意。 + static unsigned int index = 0; + + // デバイスのインデックス番号の設定 + // note: 後置インクリメントなので現在の値を代入してからインクリメントされる + dev->index = index++; // set, then increment + + // デバイス名を生成する(net0, net1, net2, ...) + snprintf(dev->name, sizeof(dev->name), "net%d", dev->index); + + // デバイスリストの先頭にデバイスを追加 + // ref: Day 1 - デバイスの生成と登録 + // https://docs.google.com/presentation/d/1ID6ggxASfc_1bWiJfDy1IFKIwzxvfYy8rUBrWYTRFj8/edit?slide=id.gd328c3072b_0_1179#slide=id.gd328c3072b_0_1179 + // 新規登録するデバイスの次のデバイスを、現在のデバイスリストの先頭のデバイスに設定する + dev->next = devices; + // 現在のデバイスリストの先頭のデバイスを新規登録するデバイスに設定する + devices = dev; + + infof("registered, dev=%s, type=0x%04x", dev->name, dev->type); + + return 0; +} static int net_device_open(struct net_device *dev) {} From f2291df34c4ed77234e2f69578348f4c17335ca3 Mon Sep 17 00:00:00 2001 From: nukopy Date: Fri, 26 Sep 2025 01:01:04 +0900 Subject: [PATCH 14/52] feat: enhance logging --- net.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net.c b/net.c index d67ef04e..1091f667 100644 --- a/net.c +++ b/net.c @@ -50,10 +50,14 @@ int net_device_register(struct net_device *dev) { // https://docs.google.com/presentation/d/1ID6ggxASfc_1bWiJfDy1IFKIwzxvfYy8rUBrWYTRFj8/edit?slide=id.gd328c3072b_0_1179#slide=id.gd328c3072b_0_1179 // 新規登録するデバイスの次のデバイスを、現在のデバイスリストの先頭のデバイスに設定する dev->next = devices; + // 現在のデバイスリストの先頭のデバイスを新規登録するデバイスに設定する devices = dev; + infof("registered device: dev=%s, type=0x%04x", dev->name, dev->type); + + return 0; +} - infof("registered, dev=%s, type=0x%04x", dev->name, dev->type); return 0; } From 5e5fd4162e754560f661d741c9c516de370836f8 Mon Sep 17 00:00:00 2001 From: nukopy Date: Fri, 26 Sep 2025 01:02:46 +0900 Subject: [PATCH 15/52] chore: add comments for network devices and its flags --- net.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/net.h b/net.h index 2ee3f3d9..a24f0faf 100644 --- a/net.h +++ b/net.h @@ -12,14 +12,21 @@ #define NET_DEVICE_TYPE_LOOPBACK 0x0001 #define NET_DEVICE_TYPE_ETHERNET 0x0002 +// NET_DEVICE_FLAG_UP: 0000_0000_0000_0001 = 1 #define NET_DEVICE_FLAG_UP 0x0001 +// NET_DEVICE_FLAG_LOOPBACK: 0000_0000_0001_0000 = 16 #define NET_DEVICE_FLAG_LOOPBACK 0x0010 +// NET_DEVICE_FLAG_BROADCAST: 0000_0000_0010_0000 = 32 #define NET_DEVICE_FLAG_BROADCAST 0x0020 +// NET_DEVICE_FLAG_P2P: 0000_0000_0100_0000 = 64 #define NET_DEVICE_FLAG_P2P 0x0040 +// NET_DEVICE_FLAG_NEED_ARP: 0000_0001_0000_0000 = 256 (= 16^2 * 1) #define NET_DEVICE_FLAG_NEED_ARP 0x0100 #define NET_DEVICE_ADDR_LEN 16 +// UP flag が立っていればビット演算の論理積 AND の値が非 0 になり、 +// truthy な値となる #define NET_DEVICE_IS_UP(x) ((x)->flags & NET_DEVICE_FLAG_UP) #define NET_DEVICE_STATE(x) (NET_DEVICE_IS_UP(x) ? "up" : "down") @@ -51,8 +58,8 @@ struct net_device { uint8_t broadcast[NET_DEVICE_ADDR_LEN]; }; - // デバイスドライバに実装されている関数が - // 設定された struct net_device_ops へのポインタ + // デバイスドライバに実装されている関数を + // 格納している構造体 struct net_device_ops へのポインタ struct net_device_ops *ops; // デバイスドライバが使用するプライベートなデータへのポインタ From 6631a0cf1944fccd60238e1ba35f71c6910fee59 Mon Sep 17 00:00:00 2001 From: nukopy Date: Fri, 26 Sep 2025 01:03:11 +0900 Subject: [PATCH 16/52] feat: open / close network device --- net.c | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/net.c b/net.c index 1091f667..3ab2b1a1 100644 --- a/net.c +++ b/net.c @@ -58,13 +58,55 @@ int net_device_register(struct net_device *dev) { return 0; } +static int net_device_open(struct net_device *dev) { + // デバイスの状態を確認(既に UP 状態の場合はエラーを返す) + if (NET_DEVICE_IS_UP(dev)) { + errorf("device is already opened: dev=%s", dev->name); + return -1; + } + + // デバイスドライバのオープン関数を呼び出す + // - オープン関数が設定されていない場合は呼び出しをスキップ + // - エラーが返されたらこの関数もエラーを返す + if (dev->ops->open) { + if (dev->ops->open(dev) == -1) { + errorf("failed to open device: dev=%s", dev->name); + return -1; + } + } + + // デバイスのオープンに成功したら UP フラグを立てる + dev->flags |= NET_DEVICE_FLAG_UP; + infof("opened device status: dev=%s, state=%s", dev->name, + NET_DEVICE_STATE(dev)); return 0; } -static int net_device_open(struct net_device *dev) {} +static int net_device_close(struct net_device *dev) { + // デバイスの状態を確認 + // UP 状態でないデバイスを close しようとしたときはエラーを返す + if (!NET_DEVICE_IS_UP(dev)) { + errorf("device is not opened: dev=%s", dev->name); + } + + // デバイスドライバのクローズ関数を呼び出す + // - クローズ関数が設定されていない場合は呼び出しをスキップ + // - エラーが返されたらこの関数もエラーを返す + if (dev->ops->close) { + if (dev->ops->close(dev) == -1) { + errorf("failed to close device: dev=%s"); + return -1; + } + } + + // UP フラグを落とす + dev->flags &= ~NET_DEVICE_FLAG_UP; + infof("closed device status: dev=%s, state=%s", dev->name, + NET_DEVICE_STATE(dev)); -static int net_device_close(struct net_device *dev) {} + return 0; +} int net_device_output(struct net_device *dev, uint16_t type, const uint8_t *data, size_t len, const void *dst) {} From 2dabe1dc745760f3786985d0692cde4b60d9c0e6 Mon Sep 17 00:00:00 2001 From: nukopy Date: Thu, 25 Sep 2025 17:41:26 +0000 Subject: [PATCH 17/52] feat: output data to device --- net.c | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/net.c b/net.c index 3ab2b1a1..5d5521d3 100644 --- a/net.c +++ b/net.c @@ -108,8 +108,42 @@ static int net_device_close(struct net_device *dev) { return 0; } +// デバイスへの出力 int net_device_output(struct net_device *dev, uint16_t type, - const uint8_t *data, size_t len, const void *dst) {} + const uint8_t *data, size_t len, const void *dst) { + // デバイスの状態を確認 + // UP 状態でないデバイスに出力する場合はエラーを返す + if (!NET_DEVICE_IS_UP(dev)) { + errorf("device is not opened on transmit: dev=%s", dev->name); + return -1; + } + + // データのサイズを確認 + // デバイスの MTU を超えるサイズのデータは送信できないのでエラーを返す + if (len > dev->mtu) { + errorf("data is too long on transmit: dev=%s, mtu=%u, len=%zu", dev->name, + dev->mtu, len); + return -1; + } + /* note: フォーマット指定子 %04x について + + - `%04x`: 値を 16 進数で出力する。ゼロ 4 桁で padding。 + - 幅を 4 桁に指定し、ゼロで埋める (`%0` + `4` + `x`) + - 16 進数で足りない桁は先頭を 0 で埋めて 4 桁で出力する + */ + debugf("transmit to device: dev=%s, type=0x%04x, len=%zu", dev->name, type, + len); + debugdump(data, len); + + // デバイスドライバの出力関数を呼び出す + // エラーが返されたらこの関数もエラーを返す + if (dev->ops->transmit(dev, type, data, len, dst) == -1) { + errorf("device transmit failure: dev=%s, len=%zu", dev->name, len); + return -1; + } + + return 0; +} int net_input_handler(uint16_t type, const uint8_t *data, size_t len, struct net_device *dev) {} From e7ec4f6d8d15fe52c7b5fc1d53b902c1a0b74db5 Mon Sep 17 00:00:00 2001 From: nukopy Date: Thu, 25 Sep 2025 17:45:30 +0000 Subject: [PATCH 18/52] [wip] feat: input handler for data from network device --- net.c | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/net.c b/net.c index 5d5521d3..b4e0bca2 100644 --- a/net.c +++ b/net.c @@ -131,22 +131,49 @@ int net_device_output(struct net_device *dev, uint16_t type, - 幅を 4 桁に指定し、ゼロで埋める (`%0` + `4` + `x`) - 16 進数で足りない桁は先頭を 0 で埋めて 4 桁で出力する */ - debugf("transmit to device: dev=%s, type=0x%04x, len=%zu", dev->name, type, + debugf("output to device: dev=%s, type=0x%04x, len=%zu", dev->name, type, len); debugdump(data, len); // デバイスドライバの出力関数を呼び出す // エラーが返されたらこの関数もエラーを返す if (dev->ops->transmit(dev, type, data, len, dst) == -1) { - errorf("device transmit failure: dev=%s, len=%zu", dev->name, len); + errorf("device transmit failure on output: dev=%s, len=%zu", dev->name, + len); return -1; } return 0; } +/* デバイスからの入力: デバイスが受信したパケットをプロトコルスタックに渡す関数 + +・プロトコルスタックへのデータの入口であり、デバイスドライバから呼び出されることを想定している +・複数のデバイスドライバからの入力を一手に受け取る + + デバイスドライからこの関数が呼び出されるイメージ: + + +----------------------+ + | net_input_handler() | + +-----------^----------+ + | + +------------+------------+ + | | + +--------------+ +--------------+ + | Device Driver| | Device Driver| + +------^-------+ +------^-------+ + | | + Device Device +*/ int net_input_handler(uint16_t type, const uint8_t *data, size_t len, - struct net_device *dev) {} + struct net_device *dev) { + // TODO: 今の段階では呼び出されたことがわかればよいのでデバッグ出力のみ + debugf("received data from device: dev=%s, type=0x%04x, len=%zu", dev->name, + type, len); + debugdump(data, len); + + return 0; +} int net_run(void) {} From 2a6999e607eef2e4c75ee2a9fe5323410f902cbd Mon Sep 17 00:00:00 2001 From: nukopy Date: Thu, 25 Sep 2025 17:57:47 +0000 Subject: [PATCH 19/52] fix: not specifed variable to format specifier --- net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net.c b/net.c index b4e0bca2..063dcad6 100644 --- a/net.c +++ b/net.c @@ -95,7 +95,7 @@ static int net_device_close(struct net_device *dev) { // - エラーが返されたらこの関数もエラーを返す if (dev->ops->close) { if (dev->ops->close(dev) == -1) { - errorf("failed to close device: dev=%s"); + errorf("failed to close device: dev=%s", dev->name); return -1; } } From 4a8448c746c22d0e373a38a34234b64178051b72 Mon Sep 17 00:00:00 2001 From: nukopy Date: Fri, 26 Sep 2025 04:17:26 +0900 Subject: [PATCH 20/52] feat: impl run / shutdown of protocol stack --- net.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/net.c b/net.c index 063dcad6..7e91b4c2 100644 --- a/net.c +++ b/net.c @@ -175,8 +175,65 @@ int net_input_handler(uint16_t type, const uint8_t *data, size_t len, return 0; } -int net_run(void) {} +/* プロトコルスタックの起動 -void net_shutdown(void) {} +## 呼び出しのコード例: net_run / net_shutdown -int net_init(void) {} +```c +int main(void) { + if (net_init() == -1) { + return -1; + } + + // デバイスの登録処理 + // ... + + // プロトコルスタックの起動 + if (net_run() == -1) { + return -1; + } + + // アプリケーションの処理 + // ... + + net_shutdown(); + + return 0; +} +``` +*/ +int net_run(void) { + struct net_device *dev; + + debugf("open all devices..."); + for (dev = devices; dev; dev = dev->next) { + if (net_device_open(dev) == -1) { + errorf("failed to open device on net_run: dev=%s"); + // 失敗しても for 文を抜けはしない + } + } + debugf("running..."); + + return 0; +} + +// プロトコルスタックの停止 +void net_shutdown(void) { + struct net_device *dev; + + debugf("close all devices..."); + for (dev = devices; dev; dev = dev->next) { + if (net_device_close(dev) == -1) { + errorf("failed to close device on shutting down: dev=%s"); + // 失敗しても for 文を抜けはしない + } + } + debugf("shutting down"); +} + +int net_init(void) { + // TODO: 今は何もしない。後に処理を追記。 + infof("initialized"); + + return 0; +} From 3d6fb0a439e7c11fe70c4a40bab143a64b802896 Mon Sep 17 00:00:00 2001 From: nukopy Date: Fri, 26 Sep 2025 04:17:43 +0900 Subject: [PATCH 21/52] feat: impl dummy network device --- driver/dummy.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/driver/dummy.c b/driver/dummy.c index ac3a516e..21eff155 100644 --- a/driver/dummy.c +++ b/driver/dummy.c @@ -5,13 +5,60 @@ #include "net.h" #include "util.h" -#define DUMMY_MTU UINT16_MAX /* maximum size of IP datagram */ +#define DUMMY_MTU UINT16_MAX /* maximum size of IP datagram (0 ~ 65535) */ static int dummy_transmit(struct net_device *dev, uint16_t type, - const uint8_t *data, size_t len, const void *dst) {} + const uint8_t *data, size_t len, const void *dst) { + debugf("dummy transmit to device: dev=%s, type=0x%04x, len=%zu", dev->name, + type, len); + debugdump(data, len); + // データを破棄 + // ダミーデバイスなのでデータに対しては何もしない + return 0; +} + +// デバイスドライバに実装されている関数へのポインタを格納する構造体 +// ダミーデバイス用に transmit のみ登録 static struct net_device_ops dummy_ops = { .transmit = dummy_transmit, }; -struct net_device *dummy_init(void) {} +/* ダミーデバイスの初期化 + +## ダミーデバイスの仕様 + +- 入力:なし(データを受信することはない) +- 出力:データを破棄 +*/ +struct net_device *dummy_init(void) { + struct net_device *dev; + + // ダミーデバイスを生成 + dev = net_device_alloc(); + if (!dev) { + errorf("net_device_alloc() failure on initializing dummy device"); + return NULL; + } + + // ダミーデバイスの設定 + // note: dev->name は net_device_register 内で更新されるのでここでは設定しない + dev->type = NET_DEVICE_TYPE_DUMMY; + dev->mtu = DUMMY_MTU; + // ヘッダもアドレスも存在しない + dev->hlen = 0; + dev->alen = 0; + // デバイスドライバに実装されている関数へのポインタを格納する構造体 + dev->ops = &dummy_ops; + + // デバイスを登録 + if (net_device_register(dev) == -1) { + debugf("net_device_register() failure on initializaing dummy device: " + "dev=%s, type=0x%04x", + dev->name, dev->type); + return NULL; + } + debugf("dummy device initialized: dev=%s", dev->name); + + return dev; +} From 95d2563d33b7c2824dcc1af099542e4bd26c780e Mon Sep 17 00:00:00 2001 From: nukopy Date: Fri, 26 Sep 2025 04:19:42 +0900 Subject: [PATCH 22/52] feat: enhance logging on net_init --- net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net.c b/net.c index 7e91b4c2..515388e5 100644 --- a/net.c +++ b/net.c @@ -233,7 +233,7 @@ void net_shutdown(void) { int net_init(void) { // TODO: 今は何もしない。後に処理を追記。 - infof("initialized"); + infof("initialized protocol stack"); return 0; } From f8324f540f90efed866b778e8cb2d2c7ef6f7cf3 Mon Sep 17 00:00:00 2001 From: nukopy Date: Fri, 26 Sep 2025 05:09:17 +0900 Subject: [PATCH 23/52] chore: add build target for step 1 --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 714b4578..cfb63439 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,12 @@ APPS = -DRIVERS = +DRIVERS = driver/dummy.o OBJS = util.o \ + net.o \ TESTS = test/step0.exe \ + test/step1.exe \ CFLAGS := $(CFLAGS) -g -W -Wall -Wno-unused-parameter -iquote . From 5e9fa46812cfb45a46d30b202cef64ca81e0bcd3 Mon Sep 17 00:00:00 2001 From: nukopy Date: Fri, 26 Sep 2025 05:10:48 +0900 Subject: [PATCH 24/52] feat: impl test of step 1 device management --- test/step1.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/test/step1.c b/test/step1.c index e17f6e5e..d30bb589 100644 --- a/test/step1.c +++ b/test/step1.c @@ -11,9 +11,81 @@ static volatile sig_atomic_t terminate; -static void on_signal(int s) { - (void)s; +/* シグナルハンドラ + +原則、シグナルハンドラの中では下記以外のことはしない: + +- 非同期安全な関数の呼び出し + - ref: https://www.jpcert.or.jp/sc-rules/c-sig30-c.html +- volatile sig_atomic_t 型の変数への書き込み +*/ +static void on_signal(int signal) { + /* + 一般に、I/O 関数をシグナルハンドラ内で呼び出すのは安全ではない。 + シグナルハンドラ内で I/O 関数を使用する場合は、 + 使用するシステムの非同期安全な関数を事前に確認すること。 + */ + // warnf("on_signal: signal received %d", signal); + + /* `(void)signal;` は必要? + `(void)signal;` は「この引数は使っていない」ことを意図的に示す + ためのお決まりの書き方。多くのコンパイラは未使用引数に警告を出すので、 + それを抑止する目的で入れている。 + + - 既にハンドラ内で `signal` を使って何かするなら不要。 + - 使わないならそのまま残しておけば警告を避けられる。 + - 別の書き方として、 + - `__attribute__((unused))` + - `void on_signal(int \/\*signal\*\/)` + - のようにコメントで示す例もある。 + - 単に `(void)signal;` がもっとも広く使われる方法です。 + */ + + // (void)signal; terminate = 1; } -int main(int argc, char *argv[]) {} +int main(int argc, char *argv[]) { + struct net_device *dev; + + // シグナルハンドラの設定 + // Ctrl + C が押された際に行儀よく終了するようにする + signal(SIGINT, on_signal); + + // プロトコルスタックの初期化 + if (net_init() == -1) { + errorf("net_init() failure"); + return -1; + } + + // ダミーデバイスの初期化(ダミーデバイスの生成、設定、登録) + dev = dummy_init(); + + // プロトコルスタックの起動 + if (net_run() == -1) { + errorf("net_run() failure"); + return -1; + } + + // 1 秒おきにデバイスにパケットを書き込む + // まだパケットを自力で生成できないのでテストデータを用いる + uint16_t type = 0x0800; + size_t len = sizeof(test_data); + // Ctrl + C が押されるとシグナルハンドラ on_signal() の中で + // terminate に 1 が設定され、while ループを抜ける + while (!terminate) { + // Internet Layer -> Network Interface Layer への書き込み + // Internet Layer から来たパケットをデバイスに書き込む + if (net_device_output(dev, type, test_data, len, NULL) == -1) { + errorf("net_device_output() failure"); + break; + } + + sleep(1); + } + + // プロトコルスタックの停止 + net_shutdown(); + + return 0; +} From 243b7f3ddf49843175661b6df4b00f8a29e8bfc6 Mon Sep 17 00:00:00 2001 From: nukopy Date: Fri, 26 Sep 2025 05:11:12 +0900 Subject: [PATCH 25/52] DONE Day 1 - STEP 1 From 7be488290048155c07d4afd83e17b3f7ac982d0e Mon Sep 17 00:00:00 2001 From: nukopy Date: Fri, 26 Sep 2025 05:23:15 +0900 Subject: [PATCH 26/52] START Day 1 - STEP 2 From 8acd1090c05dcf4d2b9a6e9dcce6f8a36c994d2a Mon Sep 17 00:00:00 2001 From: nukopy Date: Thu, 25 Sep 2025 20:26:20 +0000 Subject: [PATCH 27/52] feat: add template for interruption (from commit 09d6772) --- driver/dummy.c | 6 ++++++ platform/linux/intr.c | 40 +++++++++++++++++++++++++++++++++++++++ platform/linux/platform.h | 18 ++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 platform/linux/intr.c diff --git a/driver/dummy.c b/driver/dummy.c index 21eff155..d5503593 100644 --- a/driver/dummy.c +++ b/driver/dummy.c @@ -2,11 +2,15 @@ #include #include +#include "platform.h" + #include "net.h" #include "util.h" #define DUMMY_MTU UINT16_MAX /* maximum size of IP datagram (0 ~ 65535) */ +#define DUMMY_IRQ INTR_IRQ_BASE + static int dummy_transmit(struct net_device *dev, uint16_t type, const uint8_t *data, size_t len, const void *dst) { debugf("dummy transmit to device: dev=%s, type=0x%04x, len=%zu", dev->name, @@ -24,6 +28,8 @@ static struct net_device_ops dummy_ops = { .transmit = dummy_transmit, }; +static int dummy_isr(unsigned int irq, void *id) {} + /* ダミーデバイスの初期化 ## ダミーデバイスの仕様 diff --git a/platform/linux/intr.c b/platform/linux/intr.c new file mode 100644 index 00000000..8ef9c30c --- /dev/null +++ b/platform/linux/intr.c @@ -0,0 +1,40 @@ +#include +#include +#include +#include + +#include "platform.h" + +#include "util.h" + +struct irq_entry { + struct irq_entry *next; + unsigned int irq; + int (*handler)(unsigned int irq, void *dev); + int flags; + char name[16]; + void *dev; +}; + +/* NOTE: if you want to add/delete the entries after intr_run(), you need to + * protect these lists with a mutex. */ +static struct irq_entry *irqs; + +static sigset_t sigmask; + +static pthread_t tid; +static pthread_barrier_t barrier; + +int intr_request_irq(unsigned int irq, + int (*handler)(unsigned int irq, void *dev), int flags, + const char *name, void *dev) {} + +int intr_raise_irq(unsigned int irq) {} + +static void *intr_thread(void *arg) {} + +int intr_run(void) {} + +void intr_shutdown(void) {} + +int intr_init(void) {} diff --git a/platform/linux/platform.h b/platform/linux/platform.h index e1dd9107..d1d2cfa7 100644 --- a/platform/linux/platform.h +++ b/platform/linux/platform.h @@ -2,6 +2,7 @@ #define PLATFORM_H #include +#include #include #include @@ -33,4 +34,21 @@ static inline int mutex_unlock(mutex_t *mutex) { return pthread_mutex_unlock(mutex); } +/* + * Interrupt + */ + +#define INTR_IRQ_BASE (SIGRTMIN + 1) + +#define INTR_IRQ_SHARED 0x0001 + +extern int intr_request_irq(unsigned int irq, + int (*handler)(unsigned int irq, void *id), + int flags, const char *name, void *dev); +extern int intr_raise_irq(unsigned int irq); + +extern int intr_run(void); +extern void intr_shutdown(void); +extern int intr_init(void); + #endif From f1814b015422c1b68adce0ace76971e9be19030b Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 28 Sep 2025 01:27:29 +0900 Subject: [PATCH 28/52] chore: add comments about using signal number as IRQ number on microps --- platform/linux/platform.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/platform/linux/platform.h b/platform/linux/platform.h index d1d2cfa7..15317618 100644 --- a/platform/linux/platform.h +++ b/platform/linux/platform.h @@ -38,6 +38,21 @@ static inline int mutex_unlock(mutex_t *mutex) { * Interrupt */ +// IRQ 番号の開始番号 +// +/* + +microps では IRQ 番号としてシグナル番号を使用する。 + +Linux では、SIGRTMIN ~ SIGRTMAX (34 ~ 64)までのシグナルを +アプリケーションが任意の目的で利用できる。 + +ただし、SIGRTMIN (34) に関しては、glibc が内部的に利用しているため、 ++1 した番号から利用するようにしている。 + +そのため、microps の IRQ 番号は 35 始まり。 + +*/ #define INTR_IRQ_BASE (SIGRTMIN + 1) #define INTR_IRQ_SHARED 0x0001 From 4cc025f7b49b2982ba0214e004ecd2824feedfc2 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 28 Sep 2025 01:27:53 +0900 Subject: [PATCH 29/52] refactor: use readable function name --- platform/linux/platform.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platform/linux/platform.h b/platform/linux/platform.h index 15317618..3b70715d 100644 --- a/platform/linux/platform.h +++ b/platform/linux/platform.h @@ -57,9 +57,9 @@ Linux では、SIGRTMIN ~ SIGRTMAX (34 ~ 64)までのシグナルを #define INTR_IRQ_SHARED 0x0001 -extern int intr_request_irq(unsigned int irq, - int (*handler)(unsigned int irq, void *id), - int flags, const char *name, void *dev); +extern int intr_register_irq_entry(unsigned int irq, + int (*handler)(unsigned int irq, void *id), + int flags, const char *name, void *dev); extern int intr_raise_irq(unsigned int irq); extern int intr_run(void); From 50b10c94d07862eb396025a55568ddd81785e468 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 28 Sep 2025 01:28:18 +0900 Subject: [PATCH 30/52] feat: impl interrupt processing --- platform/linux/intr.c | 304 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 296 insertions(+), 8 deletions(-) diff --git a/platform/linux/intr.c b/platform/linux/intr.c index 8ef9c30c..78b8df18 100644 --- a/platform/linux/intr.c +++ b/platform/linux/intr.c @@ -7,34 +7,322 @@ #include "util.h" +// 割り込み要求(IRQ, Interrupt Request)の構造体 +// ネットワークデバイス(net_device 構造体)と同様に linked list 構造で管理する struct irq_entry { + // 次の IRQ 構造体 struct irq_entry *next; + // 割り込み番号(IRQ 番号) unsigned int irq; + // 割り込みハンドラ(割り込みが発生した際に呼び出す関数へのポインタ) int (*handler)(unsigned int irq, void *dev); + // フラグ(INTR_IRQ_SHARED が指定された場合は IRQ 番号を共有可能) int flags; + // デバッグ出力で識別するための名前 char name[16]; + /* 割り込みの発生元となるデバイス + + ---------- + + struct net_device 以外にも対応できるように void * で保持 + */ void *dev; }; /* NOTE: if you want to add/delete the entries after intr_run(), you need to * protect these lists with a mutex. */ +// IRQ リスト(リストの先頭を指すポインタ) static struct irq_entry *irqs; +// シグナルマスク用のシグナル集合 static sigset_t sigmask; +// 割り込みスレッドのスレッド ID static pthread_t tid; +// スレッド間の同期のためのバリア static pthread_barrier_t barrier; +// バリアで同期するスレッドの数 +// (今回はメインスレッド、割り込み処理スレッドの 2 つ) +static const int NUM_THREADS_FOR_BARRIER = 2; + +/* 割り込み要求エントリ(IRQ エントリ)を IRQ リストに登録する + * + * ---------- + * + * ## microps における「割り込み」の実装 + * + * microps では、IRQ 番号にシグナル番号を使用する。 + * IRQ 番号は、ハードウェア割り込みにおいては、 + * 「どのハードウェアによって割り込み要求(IRQ)が発生したか」を識別するための数値。 + * microsはユーザ空間で動くので、カーネル空間で動くハードウェア割り込みの仕組みは使えない。 + * そこで処理の割り込みを行うためにシグナルを使ったソフトウェア割り込みを実装する。 + * ハードウェア割り込みを模倣した割り込みの仕組みをユーザ空間で実装する。 + * + * ## シグナルによりソフトウェア割り込みにマルチスレッドを使用する理由 + * + * シグナル受信時に非同期に実行されるシグナルハンドラでは実行できる処理が大きく制限される。 + * シグナル安全(signal-safe)、非同期安全な処理の範囲で処理を実装しなければならない。 + * + * ref: https://www.jpcert.or.jp/sc-rules/c-sig30-c.html + * + * そのため、割り込み処理のために専用のスレッドを起動してシグナルの発生を待ち受けて処理する。 + * 割り込みの仕組みをマルチスレッドで実装する理由はこれ。 + * + */ +int intr_register_irq_entry(unsigned int irq, + int (*handler)(unsigned int irq, void *dev), + int flags, const char *name, void *dev) { + struct irq_entry *entry; + + debugf("registering new IRQ: irq=%u, flags=%d, name=%s", irq, flags, name); + + // -------------------------------------------------- + // IRQ 番号が既に登録されている場合、 + // IRQ 番号の共有が許可されているかどうかチェック + // どちらかが共有を許可していない場合はエラーを返す + // -------------------------------------------------- + + // IRQ リストを走査 + for (entry = irqs; entry; entry = entry->next) { + // IRQ リストに新規 IRQ エントリの IRQ 番号が既に登録されている場合、 + // その IRQ 番号に対して追加のハンドラを登録できるかどうか確認する。 + if (entry->irq == irq) { + /* + XOR(^) はビット単位の排他的論理和(2 つの値が異なるとき 1 になる) + 真理値表 (A ^ B): + A B | A^B + 0 0 | 0 + 0 1 | 1 + 1 0 | 1 + 1 1 | 0 + */ + /* + ・既存の IRQ のフラグが `INTR_IRQ_SHARED` と完全に一致しているか + - flags と INTR_IRQ_SHARED が一致している場合は XOR の結果は偽になる + - e.g. flags ^ INTR_IRQ_SHARED = 0001 ^ 0001 = 0000 -> 偽 + - flags と INTR_IRQ_SHARED が一致していない場合は XOR の結果は真 + - e.g. flags ^ INTR_IRQ_SHARED = 0000 ^ 0001 = 0001 -> 真 + ・新しく登録しようとしているフラグが`INTR_IRQ_SHARED`と完全に一致しているか + - + + をチェックしていて、どちらか一方でも値が違えば衝突とみなす。 + + 注意点として、XOR は「`INTR_IRQ_SHARED` + ビットが立っているかどうか」ではなく + 「値がまるごと同一かどうか」を判定している。 + フラグにほかのビットが含まれる場合は真になってしまうので、共有可否をビット単位で見る意図なら + `flags & INTR_IRQ_SHARED` のようにマスクした方が適切かもしれない。 + */ + + const int is_existing_irq_allows_share = + (entry->flags ^ INTR_IRQ_SHARED) == 0; + const int is_new_irq_allows_share = (flags ^ INTR_IRQ_SHARED) == 0; + const int is_irq_allows_share = + is_existing_irq_allows_share || is_new_irq_allows_share; + if (!is_irq_allows_share) { + errorf("conflicts with already registered IRQs"); + return -1; + } + } + } + + // -------------------------------------------------- + // IRQ リストへ新しいエントリを追加 + // -------------------------------------------------- + + // 新しい IRQ エントリのメモリを確保 + entry = memory_alloc(sizeof(*entry)); + if (!entry) { + errorf("memory_alloc() on registering IRQ entry failure"); + return -1; + } + + // IRQ エントリ構造体に値を設定 + entry->irq = irq; + entry->handler = handler; + entry->flags = flags; + strncpy(entry->name, name, sizeof(entry->name) - 1); + entry->dev = dev; + + // 新しい IRQ エントリを IRQ リストの先頭へ挿入 + entry->next = irqs; + irqs = entry; + + // シグナル集合へ新しいシグナルを追加 + // ここって既存のシグナルの被らないの? + // -> 被らない。既存のシグナルとの OR 演算なので。 + sigaddset(&sigmask, irq); + debugf("registered new IRQ entry: irq=%u, name=%s", entry->irq, entry->name); + + return 0; +} + +int intr_raise_irq(unsigned int irq) { + // 割り込み処理用のスレッドへシグナルを送信 + // microps では、IRQ 番号にシグナル番号を使用する + // 後続の処理では、 + // 発生したシグナルのシグナル番号と IRQ エントリの IRQ 番号が一致したときに + // IRQ エントリに登録されているハンドラが実行される + return pthread_kill(tid, (int)irq); +} + +// 割り込み処理を実行するスレッドのエントリーポイント +static void *intr_thread(void *arg) { + int terminate = 0; + int sig; + int err; + + struct irq_entry *entry; + + infof("start thread for interrupt"); + + // メインスレッドと同期を取るための処理 + pthread_barrier_wait(&barrier); + debugf("barrier unlocked on intr_thread!"); + + // ハードウェア割り込みに見立てたシグナルを処理するループ + while (!terminate) { + // ハードウェア割り込みに見立てたシグナルが発生するまで待機 + err = sigwait(&sigmask, &sig); + if (err) { + errorf("sigwait() failure: %s", strerror(err)); + break; + } + + // 発生したシグナルの種類に応じて処理 + switch (sig) { + // SIGHUP シグナルの処理: + // 割り込み処理用のスレッドへ終了を通知するためのシグナル + case SIGHUP: + terminate = 1; + break; + // デバイス割り込み用のシグナルを処理 + default: + // IRQ リストを巡回 + for (entry = irqs; entry; entry = entry->next) { + // IRQ 番号が一致するエントリの割り込みハンドラを呼び出す + // microps では、IRQ 番号にシグナル番号を使用する + if (entry->irq == (unsigned int)sig) { + debugf("call ISR: irq=%d, name=%s", entry->irq, entry->name); + entry->handler(entry->irq, entry->dev); + } + } + break; + } + } + warnf("terminated thread for interrupt"); + + return NULL; +} + +// 割り込み機構を起動する +// intr_init が呼ばれている状態でこの関数を呼ぶこと +int intr_run(void) { + int err; + + // シグナルマスクの設定 + // intr_init 内で sigmask の設定ができていることが前提 + err = pthread_sigmask(SIG_BLOCK, &sigmask, NULL); + if (err) { + errorf("pthread_sigmask"); + return -1; + } + + // 割り込み処理スレッドの起動 + err = pthread_create(&tid, NULL, intr_thread, NULL); + if (err) { + errorf("pthread_create() failed on intr_run: %s", strerror(err)); + return -1; + } + + // スレッドが動き出すまで待つ + // 他のスレッドが同じように pthread_barrier_wait() を呼び出し、 + // バリアのカウントが指定の数になるまでスレッドを停止する + infof("waiting for starting thread for interrupt with barrier..."); + pthread_barrier_wait(&barrier); + infof("thread for interrupt started!"); + + return 0; +} + +void intr_shutdown(void) { + int err; + + // 割り込み処理スレッドが起動済みかどうか確認 + /* Q. なぜ以下のコードで割り込み処理スレッドが起動済みかどうか確認できるのか? + * 前提:関数の実行順序は intr_init -> intr_run -> intr_shutdown + * + * `pthread_equal(tid, pthread_self())`は + *「今の呼び出し元スレッド(メインスレッド)の + * ID」と、グローバルに保持している `tid` を比較している。 + * + * ここの比較は `intr_init` の段階で `tid` に「メインスレッド(=呼び出し元) + * の `pthread_t`」を代入しておく前提になっている。 + * `intr_run` で割り込み処理スレッドを起動するときに + * `pthread_create(&tid, …)` として `tid` を新しいスレッドの ID で上書きする。 + * + * つまり、`intr_shutdown` をメインスレッドから呼ぶときに、 + * `tid` がまだメインスレッドと同じ ID のままなら + * 「新しいスレッドに置き換わっていない → スレッドを作れていない」= + *`pthread_equal` が真になる、という判定になる。 + * + * 逆に、スレッドが正常に起動済みなら `tid` は別の ID に差し替わっているので、 + * `pthread_equal` は偽になり、 `pthread_kill` や `pthread_join` + * に進む、という流れとなっている。 + + * 注意点として、このロジックは + * + * - `intr_init` をメインスレッドで呼ぶこと + * - 初期値として `tid = pthread_self()` をセットしていること + * - `intr_shutdown` をメインスレッドで呼ぶこと + * + * の 3 つが前提なので、別スレッドから呼び出したり、 + * 初期化を忘れたりすると意図通りにならない点には気をつける必要がある。 + */ + if (pthread_equal(tid, pthread_self()) != 0) { + warnf("thread not created"); + return; + } + + // 割り込み処理スレッドにシグナル(SIGHUP)を送信 + err = pthread_kill(tid, SIGHUP); + if (err) { + warnf("failed to kill process with SIGHUP: tid=%d, err=%s", tid, + strerror(err)); + return; + } + + // 割り込み処理スレッドが完全に終了するのを待つ + err = pthread_join(tid, NULL); + if (err) { + warnf("failed to pthread_join: tid=%d, err=%s", tid, strerror(err)); + return; + } + + infof("shutdown interrupt successfully!"); + + return; +} -int intr_request_irq(unsigned int irq, - int (*handler)(unsigned int irq, void *dev), int flags, - const char *name, void *dev) {} +int intr_init(void) { + infof("init interrupt"); -int intr_raise_irq(unsigned int irq) {} + // スレッド ID の初期値にメインスレッドの ID を設定する + tid = pthread_self(); -static void *intr_thread(void *arg) {} + // pthread_barrier の初期化(カウントを 2 に設定) + infof("init pthread barrier for %d threads", NUM_THREADS_FOR_BARRIER); + pthread_barrier_init(&barrier, NULL, NUM_THREADS_FOR_BARRIER); + debugf("barrier unlocked on intr_init!"); -int intr_run(void) {} + // グローバルな割り込み処理用のシグナル集合(シグナルマスク)を初期化(空にする) + infof("init sigmask"); + sigemptyset(&sigmask); -void intr_shutdown(void) {} + // シグナル集合に SIGHUP を追加(割り込みスレッド終了通知用) + sigaddset(&sigmask, SIGHUP); + // 注意:この段階ではまだプロセス(メインスレッド)にシグナルマスクは設定されていない + // pthread_sigmask が実行されて初めてシグナルマスクがプロセスに適用される -int intr_init(void) {} + return 0; +} From 3a0034666108850fae50bda13b859901f2c38d5b Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 28 Sep 2025 01:28:36 +0900 Subject: [PATCH 31/52] feat: impl interrupt processing on dummy device --- driver/dummy.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/driver/dummy.c b/driver/dummy.c index d5503593..20201dec 100644 --- a/driver/dummy.c +++ b/driver/dummy.c @@ -9,6 +9,11 @@ #define DUMMY_MTU UINT16_MAX /* maximum size of IP datagram (0 ~ 65535) */ +// ダミーデバイスが使う IRQ 番号 +// microps の IRQ 番号は 35 始まり +/* platform/linux/platform.h +#define INTR_IRQ_BASE (SIGRTMIN+1) +*/ #define DUMMY_IRQ INTR_IRQ_BASE static int dummy_transmit(struct net_device *dev, uint16_t type, @@ -19,6 +24,10 @@ static int dummy_transmit(struct net_device *dev, uint16_t type, // データを破棄 // ダミーデバイスなのでデータに対しては何もしない + + // テスト用に割り込みを発生させる + intr_raise_irq(DUMMY_IRQ); + return 0; } @@ -28,7 +37,15 @@ static struct net_device_ops dummy_ops = { .transmit = dummy_transmit, }; -static int dummy_isr(unsigned int irq, void *id) {} +static int dummy_isr(unsigned int irq, void *id) { + // 現段階ではダミーデバイス用の割り込みハンドラが呼び出されたことがわかれば良いのでデバッグ出力のみ + // FIXME: 本来は net_device 以外にも対応するため、キャストは行わない。 + // あくまで現段階ではデバッグ用 + struct net_device *dev = (struct net_device *)id; // 無理やりキャスト + debugf("ISR called: irq=%u, dev=%s", irq, dev->name); + + return 0; +} /* ダミーデバイスの初期化 @@ -66,5 +83,10 @@ struct net_device *dummy_init(void) { } debugf("dummy device initialized: dev=%s", dev->name); + // デバイスの割り込みハンドラとして dummy_isr を登録する + infof("register interrupt handler for dummy device"); + intr_register_irq_entry(DUMMY_IRQ, dummy_isr, INTR_IRQ_SHARED, dev->name, + dev); + return dev; } From 7cbefd4cad7ddf66f21d9458eb50998ee69a5ead Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 28 Sep 2025 01:30:05 +0900 Subject: [PATCH 32/52] feat: running interrupt processing on protocol stack (on `net_init`, `net_run`, `net_shutdown`) --- net.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/net.c b/net.c index 515388e5..2e75d5db 100644 --- a/net.c +++ b/net.c @@ -205,6 +205,14 @@ int main(void) { int net_run(void) { struct net_device *dev; + // 割り込み機構の起動 + debugf("start interrupt processing..."); + if (intr_run() == -1) { + errorf("intr_run() failure"); + return -1; + } + + // 登録済みの全デバイスをオープン debugf("open all devices..."); for (dev = devices; dev; dev = dev->next) { if (net_device_open(dev) == -1) { @@ -221,6 +229,7 @@ int net_run(void) { void net_shutdown(void) { struct net_device *dev; + // 登録済みの全デバイスをクローズ debugf("close all devices..."); for (dev = devices; dev; dev = dev->next) { if (net_device_close(dev) == -1) { @@ -228,11 +237,22 @@ void net_shutdown(void) { // 失敗しても for 文を抜けはしない } } + + // 割り込み機構の終了 + debugf("shutdown interrupt processing..."); + intr_shutdown(); + debugf("shutting down"); } int net_init(void) { - // TODO: 今は何もしない。後に処理を追記。 + // 割り込み機構の初期化 + debugf("initialize interrupt processing..."); + if (intr_init() == -1) { + errorf("intr_init() failure"); + return -1; + } + infof("initialized protocol stack"); return 0; From fbc0200cbbb121c03f172e0885012271cf97c6a8 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 28 Sep 2025 01:30:33 +0900 Subject: [PATCH 33/52] feat: add test for interrupt processing --- test/step2.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 test/step2.c diff --git a/test/step2.c b/test/step2.c new file mode 100644 index 00000000..d30bb589 --- /dev/null +++ b/test/step2.c @@ -0,0 +1,91 @@ +#include +#include +#include + +#include "net.h" +#include "util.h" + +#include "driver/dummy.h" + +#include "test.h" + +static volatile sig_atomic_t terminate; + +/* シグナルハンドラ + +原則、シグナルハンドラの中では下記以外のことはしない: + +- 非同期安全な関数の呼び出し + - ref: https://www.jpcert.or.jp/sc-rules/c-sig30-c.html +- volatile sig_atomic_t 型の変数への書き込み +*/ +static void on_signal(int signal) { + /* + 一般に、I/O 関数をシグナルハンドラ内で呼び出すのは安全ではない。 + シグナルハンドラ内で I/O 関数を使用する場合は、 + 使用するシステムの非同期安全な関数を事前に確認すること。 + */ + // warnf("on_signal: signal received %d", signal); + + /* `(void)signal;` は必要? + `(void)signal;` は「この引数は使っていない」ことを意図的に示す + ためのお決まりの書き方。多くのコンパイラは未使用引数に警告を出すので、 + それを抑止する目的で入れている。 + + - 既にハンドラ内で `signal` を使って何かするなら不要。 + - 使わないならそのまま残しておけば警告を避けられる。 + - 別の書き方として、 + - `__attribute__((unused))` + - `void on_signal(int \/\*signal\*\/)` + - のようにコメントで示す例もある。 + - 単に `(void)signal;` がもっとも広く使われる方法です。 + */ + + // (void)signal; + terminate = 1; +} + +int main(int argc, char *argv[]) { + struct net_device *dev; + + // シグナルハンドラの設定 + // Ctrl + C が押された際に行儀よく終了するようにする + signal(SIGINT, on_signal); + + // プロトコルスタックの初期化 + if (net_init() == -1) { + errorf("net_init() failure"); + return -1; + } + + // ダミーデバイスの初期化(ダミーデバイスの生成、設定、登録) + dev = dummy_init(); + + // プロトコルスタックの起動 + if (net_run() == -1) { + errorf("net_run() failure"); + return -1; + } + + // 1 秒おきにデバイスにパケットを書き込む + // まだパケットを自力で生成できないのでテストデータを用いる + uint16_t type = 0x0800; + size_t len = sizeof(test_data); + // Ctrl + C が押されるとシグナルハンドラ on_signal() の中で + // terminate に 1 が設定され、while ループを抜ける + while (!terminate) { + // Internet Layer -> Network Interface Layer への書き込み + // Internet Layer から来たパケットをデバイスに書き込む + if (net_device_output(dev, type, test_data, len, NULL) == -1) { + errorf("net_device_output() failure"); + break; + } + + sleep(1); + } + + // プロトコルスタックの停止 + net_shutdown(); + + return 0; +} From 42b6ceefe6f35f548cdf44cb059fb03b2afa2c99 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 28 Sep 2025 01:30:59 +0900 Subject: [PATCH 34/52] chore: add build target for interrupt processing --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index cfb63439..af06d668 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ OBJS = util.o \ TESTS = test/step0.exe \ test/step1.exe \ + test/step2.exe \ CFLAGS := $(CFLAGS) -g -W -Wall -Wno-unused-parameter -iquote . @@ -14,6 +15,7 @@ ifeq ($(shell uname),Linux) # Linux specific settings BASE = platform/linux CFLAGS := $(CFLAGS) -pthread -iquote $(BASE) + OBJS := $(OBJS) $(BASE)/intr.o endif ifeq ($(shell uname),Darwin) From 8333b466d9e574b0bdc40ee695df5ffa16fb8cd7 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 28 Sep 2025 01:31:23 +0900 Subject: [PATCH 35/52] docs: add empty architecture diagram of Excalidraw --- docs/microps-architecture.excalidraw | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 docs/microps-architecture.excalidraw diff --git a/docs/microps-architecture.excalidraw b/docs/microps-architecture.excalidraw new file mode 100644 index 00000000..401338e7 --- /dev/null +++ b/docs/microps-architecture.excalidraw @@ -0,0 +1,14 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff", + "lockedMultiSelections": {} + }, + "files": {} +} \ No newline at end of file From 878958597ecd644ea84f9952e5180aa5b03d2bf4 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 28 Sep 2025 01:34:25 +0900 Subject: [PATCH 36/52] DONE Day 1 - STEP 2: Interrupt Processing :tada: From feb0b6478fdb8f14f8a0377f0bcb82022caba6ad Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 12 Apr 2026 22:53:37 +0900 Subject: [PATCH 37/52] chore: add comments and update for Cursor basic settings --- .vscode/settings.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 25e2d155..99dbd975 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,17 @@ { "editor.formatOnSave": true, - "editor.formatOnPaste": false, - "clang-format.executable": "/usr/bin/clang-format", + "editor.formatOnPaste": true, + + // LSP "clangd.path": "/usr/bin/clangd", + + // Formatter + "clang-format.executable": "/usr/bin/clang-format", + + // Editor settings for C/C++ "[c]": { "editor.defaultFormatter": "xaver.clang-format", - "editor.tabSize": 2, + "editor.tabSize": 4, "editor.indentSize": "tabSize", } } From 2bf5cf7dfc271438a8b3b00cc7eb1173aa78e601 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 12 Apr 2026 22:54:03 +0900 Subject: [PATCH 38/52] chore: change format rules 2 -> 4 --- .clang-format | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.clang-format b/.clang-format index b68024fe..a92596f5 100644 --- a/.clang-format +++ b/.clang-format @@ -134,7 +134,7 @@ IndentExternBlock: AfterExternBlock IndentGotoLabels: true IndentPPDirectives: None IndentRequiresClause: true -IndentWidth: 2 +IndentWidth: 4 IndentWrappedFunctionNames: false InsertBraces: false InsertNewlineAtEOF: false @@ -243,4 +243,3 @@ WhitespaceSensitiveMacros: - PP_STRINGIZE - STRINGIZE ... - From 4655c1700a8455896164b00592a36f8a5b0f5bb4 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 12 Apr 2026 22:54:58 +0900 Subject: [PATCH 39/52] chore: format --- driver/dummy.c | 97 ++++---- net.c | 291 +++++++++++------------ net.h | 74 +++--- platform/linux/intr.c | 472 +++++++++++++++++++------------------- platform/linux/platform.h | 6 +- test/step0.c | 8 +- test/step1.c | 122 +++++----- test/step2.c | 122 +++++----- util.c | 275 +++++++++++----------- util.h | 46 ++-- 10 files changed, 762 insertions(+), 751 deletions(-) diff --git a/driver/dummy.c b/driver/dummy.c index 20201dec..bd9944f0 100644 --- a/driver/dummy.c +++ b/driver/dummy.c @@ -18,17 +18,17 @@ static int dummy_transmit(struct net_device *dev, uint16_t type, const uint8_t *data, size_t len, const void *dst) { - debugf("dummy transmit to device: dev=%s, type=0x%04x, len=%zu", dev->name, - type, len); - debugdump(data, len); + debugf("dummy transmit to device: dev=%s, type=0x%04x, len=%zu", dev->name, + type, len); + debugdump(data, len); - // データを破棄 - // ダミーデバイスなのでデータに対しては何もしない + // データを破棄 + // ダミーデバイスなのでデータに対しては何もしない - // テスト用に割り込みを発生させる - intr_raise_irq(DUMMY_IRQ); + // テスト用に割り込みを発生させる + intr_raise_irq(DUMMY_IRQ); - return 0; + return 0; } // デバイスドライバに実装されている関数へのポインタを格納する構造体 @@ -38,13 +38,13 @@ static struct net_device_ops dummy_ops = { }; static int dummy_isr(unsigned int irq, void *id) { - // 現段階ではダミーデバイス用の割り込みハンドラが呼び出されたことがわかれば良いのでデバッグ出力のみ - // FIXME: 本来は net_device 以外にも対応するため、キャストは行わない。 - // あくまで現段階ではデバッグ用 - struct net_device *dev = (struct net_device *)id; // 無理やりキャスト - debugf("ISR called: irq=%u, dev=%s", irq, dev->name); + // 現段階ではダミーデバイス用の割り込みハンドラが呼び出されたことがわかれば良いのでデバッグ出力のみ + // FIXME: 本来は net_device 以外にも対応するため、キャストは行わない。 + // あくまで現段階ではデバッグ用 + struct net_device *dev = (struct net_device *)id; // 無理やりキャスト + debugf("ISR called: irq=%u, dev=%s", irq, dev->name); - return 0; + return 0; } /* ダミーデバイスの初期化 @@ -55,38 +55,39 @@ static int dummy_isr(unsigned int irq, void *id) { - 出力:データを破棄 */ struct net_device *dummy_init(void) { - struct net_device *dev; - - // ダミーデバイスを生成 - dev = net_device_alloc(); - if (!dev) { - errorf("net_device_alloc() failure on initializing dummy device"); - return NULL; - } - - // ダミーデバイスの設定 - // note: dev->name は net_device_register 内で更新されるのでここでは設定しない - dev->type = NET_DEVICE_TYPE_DUMMY; - dev->mtu = DUMMY_MTU; - // ヘッダもアドレスも存在しない - dev->hlen = 0; - dev->alen = 0; - // デバイスドライバに実装されている関数へのポインタを格納する構造体 - dev->ops = &dummy_ops; - - // デバイスを登録 - if (net_device_register(dev) == -1) { - debugf("net_device_register() failure on initializaing dummy device: " - "dev=%s, type=0x%04x", - dev->name, dev->type); - return NULL; - } - debugf("dummy device initialized: dev=%s", dev->name); - - // デバイスの割り込みハンドラとして dummy_isr を登録する - infof("register interrupt handler for dummy device"); - intr_register_irq_entry(DUMMY_IRQ, dummy_isr, INTR_IRQ_SHARED, dev->name, - dev); - - return dev; + struct net_device *dev; + + // ダミーデバイスを生成 + dev = net_device_alloc(); + if (!dev) { + errorf("net_device_alloc() failure on initializing dummy device"); + return NULL; + } + + // ダミーデバイスの設定 + // note: dev->name は net_device_register + // 内で更新されるのでここでは設定しない + dev->type = NET_DEVICE_TYPE_DUMMY; + dev->mtu = DUMMY_MTU; + // ヘッダもアドレスも存在しない + dev->hlen = 0; + dev->alen = 0; + // デバイスドライバに実装されている関数へのポインタを格納する構造体 + dev->ops = &dummy_ops; + + // デバイスを登録 + if (net_device_register(dev) == -1) { + debugf("net_device_register() failure on initializaing dummy device: " + "dev=%s, type=0x%04x", + dev->name, dev->type); + return NULL; + } + debugf("dummy device initialized: dev=%s", dev->name); + + // デバイスの割り込みハンドラとして dummy_isr を登録する + infof("register interrupt handler for dummy device"); + intr_register_irq_entry(DUMMY_IRQ, dummy_isr, INTR_IRQ_SHARED, dev->name, + dev); + + return dev; } diff --git a/net.c b/net.c index 2e75d5db..3e4651fe 100644 --- a/net.c +++ b/net.c @@ -13,137 +13,138 @@ static struct net_device *devices; struct net_device *net_device_alloc(void) { - // pointer to new net_device - struct net_device *dev; - - // デバイス構造体のサイズのメモリを確保 - // - memory_alloc() で確保したメモリ領域は 0 で初期化されている - // - メモリが確保できなかったらエラーとして NULL を返す - // - メモリの確保と解放には memory_alloc() と memory_free() を使う - dev = memory_alloc(sizeof(*dev)); - if (!dev) { - errorf("memory_alloc() failure"); - return NULL; - } + // pointer to new net_device + struct net_device *dev; + + // デバイス構造体のサイズのメモリを確保 + // - memory_alloc() で確保したメモリ領域は 0 で初期化されている + // - メモリが確保できなかったらエラーとして NULL を返す + // - メモリの確保と解放には memory_alloc() と memory_free() を使う + dev = memory_alloc(sizeof(*dev)); + if (!dev) { + errorf("memory_alloc() failure"); + return NULL; + } - return dev; + return dev; } /* NOTE: must not be call after net_run() */ // デバイスの登録 int net_device_register(struct net_device *dev) { - // (関数の初回実行時だけ動く)デバイスのインデックス番号の初期化 - // note: 関数内で static な変数を一度だけ初期化し、関数呼出し間で保持できる。 - // note: 関数に副作用を入れることになる。 - // note: マルチスレッド環境でのデータ競合に注意。 - static unsigned int index = 0; - - // デバイスのインデックス番号の設定 - // note: 後置インクリメントなので現在の値を代入してからインクリメントされる - dev->index = index++; // set, then increment - - // デバイス名を生成する(net0, net1, net2, ...) - snprintf(dev->name, sizeof(dev->name), "net%d", dev->index); - - // デバイスリストの先頭にデバイスを追加 - // ref: Day 1 - デバイスの生成と登録 - // https://docs.google.com/presentation/d/1ID6ggxASfc_1bWiJfDy1IFKIwzxvfYy8rUBrWYTRFj8/edit?slide=id.gd328c3072b_0_1179#slide=id.gd328c3072b_0_1179 - // 新規登録するデバイスの次のデバイスを、現在のデバイスリストの先頭のデバイスに設定する - dev->next = devices; - - // 現在のデバイスリストの先頭のデバイスを新規登録するデバイスに設定する - devices = dev; - infof("registered device: dev=%s, type=0x%04x", dev->name, dev->type); - - return 0; + // (関数の初回実行時だけ動く)デバイスのインデックス番号の初期化 + // note: 関数内で static + // な変数を一度だけ初期化し、関数呼出し間で保持できる。 note: + // 関数に副作用を入れることになる。 note: + // マルチスレッド環境でのデータ競合に注意。 + static unsigned int index = 0; + + // デバイスのインデックス番号の設定 + // note: 後置インクリメントなので現在の値を代入してからインクリメントされる + dev->index = index++; // set, then increment + + // デバイス名を生成する(net0, net1, net2, ...) + snprintf(dev->name, sizeof(dev->name), "net%d", dev->index); + + // デバイスリストの先頭にデバイスを追加 + // ref: Day 1 - デバイスの生成と登録 + // https://docs.google.com/presentation/d/1ID6ggxASfc_1bWiJfDy1IFKIwzxvfYy8rUBrWYTRFj8/edit?slide=id.gd328c3072b_0_1179#slide=id.gd328c3072b_0_1179 + // 新規登録するデバイスの次のデバイスを、現在のデバイスリストの先頭のデバイスに設定する + dev->next = devices; + + // 現在のデバイスリストの先頭のデバイスを新規登録するデバイスに設定する + devices = dev; + infof("registered device: dev=%s, type=0x%04x", dev->name, dev->type); + + return 0; } static int net_device_open(struct net_device *dev) { - // デバイスの状態を確認(既に UP 状態の場合はエラーを返す) - if (NET_DEVICE_IS_UP(dev)) { - errorf("device is already opened: dev=%s", dev->name); - return -1; - } + // デバイスの状態を確認(既に UP 状態の場合はエラーを返す) + if (NET_DEVICE_IS_UP(dev)) { + errorf("device is already opened: dev=%s", dev->name); + return -1; + } - // デバイスドライバのオープン関数を呼び出す - // - オープン関数が設定されていない場合は呼び出しをスキップ - // - エラーが返されたらこの関数もエラーを返す - if (dev->ops->open) { - if (dev->ops->open(dev) == -1) { - errorf("failed to open device: dev=%s", dev->name); - return -1; + // デバイスドライバのオープン関数を呼び出す + // - オープン関数が設定されていない場合は呼び出しをスキップ + // - エラーが返されたらこの関数もエラーを返す + if (dev->ops->open) { + if (dev->ops->open(dev) == -1) { + errorf("failed to open device: dev=%s", dev->name); + return -1; + } } - } - // デバイスのオープンに成功したら UP フラグを立てる - dev->flags |= NET_DEVICE_FLAG_UP; - infof("opened device status: dev=%s, state=%s", dev->name, - NET_DEVICE_STATE(dev)); + // デバイスのオープンに成功したら UP フラグを立てる + dev->flags |= NET_DEVICE_FLAG_UP; + infof("opened device status: dev=%s, state=%s", dev->name, + NET_DEVICE_STATE(dev)); - return 0; + return 0; } static int net_device_close(struct net_device *dev) { - // デバイスの状態を確認 - // UP 状態でないデバイスを close しようとしたときはエラーを返す - if (!NET_DEVICE_IS_UP(dev)) { - errorf("device is not opened: dev=%s", dev->name); - } + // デバイスの状態を確認 + // UP 状態でないデバイスを close しようとしたときはエラーを返す + if (!NET_DEVICE_IS_UP(dev)) { + errorf("device is not opened: dev=%s", dev->name); + } - // デバイスドライバのクローズ関数を呼び出す - // - クローズ関数が設定されていない場合は呼び出しをスキップ - // - エラーが返されたらこの関数もエラーを返す - if (dev->ops->close) { - if (dev->ops->close(dev) == -1) { - errorf("failed to close device: dev=%s", dev->name); - return -1; + // デバイスドライバのクローズ関数を呼び出す + // - クローズ関数が設定されていない場合は呼び出しをスキップ + // - エラーが返されたらこの関数もエラーを返す + if (dev->ops->close) { + if (dev->ops->close(dev) == -1) { + errorf("failed to close device: dev=%s", dev->name); + return -1; + } } - } - // UP フラグを落とす - dev->flags &= ~NET_DEVICE_FLAG_UP; - infof("closed device status: dev=%s, state=%s", dev->name, - NET_DEVICE_STATE(dev)); + // UP フラグを落とす + dev->flags &= ~NET_DEVICE_FLAG_UP; + infof("closed device status: dev=%s, state=%s", dev->name, + NET_DEVICE_STATE(dev)); - return 0; + return 0; } // デバイスへの出力 int net_device_output(struct net_device *dev, uint16_t type, const uint8_t *data, size_t len, const void *dst) { - // デバイスの状態を確認 - // UP 状態でないデバイスに出力する場合はエラーを返す - if (!NET_DEVICE_IS_UP(dev)) { - errorf("device is not opened on transmit: dev=%s", dev->name); - return -1; - } + // デバイスの状態を確認 + // UP 状態でないデバイスに出力する場合はエラーを返す + if (!NET_DEVICE_IS_UP(dev)) { + errorf("device is not opened on transmit: dev=%s", dev->name); + return -1; + } - // データのサイズを確認 - // デバイスの MTU を超えるサイズのデータは送信できないのでエラーを返す - if (len > dev->mtu) { - errorf("data is too long on transmit: dev=%s, mtu=%u, len=%zu", dev->name, - dev->mtu, len); - return -1; - } - /* note: フォーマット指定子 %04x について - - - `%04x`: 値を 16 進数で出力する。ゼロ 4 桁で padding。 - - 幅を 4 桁に指定し、ゼロで埋める (`%0` + `4` + `x`) - - 16 進数で足りない桁は先頭を 0 で埋めて 4 桁で出力する - */ - debugf("output to device: dev=%s, type=0x%04x, len=%zu", dev->name, type, - len); - debugdump(data, len); - - // デバイスドライバの出力関数を呼び出す - // エラーが返されたらこの関数もエラーを返す - if (dev->ops->transmit(dev, type, data, len, dst) == -1) { - errorf("device transmit failure on output: dev=%s, len=%zu", dev->name, + // データのサイズを確認 + // デバイスの MTU を超えるサイズのデータは送信できないのでエラーを返す + if (len > dev->mtu) { + errorf("data is too long on transmit: dev=%s, mtu=%u, len=%zu", + dev->name, dev->mtu, len); + return -1; + } + /* note: フォーマット指定子 %04x について + + - `%04x`: 値を 16 進数で出力する。ゼロ 4 桁で padding。 + - 幅を 4 桁に指定し、ゼロで埋める (`%0` + `4` + `x`) + - 16 進数で足りない桁は先頭を 0 で埋めて 4 桁で出力する + */ + debugf("output to device: dev=%s, type=0x%04x, len=%zu", dev->name, type, len); - return -1; - } + debugdump(data, len); + + // デバイスドライバの出力関数を呼び出す + // エラーが返されたらこの関数もエラーを返す + if (dev->ops->transmit(dev, type, data, len, dst) == -1) { + errorf("device transmit failure on output: dev=%s, len=%zu", dev->name, + len); + return -1; + } - return 0; + return 0; } /* デバイスからの入力: デバイスが受信したパケットをプロトコルスタックに渡す関数 @@ -167,12 +168,12 @@ int net_device_output(struct net_device *dev, uint16_t type, */ int net_input_handler(uint16_t type, const uint8_t *data, size_t len, struct net_device *dev) { - // TODO: 今の段階では呼び出されたことがわかればよいのでデバッグ出力のみ - debugf("received data from device: dev=%s, type=0x%04x, len=%zu", dev->name, - type, len); - debugdump(data, len); + // TODO: 今の段階では呼び出されたことがわかればよいのでデバッグ出力のみ + debugf("received data from device: dev=%s, type=0x%04x, len=%zu", dev->name, + type, len); + debugdump(data, len); - return 0; + return 0; } /* プロトコルスタックの起動 @@ -203,57 +204,57 @@ int main(void) { ``` */ int net_run(void) { - struct net_device *dev; + struct net_device *dev; - // 割り込み機構の起動 - debugf("start interrupt processing..."); - if (intr_run() == -1) { - errorf("intr_run() failure"); - return -1; - } + // 割り込み機構の起動 + debugf("start interrupt processing..."); + if (intr_run() == -1) { + errorf("intr_run() failure"); + return -1; + } - // 登録済みの全デバイスをオープン - debugf("open all devices..."); - for (dev = devices; dev; dev = dev->next) { - if (net_device_open(dev) == -1) { - errorf("failed to open device on net_run: dev=%s"); - // 失敗しても for 文を抜けはしない + // 登録済みの全デバイスをオープン + debugf("open all devices..."); + for (dev = devices; dev; dev = dev->next) { + if (net_device_open(dev) == -1) { + errorf("failed to open device on net_run: dev=%s"); + // 失敗しても for 文を抜けはしない + } } - } - debugf("running..."); + debugf("running..."); - return 0; + return 0; } // プロトコルスタックの停止 void net_shutdown(void) { - struct net_device *dev; - - // 登録済みの全デバイスをクローズ - debugf("close all devices..."); - for (dev = devices; dev; dev = dev->next) { - if (net_device_close(dev) == -1) { - errorf("failed to close device on shutting down: dev=%s"); - // 失敗しても for 文を抜けはしない + struct net_device *dev; + + // 登録済みの全デバイスをクローズ + debugf("close all devices..."); + for (dev = devices; dev; dev = dev->next) { + if (net_device_close(dev) == -1) { + errorf("failed to close device on shutting down: dev=%s"); + // 失敗しても for 文を抜けはしない + } } - } - // 割り込み機構の終了 - debugf("shutdown interrupt processing..."); - intr_shutdown(); + // 割り込み機構の終了 + debugf("shutdown interrupt processing..."); + intr_shutdown(); - debugf("shutting down"); + debugf("shutting down"); } int net_init(void) { - // 割り込み機構の初期化 - debugf("initialize interrupt processing..."); - if (intr_init() == -1) { - errorf("intr_init() failure"); - return -1; - } + // 割り込み機構の初期化 + debugf("initialize interrupt processing..."); + if (intr_init() == -1) { + errorf("intr_init() failure"); + return -1; + } - infof("initialized protocol stack"); + infof("initialized protocol stack"); - return 0; + return 0; } diff --git a/net.h b/net.h index a24f0faf..3a0009ce 100644 --- a/net.h +++ b/net.h @@ -34,47 +34,47 @@ // ref: // https://docs.google.com/presentation/d/1ID6ggxASfc_1bWiJfDy1IFKIwzxvfYy8rUBrWYTRFj8/edit?slide=id.gd328c3072b_0_1161#slide=id.gd328c3072b_0_1161 struct net_device { - // 次のデバイスへのポインタ - struct net_device *next; - unsigned int index; - char name[IFNAMSIZ]; - - // デバイスの種別(net.h に NET_DEVICE_TYPE_XXX として定義) - uint16_t type; - - // ----- デバイスの種別によって変化する値 --------------- - uint16_t mtu; // デバイスの MTU (Maximum Transmission Unit) の値 - uint16_t flags; - uint16_t hlen; /* header length */ - uint16_t alen; /* address length */ - // ------------------------------------------------ - - // デバイスのハードウェアアドレス - // - デバイスによってアドレスサイズが異なるので大きめのバッファを用意 - // - アドレスを持たないデバイスでは値は設定されない - uint8_t addr[NET_DEVICE_ADDR_LEN]; - union { - uint8_t peer[NET_DEVICE_ADDR_LEN]; - uint8_t broadcast[NET_DEVICE_ADDR_LEN]; - }; - - // デバイスドライバに実装されている関数を - // 格納している構造体 struct net_device_ops へのポインタ - struct net_device_ops *ops; - - // デバイスドライバが使用するプライベートなデータへのポインタ - void *priv; + // 次のデバイスへのポインタ + struct net_device *next; + unsigned int index; + char name[IFNAMSIZ]; + + // デバイスの種別(net.h に NET_DEVICE_TYPE_XXX として定義) + uint16_t type; + + // ----- デバイスの種別によって変化する値 --------------- + uint16_t mtu; // デバイスの MTU (Maximum Transmission Unit) の値 + uint16_t flags; + uint16_t hlen; /* header length */ + uint16_t alen; /* address length */ + // ------------------------------------------------ + + // デバイスのハードウェアアドレス + // - デバイスによってアドレスサイズが異なるので大きめのバッファを用意 + // - アドレスを持たないデバイスでは値は設定されない + uint8_t addr[NET_DEVICE_ADDR_LEN]; + union { + uint8_t peer[NET_DEVICE_ADDR_LEN]; + uint8_t broadcast[NET_DEVICE_ADDR_LEN]; + }; + + // デバイスドライバに実装されている関数を + // 格納している構造体 struct net_device_ops へのポインタ + struct net_device_ops *ops; + + // デバイスドライバが使用するプライベートなデータへのポインタ + void *priv; }; // デバイスドライバに実装されている関数へのポインタを格納 struct net_device_ops { - // optional - int (*open)(struct net_device *dev); - // optional - int (*close)(struct net_device *dev); - // required: 送信関数 transmit は必須 - int (*transmit)(struct net_device *dev, uint16_t type, const uint8_t *data, - size_t len, const void *dst); + // optional + int (*open)(struct net_device *dev); + // optional + int (*close)(struct net_device *dev); + // required: 送信関数 transmit は必須 + int (*transmit)(struct net_device *dev, uint16_t type, const uint8_t *data, + size_t len, const void *dst); }; extern struct net_device *net_device_alloc(void); diff --git a/platform/linux/intr.c b/platform/linux/intr.c index 78b8df18..fc62446b 100644 --- a/platform/linux/intr.c +++ b/platform/linux/intr.c @@ -10,23 +10,23 @@ // 割り込み要求(IRQ, Interrupt Request)の構造体 // ネットワークデバイス(net_device 構造体)と同様に linked list 構造で管理する struct irq_entry { - // 次の IRQ 構造体 - struct irq_entry *next; - // 割り込み番号(IRQ 番号) - unsigned int irq; - // 割り込みハンドラ(割り込みが発生した際に呼び出す関数へのポインタ) - int (*handler)(unsigned int irq, void *dev); - // フラグ(INTR_IRQ_SHARED が指定された場合は IRQ 番号を共有可能) - int flags; - // デバッグ出力で識別するための名前 - char name[16]; - /* 割り込みの発生元となるデバイス - - ---------- - - struct net_device 以外にも対応できるように void * で保持 - */ - void *dev; + // 次の IRQ 構造体 + struct irq_entry *next; + // 割り込み番号(IRQ 番号) + unsigned int irq; + // 割り込みハンドラ(割り込みが発生した際に呼び出す関数へのポインタ) + int (*handler)(unsigned int irq, void *dev); + // フラグ(INTR_IRQ_SHARED が指定された場合は IRQ 番号を共有可能) + int flags; + // デバッグ出力で識別するための名前 + char name[16]; + /* 割り込みの発生元となるデバイス + + ---------- + + struct net_device 以外にも対応できるように void * で保持 + */ + void *dev; }; /* NOTE: if you want to add/delete the entries after intr_run(), you need to @@ -72,257 +72,263 @@ static const int NUM_THREADS_FOR_BARRIER = 2; int intr_register_irq_entry(unsigned int irq, int (*handler)(unsigned int irq, void *dev), int flags, const char *name, void *dev) { - struct irq_entry *entry; - - debugf("registering new IRQ: irq=%u, flags=%d, name=%s", irq, flags, name); - - // -------------------------------------------------- - // IRQ 番号が既に登録されている場合、 - // IRQ 番号の共有が許可されているかどうかチェック - // どちらかが共有を許可していない場合はエラーを返す - // -------------------------------------------------- - - // IRQ リストを走査 - for (entry = irqs; entry; entry = entry->next) { - // IRQ リストに新規 IRQ エントリの IRQ 番号が既に登録されている場合、 - // その IRQ 番号に対して追加のハンドラを登録できるかどうか確認する。 - if (entry->irq == irq) { - /* - XOR(^) はビット単位の排他的論理和(2 つの値が異なるとき 1 になる) - 真理値表 (A ^ B): - A B | A^B - 0 0 | 0 - 0 1 | 1 - 1 0 | 1 - 1 1 | 0 - */ - /* - ・既存の IRQ のフラグが `INTR_IRQ_SHARED` と完全に一致しているか - - flags と INTR_IRQ_SHARED が一致している場合は XOR の結果は偽になる - - e.g. flags ^ INTR_IRQ_SHARED = 0001 ^ 0001 = 0000 -> 偽 - - flags と INTR_IRQ_SHARED が一致していない場合は XOR の結果は真 - - e.g. flags ^ INTR_IRQ_SHARED = 0000 ^ 0001 = 0001 -> 真 - ・新しく登録しようとしているフラグが`INTR_IRQ_SHARED`と完全に一致しているか - - - - をチェックしていて、どちらか一方でも値が違えば衝突とみなす。 - - 注意点として、XOR は「`INTR_IRQ_SHARED` - ビットが立っているかどうか」ではなく - 「値がまるごと同一かどうか」を判定している。 - フラグにほかのビットが含まれる場合は真になってしまうので、共有可否をビット単位で見る意図なら - `flags & INTR_IRQ_SHARED` のようにマスクした方が適切かもしれない。 - */ - - const int is_existing_irq_allows_share = - (entry->flags ^ INTR_IRQ_SHARED) == 0; - const int is_new_irq_allows_share = (flags ^ INTR_IRQ_SHARED) == 0; - const int is_irq_allows_share = - is_existing_irq_allows_share || is_new_irq_allows_share; - if (!is_irq_allows_share) { - errorf("conflicts with already registered IRQs"); + struct irq_entry *entry; + + debugf("registering new IRQ: irq=%u, flags=%d, name=%s", irq, flags, name); + + // -------------------------------------------------- + // IRQ 番号が既に登録されている場合、 + // IRQ 番号の共有が許可されているかどうかチェック + // どちらかが共有を許可していない場合はエラーを返す + // -------------------------------------------------- + + // IRQ リストを走査 + for (entry = irqs; entry; entry = entry->next) { + // IRQ リストに新規 IRQ エントリの IRQ 番号が既に登録されている場合、 + // その IRQ 番号に対して追加のハンドラを登録できるかどうか確認する。 + if (entry->irq == irq) { + /* + XOR(^) はビット単位の排他的論理和(2 つの値が異なるとき 1 になる) + 真理値表 (A ^ B): + A B | A^B + 0 0 | 0 + 0 1 | 1 + 1 0 | 1 + 1 1 | 0 + */ + /* + ・既存の IRQ のフラグが `INTR_IRQ_SHARED` と完全に一致しているか + - flags と INTR_IRQ_SHARED が一致している場合は XOR + の結果は偽になる + - e.g. flags ^ INTR_IRQ_SHARED = 0001 ^ 0001 = 0000 -> 偽 + - flags と INTR_IRQ_SHARED が一致していない場合は XOR の結果は真 + - e.g. flags ^ INTR_IRQ_SHARED = 0000 ^ 0001 = 0001 -> 真 + ・新しく登録しようとしているフラグが`INTR_IRQ_SHARED`と完全に一致しているか + - + + をチェックしていて、どちらか一方でも値が違えば衝突とみなす。 + + 注意点として、XOR は「`INTR_IRQ_SHARED` + ビットが立っているかどうか」ではなく + 「値がまるごと同一かどうか」を判定している。 + フラグにほかのビットが含まれる場合は真になってしまうので、共有可否をビット単位で見る意図なら + `flags & INTR_IRQ_SHARED` のようにマスクした方が適切かもしれない。 + */ + + const int is_existing_irq_allows_share = + (entry->flags ^ INTR_IRQ_SHARED) == 0; + const int is_new_irq_allows_share = (flags ^ INTR_IRQ_SHARED) == 0; + const int is_irq_allows_share = + is_existing_irq_allows_share || is_new_irq_allows_share; + if (!is_irq_allows_share) { + errorf("conflicts with already registered IRQs"); + return -1; + } + } + } + + // -------------------------------------------------- + // IRQ リストへ新しいエントリを追加 + // -------------------------------------------------- + + // 新しい IRQ エントリのメモリを確保 + entry = memory_alloc(sizeof(*entry)); + if (!entry) { + errorf("memory_alloc() on registering IRQ entry failure"); return -1; - } } - } - - // -------------------------------------------------- - // IRQ リストへ新しいエントリを追加 - // -------------------------------------------------- - - // 新しい IRQ エントリのメモリを確保 - entry = memory_alloc(sizeof(*entry)); - if (!entry) { - errorf("memory_alloc() on registering IRQ entry failure"); - return -1; - } - - // IRQ エントリ構造体に値を設定 - entry->irq = irq; - entry->handler = handler; - entry->flags = flags; - strncpy(entry->name, name, sizeof(entry->name) - 1); - entry->dev = dev; - - // 新しい IRQ エントリを IRQ リストの先頭へ挿入 - entry->next = irqs; - irqs = entry; - - // シグナル集合へ新しいシグナルを追加 - // ここって既存のシグナルの被らないの? - // -> 被らない。既存のシグナルとの OR 演算なので。 - sigaddset(&sigmask, irq); - debugf("registered new IRQ entry: irq=%u, name=%s", entry->irq, entry->name); - - return 0; + + // IRQ エントリ構造体に値を設定 + entry->irq = irq; + entry->handler = handler; + entry->flags = flags; + strncpy(entry->name, name, sizeof(entry->name) - 1); + entry->dev = dev; + + // 新しい IRQ エントリを IRQ リストの先頭へ挿入 + entry->next = irqs; + irqs = entry; + + // シグナル集合へ新しいシグナルを追加 + // ここって既存のシグナルの被らないの? + // -> 被らない。既存のシグナルとの OR 演算なので。 + sigaddset(&sigmask, irq); + debugf("registered new IRQ entry: irq=%u, name=%s", entry->irq, + entry->name); + + return 0; } int intr_raise_irq(unsigned int irq) { - // 割り込み処理用のスレッドへシグナルを送信 - // microps では、IRQ 番号にシグナル番号を使用する - // 後続の処理では、 - // 発生したシグナルのシグナル番号と IRQ エントリの IRQ 番号が一致したときに - // IRQ エントリに登録されているハンドラが実行される - return pthread_kill(tid, (int)irq); + // 割り込み処理用のスレッドへシグナルを送信 + // microps では、IRQ 番号にシグナル番号を使用する + // 後続の処理では、 + // 発生したシグナルのシグナル番号と IRQ エントリの IRQ 番号が一致したときに + // IRQ エントリに登録されているハンドラが実行される + return pthread_kill(tid, (int)irq); } // 割り込み処理を実行するスレッドのエントリーポイント static void *intr_thread(void *arg) { - int terminate = 0; - int sig; - int err; + int terminate = 0; + int sig; + int err; - struct irq_entry *entry; + struct irq_entry *entry; - infof("start thread for interrupt"); + infof("start thread for interrupt"); - // メインスレッドと同期を取るための処理 - pthread_barrier_wait(&barrier); - debugf("barrier unlocked on intr_thread!"); + // メインスレッドと同期を取るための処理 + pthread_barrier_wait(&barrier); + debugf("barrier unlocked on intr_thread!"); - // ハードウェア割り込みに見立てたシグナルを処理するループ - while (!terminate) { - // ハードウェア割り込みに見立てたシグナルが発生するまで待機 - err = sigwait(&sigmask, &sig); - if (err) { - errorf("sigwait() failure: %s", strerror(err)); - break; - } + // ハードウェア割り込みに見立てたシグナルを処理するループ + while (!terminate) { + // ハードウェア割り込みに見立てたシグナルが発生するまで待機 + err = sigwait(&sigmask, &sig); + if (err) { + errorf("sigwait() failure: %s", strerror(err)); + break; + } - // 発生したシグナルの種類に応じて処理 - switch (sig) { - // SIGHUP シグナルの処理: - // 割り込み処理用のスレッドへ終了を通知するためのシグナル - case SIGHUP: - terminate = 1; - break; - // デバイス割り込み用のシグナルを処理 - default: - // IRQ リストを巡回 - for (entry = irqs; entry; entry = entry->next) { - // IRQ 番号が一致するエントリの割り込みハンドラを呼び出す - // microps では、IRQ 番号にシグナル番号を使用する - if (entry->irq == (unsigned int)sig) { - debugf("call ISR: irq=%d, name=%s", entry->irq, entry->name); - entry->handler(entry->irq, entry->dev); + // 発生したシグナルの種類に応じて処理 + switch (sig) { + // SIGHUP シグナルの処理: + // 割り込み処理用のスレッドへ終了を通知するためのシグナル + case SIGHUP: + terminate = 1; + break; + // デバイス割り込み用のシグナルを処理 + default: + // IRQ リストを巡回 + for (entry = irqs; entry; entry = entry->next) { + // IRQ 番号が一致するエントリの割り込みハンドラを呼び出す + // microps では、IRQ 番号にシグナル番号を使用する + if (entry->irq == (unsigned int)sig) { + debugf("call ISR: irq=%d, name=%s", entry->irq, + entry->name); + entry->handler(entry->irq, entry->dev); + } + } + break; } - } - break; } - } - warnf("terminated thread for interrupt"); + warnf("terminated thread for interrupt"); - return NULL; + return NULL; } // 割り込み機構を起動する // intr_init が呼ばれている状態でこの関数を呼ぶこと int intr_run(void) { - int err; - - // シグナルマスクの設定 - // intr_init 内で sigmask の設定ができていることが前提 - err = pthread_sigmask(SIG_BLOCK, &sigmask, NULL); - if (err) { - errorf("pthread_sigmask"); - return -1; - } - - // 割り込み処理スレッドの起動 - err = pthread_create(&tid, NULL, intr_thread, NULL); - if (err) { - errorf("pthread_create() failed on intr_run: %s", strerror(err)); - return -1; - } - - // スレッドが動き出すまで待つ - // 他のスレッドが同じように pthread_barrier_wait() を呼び出し、 - // バリアのカウントが指定の数になるまでスレッドを停止する - infof("waiting for starting thread for interrupt with barrier..."); - pthread_barrier_wait(&barrier); - infof("thread for interrupt started!"); - - return 0; + int err; + + // シグナルマスクの設定 + // intr_init 内で sigmask の設定ができていることが前提 + err = pthread_sigmask(SIG_BLOCK, &sigmask, NULL); + if (err) { + errorf("pthread_sigmask"); + return -1; + } + + // 割り込み処理スレッドの起動 + err = pthread_create(&tid, NULL, intr_thread, NULL); + if (err) { + errorf("pthread_create() failed on intr_run: %s", strerror(err)); + return -1; + } + + // スレッドが動き出すまで待つ + // 他のスレッドが同じように pthread_barrier_wait() を呼び出し、 + // バリアのカウントが指定の数になるまでスレッドを停止する + infof("waiting for starting thread for interrupt with barrier..."); + pthread_barrier_wait(&barrier); + infof("thread for interrupt started!"); + + return 0; } void intr_shutdown(void) { - int err; - - // 割り込み処理スレッドが起動済みかどうか確認 - /* Q. なぜ以下のコードで割り込み処理スレッドが起動済みかどうか確認できるのか? - * 前提:関数の実行順序は intr_init -> intr_run -> intr_shutdown - * - * `pthread_equal(tid, pthread_self())`は - *「今の呼び出し元スレッド(メインスレッド)の - * ID」と、グローバルに保持している `tid` を比較している。 - * - * ここの比較は `intr_init` の段階で `tid` に「メインスレッド(=呼び出し元) - * の `pthread_t`」を代入しておく前提になっている。 - * `intr_run` で割り込み処理スレッドを起動するときに - * `pthread_create(&tid, …)` として `tid` を新しいスレッドの ID で上書きする。 - * - * つまり、`intr_shutdown` をメインスレッドから呼ぶときに、 - * `tid` がまだメインスレッドと同じ ID のままなら - * 「新しいスレッドに置き換わっていない → スレッドを作れていない」= - *`pthread_equal` が真になる、という判定になる。 - * - * 逆に、スレッドが正常に起動済みなら `tid` は別の ID に差し替わっているので、 - * `pthread_equal` は偽になり、 `pthread_kill` や `pthread_join` - * に進む、という流れとなっている。 - - * 注意点として、このロジックは - * - * - `intr_init` をメインスレッドで呼ぶこと - * - 初期値として `tid = pthread_self()` をセットしていること - * - `intr_shutdown` をメインスレッドで呼ぶこと - * - * の 3 つが前提なので、別スレッドから呼び出したり、 - * 初期化を忘れたりすると意図通りにならない点には気をつける必要がある。 - */ - if (pthread_equal(tid, pthread_self()) != 0) { - warnf("thread not created"); - return; - } + int err; + + // 割り込み処理スレッドが起動済みかどうか確認 + /* Q. + なぜ以下のコードで割り込み処理スレッドが起動済みかどうか確認できるのか? + * 前提:関数の実行順序は intr_init -> intr_run -> intr_shutdown + * + * `pthread_equal(tid, pthread_self())`は + *「今の呼び出し元スレッド(メインスレッド)の + * ID」と、グローバルに保持している `tid` を比較している。 + * + * ここの比較は `intr_init` の段階で `tid` に「メインスレッド(=呼び出し元) + * の `pthread_t`」を代入しておく前提になっている。 + * `intr_run` で割り込み処理スレッドを起動するときに + * `pthread_create(&tid, …)` として `tid` を新しいスレッドの ID + で上書きする。 + * + * つまり、`intr_shutdown` をメインスレッドから呼ぶときに、 + * `tid` がまだメインスレッドと同じ ID のままなら + * 「新しいスレッドに置き換わっていない → スレッドを作れていない」= + *`pthread_equal` が真になる、という判定になる。 + * + * 逆に、スレッドが正常に起動済みなら `tid` は別の ID + に差し替わっているので、 + * `pthread_equal` は偽になり、 `pthread_kill` や `pthread_join` + * に進む、という流れとなっている。 + + * 注意点として、このロジックは + * + * - `intr_init` をメインスレッドで呼ぶこと + * - 初期値として `tid = pthread_self()` をセットしていること + * - `intr_shutdown` をメインスレッドで呼ぶこと + * + * の 3 つが前提なので、別スレッドから呼び出したり、 + * 初期化を忘れたりすると意図通りにならない点には気をつける必要がある。 + */ + if (pthread_equal(tid, pthread_self()) != 0) { + warnf("thread not created"); + return; + } - // 割り込み処理スレッドにシグナル(SIGHUP)を送信 - err = pthread_kill(tid, SIGHUP); - if (err) { - warnf("failed to kill process with SIGHUP: tid=%d, err=%s", tid, - strerror(err)); - return; - } + // 割り込み処理スレッドにシグナル(SIGHUP)を送信 + err = pthread_kill(tid, SIGHUP); + if (err) { + warnf("failed to kill process with SIGHUP: tid=%d, err=%s", tid, + strerror(err)); + return; + } - // 割り込み処理スレッドが完全に終了するのを待つ - err = pthread_join(tid, NULL); - if (err) { - warnf("failed to pthread_join: tid=%d, err=%s", tid, strerror(err)); - return; - } + // 割り込み処理スレッドが完全に終了するのを待つ + err = pthread_join(tid, NULL); + if (err) { + warnf("failed to pthread_join: tid=%d, err=%s", tid, strerror(err)); + return; + } - infof("shutdown interrupt successfully!"); + infof("shutdown interrupt successfully!"); - return; + return; } int intr_init(void) { - infof("init interrupt"); + infof("init interrupt"); - // スレッド ID の初期値にメインスレッドの ID を設定する - tid = pthread_self(); + // スレッド ID の初期値にメインスレッドの ID を設定する + tid = pthread_self(); - // pthread_barrier の初期化(カウントを 2 に設定) - infof("init pthread barrier for %d threads", NUM_THREADS_FOR_BARRIER); - pthread_barrier_init(&barrier, NULL, NUM_THREADS_FOR_BARRIER); - debugf("barrier unlocked on intr_init!"); + // pthread_barrier の初期化(カウントを 2 に設定) + infof("init pthread barrier for %d threads", NUM_THREADS_FOR_BARRIER); + pthread_barrier_init(&barrier, NULL, NUM_THREADS_FOR_BARRIER); + debugf("barrier unlocked on intr_init!"); - // グローバルな割り込み処理用のシグナル集合(シグナルマスク)を初期化(空にする) - infof("init sigmask"); - sigemptyset(&sigmask); + // グローバルな割り込み処理用のシグナル集合(シグナルマスク)を初期化(空にする) + infof("init sigmask"); + sigemptyset(&sigmask); - // シグナル集合に SIGHUP を追加(割り込みスレッド終了通知用) - sigaddset(&sigmask, SIGHUP); - // 注意:この段階ではまだプロセス(メインスレッド)にシグナルマスクは設定されていない - // pthread_sigmask が実行されて初めてシグナルマスクがプロセスに適用される + // シグナル集合に SIGHUP を追加(割り込みスレッド終了通知用) + sigaddset(&sigmask, SIGHUP); + // 注意:この段階ではまだプロセス(メインスレッド)にシグナルマスクは設定されていない + // pthread_sigmask が実行されて初めてシグナルマスクがプロセスに適用される - return 0; + return 0; } diff --git a/platform/linux/platform.h b/platform/linux/platform.h index 3b70715d..d2b7138c 100644 --- a/platform/linux/platform.h +++ b/platform/linux/platform.h @@ -23,15 +23,15 @@ typedef pthread_mutex_t mutex_t; #define MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER static inline int mutex_init(mutex_t *mutex) { - return pthread_mutex_init(mutex, NULL); + return pthread_mutex_init(mutex, NULL); } static inline int mutex_lock(mutex_t *mutex) { - return pthread_mutex_lock(mutex); + return pthread_mutex_lock(mutex); } static inline int mutex_unlock(mutex_t *mutex) { - return pthread_mutex_unlock(mutex); + return pthread_mutex_unlock(mutex); } /* diff --git a/test/step0.c b/test/step0.c index 59f9f548..b70b315d 100644 --- a/test/step0.c +++ b/test/step0.c @@ -1,10 +1,10 @@ -#include "test.h" +#include "test.h" // IWYU pragma: keep #include "util.h" int main(void) { - debugf("Hello, World!"); + debugf("Hello, World!"); - debugdump(test_data, sizeof(test_data)); + debugdump(test_data, sizeof(test_data)); - return 0; + return 0; } diff --git a/test/step1.c b/test/step1.c index d30bb589..2cb888d5 100644 --- a/test/step1.c +++ b/test/step1.c @@ -20,72 +20,72 @@ static volatile sig_atomic_t terminate; - volatile sig_atomic_t 型の変数への書き込み */ static void on_signal(int signal) { - /* - 一般に、I/O 関数をシグナルハンドラ内で呼び出すのは安全ではない。 - シグナルハンドラ内で I/O 関数を使用する場合は、 - 使用するシステムの非同期安全な関数を事前に確認すること。 - */ - // warnf("on_signal: signal received %d", signal); - - /* `(void)signal;` は必要? - `(void)signal;` は「この引数は使っていない」ことを意図的に示す - ためのお決まりの書き方。多くのコンパイラは未使用引数に警告を出すので、 - それを抑止する目的で入れている。 - - - 既にハンドラ内で `signal` を使って何かするなら不要。 - - 使わないならそのまま残しておけば警告を避けられる。 - - 別の書き方として、 - - `__attribute__((unused))` - - `void on_signal(int \/\*signal\*\/)` - - のようにコメントで示す例もある。 - - 単に `(void)signal;` がもっとも広く使われる方法です。 - */ - - // (void)signal; - terminate = 1; + /* + 一般に、I/O 関数をシグナルハンドラ内で呼び出すのは安全ではない。 + シグナルハンドラ内で I/O 関数を使用する場合は、 + 使用するシステムの非同期安全な関数を事前に確認すること。 + */ + // warnf("on_signal: signal received %d", signal); + + /* `(void)signal;` は必要? + `(void)signal;` は「この引数は使っていない」ことを意図的に示す + ためのお決まりの書き方。多くのコンパイラは未使用引数に警告を出すので、 + それを抑止する目的で入れている。 + + - 既にハンドラ内で `signal` を使って何かするなら不要。 + - 使わないならそのまま残しておけば警告を避けられる。 + - 別の書き方として、 + - `__attribute__((unused))` + - `void on_signal(int \/\*signal\*\/)` + - のようにコメントで示す例もある。 + - 単に `(void)signal;` がもっとも広く使われる方法です。 + */ + + // (void)signal; + terminate = 1; } int main(int argc, char *argv[]) { - struct net_device *dev; - - // シグナルハンドラの設定 - // Ctrl + C が押された際に行儀よく終了するようにする - signal(SIGINT, on_signal); - - // プロトコルスタックの初期化 - if (net_init() == -1) { - errorf("net_init() failure"); - return -1; - } - - // ダミーデバイスの初期化(ダミーデバイスの生成、設定、登録) - dev = dummy_init(); - - // プロトコルスタックの起動 - if (net_run() == -1) { - errorf("net_run() failure"); - return -1; - } - - // 1 秒おきにデバイスにパケットを書き込む - // まだパケットを自力で生成できないのでテストデータを用いる - uint16_t type = 0x0800; - size_t len = sizeof(test_data); - // Ctrl + C が押されるとシグナルハンドラ on_signal() の中で - // terminate に 1 が設定され、while ループを抜ける - while (!terminate) { - // Internet Layer -> Network Interface Layer への書き込み - // Internet Layer から来たパケットをデバイスに書き込む - if (net_device_output(dev, type, test_data, len, NULL) == -1) { - errorf("net_device_output() failure"); - break; + struct net_device *dev; + + // シグナルハンドラの設定 + // Ctrl + C が押された際に行儀よく終了するようにする + signal(SIGINT, on_signal); + + // プロトコルスタックの初期化 + if (net_init() == -1) { + errorf("net_init() failure"); + return -1; + } + + // ダミーデバイスの初期化(ダミーデバイスの生成、設定、登録) + dev = dummy_init(); + + // プロトコルスタックの起動 + if (net_run() == -1) { + errorf("net_run() failure"); + return -1; } - sleep(1); - } + // 1 秒おきにデバイスにパケットを書き込む + // まだパケットを自力で生成できないのでテストデータを用いる + uint16_t type = 0x0800; + size_t len = sizeof(test_data); + // Ctrl + C が押されるとシグナルハンドラ on_signal() の中で + // terminate に 1 が設定され、while ループを抜ける + while (!terminate) { + // Internet Layer -> Network Interface Layer への書き込み + // Internet Layer から来たパケットをデバイスに書き込む + if (net_device_output(dev, type, test_data, len, NULL) == -1) { + errorf("net_device_output() failure"); + break; + } + + sleep(1); + } - // プロトコルスタックの停止 - net_shutdown(); + // プロトコルスタックの停止 + net_shutdown(); - return 0; + return 0; } diff --git a/test/step2.c b/test/step2.c index d30bb589..2cb888d5 100644 --- a/test/step2.c +++ b/test/step2.c @@ -20,72 +20,72 @@ static volatile sig_atomic_t terminate; - volatile sig_atomic_t 型の変数への書き込み */ static void on_signal(int signal) { - /* - 一般に、I/O 関数をシグナルハンドラ内で呼び出すのは安全ではない。 - シグナルハンドラ内で I/O 関数を使用する場合は、 - 使用するシステムの非同期安全な関数を事前に確認すること。 - */ - // warnf("on_signal: signal received %d", signal); - - /* `(void)signal;` は必要? - `(void)signal;` は「この引数は使っていない」ことを意図的に示す - ためのお決まりの書き方。多くのコンパイラは未使用引数に警告を出すので、 - それを抑止する目的で入れている。 - - - 既にハンドラ内で `signal` を使って何かするなら不要。 - - 使わないならそのまま残しておけば警告を避けられる。 - - 別の書き方として、 - - `__attribute__((unused))` - - `void on_signal(int \/\*signal\*\/)` - - のようにコメントで示す例もある。 - - 単に `(void)signal;` がもっとも広く使われる方法です。 - */ - - // (void)signal; - terminate = 1; + /* + 一般に、I/O 関数をシグナルハンドラ内で呼び出すのは安全ではない。 + シグナルハンドラ内で I/O 関数を使用する場合は、 + 使用するシステムの非同期安全な関数を事前に確認すること。 + */ + // warnf("on_signal: signal received %d", signal); + + /* `(void)signal;` は必要? + `(void)signal;` は「この引数は使っていない」ことを意図的に示す + ためのお決まりの書き方。多くのコンパイラは未使用引数に警告を出すので、 + それを抑止する目的で入れている。 + + - 既にハンドラ内で `signal` を使って何かするなら不要。 + - 使わないならそのまま残しておけば警告を避けられる。 + - 別の書き方として、 + - `__attribute__((unused))` + - `void on_signal(int \/\*signal\*\/)` + - のようにコメントで示す例もある。 + - 単に `(void)signal;` がもっとも広く使われる方法です。 + */ + + // (void)signal; + terminate = 1; } int main(int argc, char *argv[]) { - struct net_device *dev; - - // シグナルハンドラの設定 - // Ctrl + C が押された際に行儀よく終了するようにする - signal(SIGINT, on_signal); - - // プロトコルスタックの初期化 - if (net_init() == -1) { - errorf("net_init() failure"); - return -1; - } - - // ダミーデバイスの初期化(ダミーデバイスの生成、設定、登録) - dev = dummy_init(); - - // プロトコルスタックの起動 - if (net_run() == -1) { - errorf("net_run() failure"); - return -1; - } - - // 1 秒おきにデバイスにパケットを書き込む - // まだパケットを自力で生成できないのでテストデータを用いる - uint16_t type = 0x0800; - size_t len = sizeof(test_data); - // Ctrl + C が押されるとシグナルハンドラ on_signal() の中で - // terminate に 1 が設定され、while ループを抜ける - while (!terminate) { - // Internet Layer -> Network Interface Layer への書き込み - // Internet Layer から来たパケットをデバイスに書き込む - if (net_device_output(dev, type, test_data, len, NULL) == -1) { - errorf("net_device_output() failure"); - break; + struct net_device *dev; + + // シグナルハンドラの設定 + // Ctrl + C が押された際に行儀よく終了するようにする + signal(SIGINT, on_signal); + + // プロトコルスタックの初期化 + if (net_init() == -1) { + errorf("net_init() failure"); + return -1; + } + + // ダミーデバイスの初期化(ダミーデバイスの生成、設定、登録) + dev = dummy_init(); + + // プロトコルスタックの起動 + if (net_run() == -1) { + errorf("net_run() failure"); + return -1; } - sleep(1); - } + // 1 秒おきにデバイスにパケットを書き込む + // まだパケットを自力で生成できないのでテストデータを用いる + uint16_t type = 0x0800; + size_t len = sizeof(test_data); + // Ctrl + C が押されるとシグナルハンドラ on_signal() の中で + // terminate に 1 が設定され、while ループを抜ける + while (!terminate) { + // Internet Layer -> Network Interface Layer への書き込み + // Internet Layer から来たパケットをデバイスに書き込む + if (net_device_output(dev, type, test_data, len, NULL) == -1) { + errorf("net_device_output() failure"); + break; + } + + sleep(1); + } - // プロトコルスタックの停止 - net_shutdown(); + // プロトコルスタックの停止 + net_shutdown(); - return 0; + return 0; } diff --git a/util.c b/util.c index aba7d5ba..994d08b4 100644 --- a/util.c +++ b/util.c @@ -19,59 +19,62 @@ int lprintf(FILE *fp, int level, const char *file, int line, const char *func, const char *fmt, ...) { - struct timeval tv; - struct tm tm; - char timestamp[32]; - int n = 0; - va_list ap; - - flockfile(fp); - gettimeofday(&tv, NULL); - strftime(timestamp, sizeof(timestamp), "%T", localtime_r(&tv.tv_sec, &tm)); - n += fprintf(fp, "%s.%03d [%c] %s: ", timestamp, (int)(tv.tv_usec / 1000), - level, func); - va_start(ap, fmt); - n += vfprintf(fp, fmt, ap); - va_end(ap); - n += fprintf(fp, " (%s:%d)\n", file, line); - funlockfile(fp); - return n; + struct timeval tv; + struct tm tm; + char timestamp[32]; + int n = 0; + va_list ap; + + flockfile(fp); + gettimeofday(&tv, NULL); + strftime(timestamp, sizeof(timestamp), "%T", localtime_r(&tv.tv_sec, &tm)); + n += fprintf(fp, "%s.%03d [%c] %s: ", timestamp, (int)(tv.tv_usec / 1000), + level, func); + va_start(ap, fmt); + n += vfprintf(fp, fmt, ap); + va_end(ap); + n += fprintf(fp, " (%s:%d)\n", file, line); + funlockfile(fp); + return n; } void hexdump(FILE *fp, const void *data, size_t size) { - unsigned char *src; - int offset, index; - - flockfile(fp); - src = (unsigned char *)data; - fprintf(fp, "+------+-------------------------------------------------+------" - "------------+\n"); - for (offset = 0; offset < (int)size; offset += 16) { - fprintf(fp, "| %04x | ", offset); - for (index = 0; index < 16; index++) { - if (offset + index < (int)size) { - fprintf(fp, "%02x ", 0xff & src[offset + index]); - } else { - fprintf(fp, " "); - } - } - fprintf(fp, "| "); - for (index = 0; index < 16; index++) { - if (offset + index < (int)size) { - if (isascii(src[offset + index]) && isprint(src[offset + index])) { - fprintf(fp, "%c", src[offset + index]); - } else { - fprintf(fp, "."); + unsigned char *src; + int offset, index; + + flockfile(fp); + src = (unsigned char *)data; + fprintf(fp, + "+------+-------------------------------------------------+------" + "------------+\n"); + for (offset = 0; offset < (int)size; offset += 16) { + fprintf(fp, "| %04x | ", offset); + for (index = 0; index < 16; index++) { + if (offset + index < (int)size) { + fprintf(fp, "%02x ", 0xff & src[offset + index]); + } else { + fprintf(fp, " "); + } } - } else { - fprintf(fp, " "); - } - } - fprintf(fp, " |\n"); - } - fprintf(fp, "+------+-------------------------------------------------+------" - "------------+\n"); - funlockfile(fp); + fprintf(fp, "| "); + for (index = 0; index < 16; index++) { + if (offset + index < (int)size) { + if (isascii(src[offset + index]) && + isprint(src[offset + index])) { + fprintf(fp, "%c", src[offset + index]); + } else { + fprintf(fp, "."); + } + } else { + fprintf(fp, " "); + } + } + fprintf(fp, " |\n"); + } + fprintf(fp, + "+------+-------------------------------------------------+------" + "------------+\n"); + funlockfile(fp); } /* @@ -79,74 +82,74 @@ void hexdump(FILE *fp, const void *data, size_t size) { */ struct queue_entry { - struct queue_entry *next; - void *data; + struct queue_entry *next; + void *data; }; void queue_init(struct queue_head *queue) { - queue->head = NULL; - queue->tail = NULL; - queue->num = 0; + queue->head = NULL; + queue->tail = NULL; + queue->num = 0; } void *queue_push(struct queue_head *queue, void *data) { - struct queue_entry *entry; - - if (!queue) { - return NULL; - } - entry = memory_alloc(sizeof(*entry)); - if (!entry) { - return NULL; - } - entry->next = NULL; - entry->data = data; - if (queue->tail) { - queue->tail->next = entry; - } - queue->tail = entry; - if (!queue->head) { - queue->head = entry; - } - queue->num++; - return data; + struct queue_entry *entry; + + if (!queue) { + return NULL; + } + entry = memory_alloc(sizeof(*entry)); + if (!entry) { + return NULL; + } + entry->next = NULL; + entry->data = data; + if (queue->tail) { + queue->tail->next = entry; + } + queue->tail = entry; + if (!queue->head) { + queue->head = entry; + } + queue->num++; + return data; } void *queue_pop(struct queue_head *queue) { - struct queue_entry *entry; - void *data; - - if (!queue || !queue->head) { - return NULL; - } - entry = queue->head; - queue->head = entry->next; - if (!queue->head) { - queue->tail = NULL; - } - queue->num--; - data = entry->data; - memory_free(entry); - return data; + struct queue_entry *entry; + void *data; + + if (!queue || !queue->head) { + return NULL; + } + entry = queue->head; + queue->head = entry->next; + if (!queue->head) { + queue->tail = NULL; + } + queue->num--; + data = entry->data; + memory_free(entry); + return data; } void *queue_peek(struct queue_head *queue) { - if (!queue || !queue->head) { - return NULL; - } - return queue->head->data; + if (!queue || !queue->head) { + return NULL; + } + return queue->head->data; } void queue_foreach(struct queue_head *queue, void (*func)(void *arg, void *data), void *arg) { - struct queue_entry *entry; - - if (!queue || !func) { - return; - } - for (entry = queue->head; entry; entry = entry->next) { - func(arg, entry->data); - } + struct queue_entry *entry; + + if (!queue || !func) { + return; + } + for (entry = queue->head; entry; entry = entry->next) { + func(arg, entry->data); + } } /* @@ -163,46 +166,46 @@ void queue_foreach(struct queue_head *queue, static int endian; static int byteorder(void) { - uint32_t x = 0x00000001; + uint32_t x = 0x00000001; - return *(uint8_t *)&x ? __LITTLE_ENDIAN : __BIG_ENDIAN; + return *(uint8_t *)&x ? __LITTLE_ENDIAN : __BIG_ENDIAN; } static uint16_t byteswap16(uint16_t v) { - return (v & 0x00ff) << 8 | (v & 0xff00) >> 8; + return (v & 0x00ff) << 8 | (v & 0xff00) >> 8; } static uint32_t byteswap32(uint32_t v) { - return (v & 0x000000ff) << 24 | (v & 0x0000ff00) << 8 | - (v & 0x00ff0000) >> 8 | (v & 0xff000000) >> 24; + return (v & 0x000000ff) << 24 | (v & 0x0000ff00) << 8 | + (v & 0x00ff0000) >> 8 | (v & 0xff000000) >> 24; } uint16_t hton16(uint16_t h) { - if (!endian) { - endian = byteorder(); - } - return endian == __LITTLE_ENDIAN ? byteswap16(h) : h; + if (!endian) { + endian = byteorder(); + } + return endian == __LITTLE_ENDIAN ? byteswap16(h) : h; } uint16_t ntoh16(uint16_t n) { - if (!endian) { - endian = byteorder(); - } - return endian == __LITTLE_ENDIAN ? byteswap16(n) : n; + if (!endian) { + endian = byteorder(); + } + return endian == __LITTLE_ENDIAN ? byteswap16(n) : n; } uint32_t hton32(uint32_t h) { - if (!endian) { - endian = byteorder(); - } - return endian == __LITTLE_ENDIAN ? byteswap32(h) : h; + if (!endian) { + endian = byteorder(); + } + return endian == __LITTLE_ENDIAN ? byteswap32(h) : h; } uint32_t ntoh32(uint32_t n) { - if (!endian) { - endian = byteorder(); - } - return endian == __LITTLE_ENDIAN ? byteswap32(n) : n; + if (!endian) { + endian = byteorder(); + } + return endian == __LITTLE_ENDIAN ? byteswap32(n) : n; } /* @@ -210,18 +213,18 @@ uint32_t ntoh32(uint32_t n) { */ uint16_t cksum16(uint16_t *addr, uint16_t count, uint32_t init) { - uint32_t sum; - - sum = init; - while (count > 1) { - sum += *(addr++); - count -= 2; - } - if (count > 0) { - sum += *(uint8_t *)addr; - } - while (sum >> 16) { - sum = (sum & 0xffff) + (sum >> 16); - } - return ~(uint16_t)sum; + uint32_t sum; + + sum = init; + while (count > 1) { + sum += *(addr++); + count -= 2; + } + if (count > 0) { + sum += *(uint8_t *)addr; + } + while (sum >> 16) { + sum = (sum & 0xffff) + (sum >> 16); + } + return ~(uint16_t)sum; } diff --git a/util.h b/util.h index c48e1a33..7220b644 100644 --- a/util.h +++ b/util.h @@ -29,37 +29,37 @@ */ #define timeval_add_usec(x, y) \ - do { \ - (x)->tv_sec += y / 1000000; \ - (x)->tv_usec += y % 1000000; \ - if ((x)->tv_usec >= 1000000) { \ - (x)->tv_sec += 1; \ - (x)->tv_usec -= 1000000; \ - } \ - } while (0); + do { \ + (x)->tv_sec += y / 1000000; \ + (x)->tv_usec += y % 1000000; \ + if ((x)->tv_usec >= 1000000) { \ + (x)->tv_sec += 1; \ + (x)->tv_usec -= 1000000; \ + } \ + } while (0); #define timespec_add_nsec(x, y) \ - do { \ - (x)->tv_sec += y / 1000000000; \ - (x)->tv_nsec += y % 1000000000; \ - if ((x)->tv_nsec >= 1000000000) { \ - (x)->tv_sec += 1; \ - (x)->tv_nsec -= 1000000000; \ - } \ - } while (0); + do { \ + (x)->tv_sec += y / 1000000000; \ + (x)->tv_nsec += y % 1000000000; \ + if ((x)->tv_nsec >= 1000000000) { \ + (x)->tv_sec += 1; \ + (x)->tv_nsec -= 1000000000; \ + } \ + } while (0); /* * Logging */ #define errorf(...) \ - lprintf(stderr, 'E', __FILE__, __LINE__, __func__, __VA_ARGS__) + lprintf(stderr, 'E', __FILE__, __LINE__, __func__, __VA_ARGS__) #define warnf(...) \ - lprintf(stderr, 'W', __FILE__, __LINE__, __func__, __VA_ARGS__) + lprintf(stderr, 'W', __FILE__, __LINE__, __func__, __VA_ARGS__) #define infof(...) \ - lprintf(stderr, 'I', __FILE__, __LINE__, __func__, __VA_ARGS__) + lprintf(stderr, 'I', __FILE__, __LINE__, __func__, __VA_ARGS__) #define debugf(...) \ - lprintf(stderr, 'D', __FILE__, __LINE__, __func__, __VA_ARGS__) + lprintf(stderr, 'D', __FILE__, __LINE__, __func__, __VA_ARGS__) #ifdef HEXDUMP #define debugdump(...) hexdump(stderr, __VA_ARGS__) @@ -78,9 +78,9 @@ extern void hexdump(FILE *fp, const void *data, size_t size); struct queue_entry; struct queue_head { - struct queue_entry *head; - struct queue_entry *tail; - unsigned int num; + struct queue_entry *head; + struct queue_entry *tail; + unsigned int num; }; extern void queue_init(struct queue_head *queue); From af32db96fce520785e18aeb5d421ea7270f4f243 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 12 Apr 2026 22:55:17 +0900 Subject: [PATCH 40/52] chore: define var `CC` in Makefile --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index af06d668..6e41d1c2 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ TESTS = test/step0.exe \ test/step1.exe \ test/step2.exe \ +CC := gcc CFLAGS := $(CFLAGS) -g -W -Wall -Wno-unused-parameter -iquote . ifeq ($(shell uname),Linux) From f978c4205850c3bde7c26b9dec1d92713893044b Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 12 Apr 2026 22:55:40 +0900 Subject: [PATCH 41/52] chore: add gitignore rules for Vagrant --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index c6127b38..2868f72a 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,9 @@ modules.order Module.symvers Mkfile.old dkms.conf + +# Vagrant +.vagrant/ + +# Log files (if you are creating logs in debug mode, uncomment this) +# *.log From 91d65bce145a50c229e000102ae955981c58511f Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 12 Apr 2026 22:56:08 +0900 Subject: [PATCH 42/52] chore: add Vagrant bootstrap scripts --- scripts/bootstrap.rust.sh | 32 ++++++++++++++++++++++++++++++++ scripts/bootstrap.sh | 22 ++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 scripts/bootstrap.rust.sh create mode 100644 scripts/bootstrap.sh diff --git a/scripts/bootstrap.rust.sh b/scripts/bootstrap.rust.sh new file mode 100644 index 00000000..30539b19 --- /dev/null +++ b/scripts/bootstrap.rust.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -exu pipefail + +export DEBIAN_FRONTEND=noninteractive + +echo "Bootstrapping VM for toy-tcpip..." +echo "Run script as non-root user: whoami=$(whoami)" + +# ------------------------------------------------------------ +# install general packages +# ------------------------------------------------------------ + +sudo apt-get update -y && sudo apt-get install -y git gcc + +# ------------------------------------------------------------ +# install Rust +# ------------------------------------------------------------ + +# ref: https://github.com/rust-lang-deprecated/rustup.sh/issues/83 +curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +# set cargo path +source $HOME/.cargo/env +echo "cargo version: $(cargo --version)" + +# add to bashrc to add Rust toolchains to PATH +echo "source $HOME/.cargo/env" >> $HOME/.bashrc + +set +exu pipefail + +echo "Bootstrap script completed successfully!" diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh new file mode 100644 index 00000000..5e327a44 --- /dev/null +++ b/scripts/bootstrap.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -exu + +export DEBIAN_FRONTEND=noninteractive + +echo "Bootstrapping VM for toy-tcpip..." +echo "Run script as non-root user: whoami=$(whoami)" + +# install general packages +echo "Installing general packages..." +sudo apt-get update -y && sudo apt-get install -y \ + git \ + make \ + manpages-dev \ + gcc \ + clangd \ + clang-format + +set +exu + +echo "Bootstrap script completed successfully!" From 6a96f597e44e044bd6dfcb3e4cfaf3b7208cec9d Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 12 Apr 2026 22:56:26 +0900 Subject: [PATCH 43/52] chore: add `Vagrantfile` for setup VM --- Vagrantfile | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 Vagrantfile diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 00000000..3b7b6da7 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,61 @@ +$vm_name = "toy-tcpip" +$vm_cpus = 4 +$vm_memory = 4096 +$vm_notes = "VM for development of toy-tcpip" +$vm_directory_share_mode = "virtFS" +$vm_forwarded_port = 80 +$vm_host_port = 8080 + +# for synced folder +$base_dir = "/home/me" +$base_work_dir = "#{$base_dir}/workspace" +$work_dir = "#{$base_work_dir}/microps" +$sync_dir = "#{$base_work_dir}/_mnt_microps" + +# ref: https://naveenrajm7.github.io/vagrant_utm/configuration.html +Vagrant.configure("2") do |config| + # ref: https://portal.cloud.hashicorp.com/vagrant/discover/utm/ubuntu-24.04 + config.vm.box = "utm/ubuntu-24.04" + config.vm.box_version = "0.0.1" + + # Hostname inside the VM + config.vm.hostname = $vm_name + + # Ports to forward + # localhost:80 access is forwarded to :8080 + config.vm.network "forwarded_port", guest: $vm_forwarded_port, host: $vm_host_port + + # Synced folder + config.vm.synced_folder ".", $sync_dir, create: true + + # 501:20 → 1000:1000 に見せる + config.bindfs.bind_folder $sync_dir, $work_dir, + map: "501/1000:@20/@1000", perms: "u=rwX:g=rwX:o=rX", create_as_user: true + + # Provider specific configs + config.vm.provider "utm" do |u| + # Name in UTM UI + u.name = $vm_name + + # Machine type + # not supported on vagrant_utm plugin + # u.machine = "vf" + + # CPU in cores + u.cpus = $vm_cpus + + # Memory in MB + u.memory = $vm_memory + + # Notes for UTM VM (Appears in UTM UI) + u.notes = $vm_notes + + # QEMU Directoy Share mode for the VM. + # Takes none, webDAV or virtFS + u.directory_share_mode = $vm_directory_share_mode + end + + # Provisioner config, supports all built provisioners + # ref: https://developer.hashicorp.com/vagrant/docs/provisioning/shell + config.vm.provision "shell", privileged: false, path: "scripts/bootstrap.sh" +end From fe55aa820dff4accb323093d9cf747756fbb28ec Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 12 Apr 2026 22:57:17 +0900 Subject: [PATCH 44/52] docs: add README --- README.md | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..35de7f8b --- /dev/null +++ b/README.md @@ -0,0 +1,132 @@ +# microps + +[![MIT license badge][mit-badge]][mit-url] + +[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg +[mit-url]: https://github.com/nukopy/microps/blob/main/LICENSE + +microps is an implementation of a small TCP/IP protocol stack written in C for learning. + +This project is based on the development guide, [KLab Expert Camp 6](https://drive.google.com/drive/folders/1k2vymbC3vUk5CTJbay4LLEdZ9HemIpZe) (only in Japanese). + +Original implementation is [pandax381/microps](https://github.com/pandax381/microps). + +## Features + +TODO + +ref: https://github.com/pandax381/microps#features + +## Environment + +Host + +- OS: macOS 14.7 +- UTM 4.6.5 +- Ruby 3.3.0 +- Vagrant 2.4.9 +- vagrant_utm 0.1.5 (Version Constraint: > 0) +- [vagrant-bindfs](https://github.com/gael-ian/vagrant-bindfs) 1.3.1 + +VM for development + +- OS: Ubuntu 24.04 + - Box definition in HashiCorp: [utm/ubuntu-24.04](https://portal.cloud.hashicorp.com/vagrant/discover/utm/ubuntu-24.04) + - Official doc of UTM: [UTM Vagrant Box Gallery](https://naveenrajm7.github.io/vagrant_utm/boxes/utm_box_gallery.html) + +## Setup + +### Install UTM + +See [UTM homepage](https://mac.getutm.app/). + +For macOS users, this article is helpful (only in Japanese): [【macOS】UTM で Ubuntu Desktop 24.04 LTS の仮想マシン環境を構築する](https://zenn.dev/pyteyon/scraps/0c8cec3c56812b) + +### Install Vagrant + +```sh +brew install vagrant + +# install vagrant plugins for UTM +vagrant plugin install vagrant_utm --plugin-version 0.1.5 +vagrant plugin install vagrant-bindfs --plugin-version 1.3.1 +``` + +### Setup VM + +```sh +cd microps + +# start VM +vagrant up + +# start VM with provision (rerun bootstrap script) +vagrant up --provision + +# reload VM +vagrant reload + +# realod VM with provision +vagrant reload --provision + +# status VM +vagrant status + +# ssh to VM +vagrant ssh + +# get SSH config (e.g. for VSCode Remote-SSH) +vagrant ssh-config + +# stop VM +vagrant halt +``` + +## Development + +Working directory in VM is `/home/me/workspace/microps`, so following commands should be run in this directory. + +### Build + +```sh +cd /home/me/workspace/microps + +# build +make + +# clean +make clean +``` + +### Test + +```sh +# build +make + +# run tests: step0.exe, step1.exe, ... +./test/step0.exe +``` + +### Format + +```sh +make format +``` + +## References + +- [github.com/pandax381/microps](https://github.com/pandax381/microps) + - Implementation of TCP/IP protocol stack in C. This is a reference implementation of this project. +- [KLab Expert Camp 6](https://drive.google.com/drive/folders/1k2vymbC3vUk5CTJbay4LLEdZ9HemIpZe) + - A series of lectures on TCP/IP protocol stack, [microps](https://github.com/pandax381/microps). This project is based on the contents of this lecture. +- [Available Vagrant boxes for UTM](https://portal.cloud.hashicorp.com/vagrant/discover?query=utm) + - Vagrant plugin for UTM. + +## License + +Copyright (c) 2012-2021 YAMAMOTO Masaya + +microps is licensed under the MIT License. For more details, check out the [LICENSE](./LICENSE) file. + +# microps From 303453ebb7eacce0b060beae4661e8ee378884f5 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 12 Apr 2026 23:26:41 +0900 Subject: [PATCH 45/52] chore: change directory structure & update build settings in Makefile --- Makefile | 21 ++++++++++++--------- {driver => src/driver}/dummy.c | 0 {driver => src/driver}/dummy.h | 0 net.c => src/net.c | 0 net.h => src/net.h | 0 {platform => src/platform}/linux/intr.c | 0 {platform => src/platform}/linux/platform.h | 0 util.c => src/util.c | 0 util.h => src/util.h | 0 9 files changed, 12 insertions(+), 9 deletions(-) rename {driver => src/driver}/dummy.c (100%) rename {driver => src/driver}/dummy.h (100%) rename net.c => src/net.c (100%) rename net.h => src/net.h (100%) rename {platform => src/platform}/linux/intr.c (100%) rename {platform => src/platform}/linux/platform.h (100%) rename util.c => src/util.c (100%) rename util.h => src/util.h (100%) diff --git a/Makefile b/Makefile index 6e41d1c2..4116d4aa 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,23 @@ +SRC_DIR := src +TEST_DIR := test + APPS = -DRIVERS = driver/dummy.o +DRIVERS = $(SRC_DIR)/driver/dummy.o -OBJS = util.o \ - net.o \ +OBJS = $(SRC_DIR)/util.o \ + $(SRC_DIR)/net.o \ -TESTS = test/step0.exe \ - test/step1.exe \ - test/step2.exe \ +TESTS = $(TEST_DIR)/step0.exe \ + $(TEST_DIR)/step1.exe \ + $(TEST_DIR)/step2.exe \ CC := gcc -CFLAGS := $(CFLAGS) -g -W -Wall -Wno-unused-parameter -iquote . +CFLAGS := $(CFLAGS) -g -W -Wall -Wno-unused-parameter -iquote $(SRC_DIR) ifeq ($(shell uname),Linux) # Linux specific settings - BASE = platform/linux + BASE = $(SRC_DIR)/platform/linux CFLAGS := $(CFLAGS) -pthread -iquote $(BASE) OBJS := $(OBJS) $(BASE)/intr.o endif @@ -33,7 +36,7 @@ all: $(APPS) $(TESTS) $(APPS): %.exe : %.o $(OBJS) $(DRIVERS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) -$(TESTS): %.exe : %.o $(OBJS) $(DRIVERS) test/test.h +$(TESTS): %.exe : %.o $(OBJS) $(DRIVERS) $(TEST_DIR)/test.h $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) .c.o: diff --git a/driver/dummy.c b/src/driver/dummy.c similarity index 100% rename from driver/dummy.c rename to src/driver/dummy.c diff --git a/driver/dummy.h b/src/driver/dummy.h similarity index 100% rename from driver/dummy.h rename to src/driver/dummy.h diff --git a/net.c b/src/net.c similarity index 100% rename from net.c rename to src/net.c diff --git a/net.h b/src/net.h similarity index 100% rename from net.h rename to src/net.h diff --git a/platform/linux/intr.c b/src/platform/linux/intr.c similarity index 100% rename from platform/linux/intr.c rename to src/platform/linux/intr.c diff --git a/platform/linux/platform.h b/src/platform/linux/platform.h similarity index 100% rename from platform/linux/platform.h rename to src/platform/linux/platform.h diff --git a/util.c b/src/util.c similarity index 100% rename from util.c rename to src/util.c diff --git a/util.h b/src/util.h similarity index 100% rename from util.h rename to src/util.h From b69e126c24e3637082c74811a14befbe7bfe3814 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 12 Apr 2026 23:26:58 +0900 Subject: [PATCH 46/52] chore: unnecessary Cursor settings --- .vscode/c_cpp_properties.json | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .vscode/c_cpp_properties.json diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json deleted file mode 100644 index 85d50b18..00000000 --- a/.vscode/c_cpp_properties.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "configurations": [ - { - "name": "Linux", - "includePath": [ - "${workspaceFolder}/**" - ], - "defines": [], - "macFrameworkPath": [], - "compilerPath": "/usr/bin/gcc", - "intelliSenseMode": "linux-gcc-x64", - "cStandard": "c17", - "cppStandard": "c++17" - } - ], - "version": 4 -} From 0cb738baada6520c94baf5105cf63b07bea5d324 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 12 Apr 2026 23:27:29 +0900 Subject: [PATCH 47/52] chore: update LSP clangd settings related to include --- compile_flags.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compile_flags.txt b/compile_flags.txt index ec1c3067..d2f81450 100644 --- a/compile_flags.txt +++ b/compile_flags.txt @@ -1,2 +1,4 @@ --I. --Iplatform/linux +-iquote +src +-iquote +src/platform/linux From 96b2d1c64d77d05b83ad6f594eae1771bae8e2cf Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 12 Apr 2026 23:27:55 +0900 Subject: [PATCH 48/52] docs: add build command with hexdump for debug --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 35de7f8b..2e3a0889 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,16 @@ make ./test/step0.exe ``` +Build with hexdump flag: + +```sh +make clean +CFLAGS=-DHEXDUMP make + +# run tests: step0.exe, step1.exe, ... +./test/step0.exe +``` + ### Format ```sh From 2430fc53679e4540538cf1ca1f815ab1bf68303c Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 12 Apr 2026 17:52:05 +0000 Subject: [PATCH 49/52] chore: add comments for clangd --- .vscode/settings.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 99dbd975..328d77a8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,10 @@ // LSP "clangd.path": "/usr/bin/clangd", + // for compile_commands.json + // "clangd.arguments": [ + // "--compile-commands-dir=${workspaceFolder}" + // ], // Formatter "clang-format.executable": "/usr/bin/clang-format", From 128b8f2fddda967e5c6857e8386895cec4c92509 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 12 Apr 2026 18:28:06 +0000 Subject: [PATCH 50/52] docs: add Cursor project rules --- .cursor/rules/markdown-writing.mdc | 75 ++++++++++++++++++++++++++++++ .cursor/rules/respond-japanese.mdc | 16 +++++++ .cursor/rules/review-doc.mdc | 73 +++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 .cursor/rules/markdown-writing.mdc create mode 100644 .cursor/rules/respond-japanese.mdc create mode 100644 .cursor/rules/review-doc.mdc diff --git a/.cursor/rules/markdown-writing.mdc b/.cursor/rules/markdown-writing.mdc new file mode 100644 index 00000000..52e62875 --- /dev/null +++ b/.cursor/rules/markdown-writing.mdc @@ -0,0 +1,75 @@ +--- +description: Rules for writing Markdown documents, including code blocks, sequence diagrams, and language-specific conventions. +# Cursor +globs: "**/*.md" +alwaysApply: false +# Claude Code +paths: + - "**/*.md" +--- + +# Markdown Document Writing Guidelines + +## Common Writing Rules + +- Do not use dividers (`---`, `***`, `___`) +- Always specify a language identifier on code blocks. Use `txt` if no programming language applies. + + Bad: + + ````txt + ``` + some golang code + ``` + ```` + + Good: + + ````txt + ```go + some golang code + ``` + ```` + +- When writing `code` spans or English words inline in Japanese text, add a single half-width space before and after them +- Do not use bold emphasis (`**...**` or `__...__`) in Markdown prose; rely on headings, lists, and `code` spans for structure and emphasis + +## Sequence Diagram Writing Rules + +- Use language-agnostic `mermaid` for sequence diagrams +- Write all text in English, except API-specific Japanese proper nouns +- Use the official full name for participant labels (e.g. `Biprogy API`, not `BipAPI`) + +### `alt` / `else` blocks + +- Use `alt/else` for mutually exclusive branches, not `opt` +- Always put the error case first, success case in `else` + +### Self-arrows + +- Use a self-arrow (`Server->>Server`) for internal processing such as token expiry validation + +### HTTP communication + +- Prefix outgoing HTTP requests with `Request:
` followed by the method and path +- Response arrows must use dashed lines (`-->>`) — prefix with `Response:
` followed by the response body + + ```mermaid + Client->>+Server: Request:
POST /oauth/v2/token/{api_key} + Server-->>-Client: Response:
{ access_token, expires_in, refresh_token } + ``` + +### DB operations + +- Prefix DB reads with `Read:
` and DB writes with `Write:
` +- Response arrows must use dashed lines (`-->>`) — write the result directly with no prefix + + ```mermaid + Server->>+DB: Read:
user_id=123 + DB-->>-Server: user={id: 123, name: "John Doe", encrypted_email: "john.doe@example.com"} or nil + ``` + + ```mermaid + Server->>+DB: Write:
user_id=123
name="John Doe"
encrypted_email="xxx" + DB-->>-Server: OK + ``` diff --git a/.cursor/rules/respond-japanese.mdc b/.cursor/rules/respond-japanese.mdc new file mode 100644 index 00000000..efb1ad3c --- /dev/null +++ b/.cursor/rules/respond-japanese.mdc @@ -0,0 +1,16 @@ +--- +description: +alwaysApply: true +--- + +# Respond in Japanese in chat + +- Respond in Japanese in chat +- If the user asks for a code block, respond in English +- If the user asks for a command, respond in English +- If the user asks for a path, respond in English +- If the user asks for an identifier, respond in English +- If the user asks for a description, respond in English +- If the user asks for a summary, respond in English +- If the user asks for an explanation, respond in English +- If the user asks for a tutorial, respond in English diff --git a/.cursor/rules/review-doc.mdc b/.cursor/rules/review-doc.mdc new file mode 100644 index 00000000..ab6262fe --- /dev/null +++ b/.cursor/rules/review-doc.mdc @@ -0,0 +1,73 @@ +--- +description: Rules for reviewing Markdown documents for readability, consistency, and accuracy. +# Cursor +globs: "**/*.md" +alwaysApply: false +# Claude Code +paths: + - "**/*.md" +--- + +# Document Review Guidelines + +When reviewing a document, check each of the following areas and report issues grouped by severity: **critical** (blocks publication), **moderate** (degrades quality), **minor** (polish). + +## Readability + +### Sentence length and complexity + +- Flag sentences that pack multiple distinct points into a single breath; suggest splitting them. +- In Japanese prose, a sentence exceeding roughly 100 characters without a natural pause is a candidate for review. +- Flag deeply nested parenthetical clauses that make the main point hard to follow. + +### First-time terminology + +- Flag any technical or domain-specific term that appears for the first time without an inline explanation or a pointer to where it is defined. +- Do not flag terms that are clearly within the stated target audience's prior knowledge. +- Check that an abbreviation is expanded on its first use (e.g., "テストランナー(以下、ランナー)"). + +### Redundancy + +- Flag a sentence that restates information already given in the immediately preceding sentence without adding new context. +- Flag a section introduction that does nothing but repeat the heading. +- Flag a summary or conclusion that lists exactly what was already covered without adding synthesis or a takeaway. + +## Consistency + +### Terminology + +- Flag a concept that is referred to by more than one name within the document (e.g., "実行単位" vs "テストの実行単位"). +- Flag a term whose meaning or scope appears to shift between sections. + +### Writing style + +- Flag mixing of formal (です・ます) and plain (だ・である) sentence-ending forms unless the mix is clearly intentional (e.g., inside quoted code comments vs. prose). +- Flag heading styles that are inconsistent (e.g., some headings end with a noun, others with a verb form). + +### Document structure + +- Flag a table of contents entry that does not exactly match the corresponding section heading. +- Flag heading levels that skip a rank (e.g., `##` directly followed by `####`). +- Flag internal cross-references (links or section names in prose) that point to a heading that does not exist or has been renamed. + +### Code and prose alignment + +- Flag a prose description that contradicts the adjacent code block (e.g., wrong argument order, wrong return type). +- Flag identifiers in prose (variable names, function names, type names) that do not match those in the code blocks. + +## Accuracy + +### Technical correctness + +- Flag factual claims that appear incorrect or are inconsistent with what the code or referenced sources show (e.g., wrong API signatures, mischaracterized behavior). +- Flag code blocks that contain syntax errors or would produce output different from what the surrounding text claims. + +### Version and environment information + +- Flag version numbers, OS names, or tool versions that are inconsistent within the document. +- Flag environment details in the prose that do not match the environment section (if one exists). + +### References and links + +- Flag broken anchor links or links to renamed headings. +- Flag a footnote or reference label that is defined but never cited, or cited but never defined. From 10ea1ae619b11193d248edf5ee6f20925b9971ab Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 12 Apr 2026 18:30:39 +0000 Subject: [PATCH 51/52] docs: add note for integration test --- docs/notes/integration-test-e2e-boundary.md | 155 ++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 docs/notes/integration-test-e2e-boundary.md diff --git a/docs/notes/integration-test-e2e-boundary.md b/docs/notes/integration-test-e2e-boundary.md new file mode 100644 index 00000000..e18d9418 --- /dev/null +++ b/docs/notes/integration-test-e2e-boundary.md @@ -0,0 +1,155 @@ +# integration test と E2E test の境界 + +境界は、「どこまでつないで、何を保証したいか」で考えると分かりやすいです。 + +かなり雑に言うと、次のとおりです。 + +- 結合テスト( `integration test` ): 複数コンポーネントをつないで、その連携が正しいかを確かめる +- E2E テスト( `end-to-end test` 、以下 E2E): ユーザーや外部利用者の入口から出口まで、ひとつの利用フロー全体が成立するかを確かめる + +## まず integration + +integration は、モジュール単体ではなく、境界をまたいだ接続部分を見るテストです。 + +たとえば Web アプリなら、次のような組み合わせです。 + +- API サーバ + DB +- API サーバ + Redis +- 認証 middleware + handler + DB +- HTTP handler + service + repository + +ここで見たいのは、次のような点です。 + +- データの受け渡しが正しいか +- インターフェースのつなぎ込みが正しいか +- トランザクションや永続化が正しいか +- 依存先と合わせたときに壊れないか + +つまり integration は、システム全体でなくてもよいです。一部だけ本物をつないで確認するテストでも integration です。 + +## 次に E2E + +E2E は、利用者の行動単位で見ることが多いです。 + +たとえば Web サービスなら、次のような流れです。 + +- ログインする +- 商品を検索する +- カートに入れる +- 購入する +- 注文完了画面が出る +- DB やメール送信まで含めて成立する + +という、最初から最後までの業務フローを確認します。 + +ここでは内部のモジュール境界より、次の点が重要です。 + +- ユーザーにとって機能が成立しているか +- 本番に近い構成で動くか +- 主要フローが壊れていないか + +## 境界はどこか + +実際の境界はきれいに線引きできるわけではありません。ただ、次の観点でかなり整理できます。 + +### 1. 見ている視点 + +- integration: 開発者視点で「この部品同士の接続が正しいか」 +- E2E: 利用者視点で「この機能フローが最後まで成功するか」 + +### 2. 範囲 + +- integration: 部分的でもよい +- E2E: 基本的に入口から出口まで + +### 3. 失敗したときの意味 + +- integration: どの境界の接続が壊れたかを知りたい +- E2E: ユーザー体験として成立しないことを検知したい + +### 4. 実行コスト + +- integration: 比較的軽い +- E2E: 重い、遅い、不安定になりやすい + +## 具体例 + +### 例1: Go の API サーバ + +`POST /users` でユーザー作成する場合です。 + +#### integration test + +- HTTP handler を起動 +- 実 DB につなぐ +- リクエストを送る +- DB に保存されているか確認 + +これは handler、service、repository、DB の連携を見るので integration です。UI もメール送信も本番全体も見ていません。 + +#### E2E test + +- ブラウザを立ち上げる +- サインアップ画面に入力 +- 送信 +- メール認証 +- ログイン +- マイページ表示確認 + +これはユーザーの流れ全体を見ているので E2E です。 + +### 例2: 自作 TCP/IP スタック + +#### integration test + +- Ethernet 層と IP 層を接続 +- IP パケットを渡す +- 正しく分解・転送されるか確認 + +あるいは次のような例も integration です。 + +- TCP 層 + timer + retransmission queue を本物でつなぐ +- ACK ロス時に再送されるか確認 + +複数コンポーネント連携を見ていますが、システム全体の利用フローではありません。 + +#### E2E test + +- Node A と Node B を立てる +- 自作スタックで 3-way handshake +- データ送信 +- 再送 +- 切断 +- 最後に期待した payload が相手に届く + +これはかなり E2E 的です。「TCP 通信という利用フロー全体」が成立しているかを見ています。 + +## よくある迷い + +### 「本物の DB を使ったら E2E?」 + +いいえ、必ずしも違います。本物の依存先を使うテストは integration であることは多いですが、E2E とは限りません。 + +### 「API を叩いたら E2E?」 + +これも違います。API を直接叩いて 1 endpoint の保存処理だけ見ているなら、integration のことが多いです。 + +### 「全部本物なら E2E?」 + +これも半分だけ正しいです。全部本物でも、見ている範囲が一部分なら integration です。入口から出口までのユーザーフローを見て初めて E2E と呼びやすいです。 + +## 実務での判断基準 + +迷ったら、次の質問で判定するとよいです。 + +「このテストは、部品同士の接続確認なのか、利用フロー全体の成立確認なのか?」 + +- 接続確認なら integration +- 利用フロー全体なら E2E + +## 一言でまとめると + +- integration は「部品と部品のつながりを見るテスト」 +- E2E は「ユーザーの目的達成まで全部通るかを見るテスト」 + +E2E は integration を含む、より広い範囲のテストだと思うと分かりやすいです。 From 960abe965b3254075314cab0a2a740c3597f7557 Mon Sep 17 00:00:00 2001 From: nukopy Date: Sun, 12 Apr 2026 18:31:20 +0000 Subject: [PATCH 52/52] docs: add Git configuration instructions --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 2e3a0889..6cc776be 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ vagrant plugin install vagrant-bindfs --plugin-version 1.3.1 ### Setup VM +Commands for managing VM: + ```sh cd microps @@ -82,6 +84,13 @@ vagrant ssh-config vagrant halt ``` +Before starting development, you should set Git config in VM like below: + +```sh +git config --global user.name "nukopy" +git config --global user.email "nukopy@gmail.com" +``` + ## Development Working directory in VM is `/home/me/workspace/microps`, so following commands should be run in this directory.