Сдача лабораторных работ включает два этапа: выполнение и защита.
- Выполнение подразумевает выполнение выданных преподавателем заданий на лабораторную работу с последующей подачей результатов на проверку. Результаты подаются путем создания Pull Request в этом репозитории (правила оформления указаны в соответствующем пункте), а проверка происходит заочно по установленному графику без участия студента. По результатам проверки, преподаватель в комментарии к Pull Request указывает на выявленные недостатки в решении (если такие имеются) и предоставляет студенту возможность исправить их и подать к перепроверке. После перепроверки, преподаватель оставляет финальный комментарий к итоговому решению, где указывает степень успешности выполнения поставленного задания и оценивает решение.
- Защита подразумевает демонстрацию работоспособности и корректности полученных результатов "вживую" во время лабораторных занятий, с дачей ответов на контрольные вопросы преподавателя.
Выполнение и защита оцениваются отдельно, по шкале от 0 до 5 баллов;
Итоговая оценка за лабораторную работу определяется по формуле:
где R LAB — итоговый рейтинговый балл за лабораторную работу; K LAB — коэффициент пропорциональности (фиксированный, в соответствии с РСО); N f — оценка за выполнение работы, от 0 до 5; N s — оценка за защиту лабораторной работы, от 0 до 5.
Таким образом, именно оценка за выполнение вносит наибольший вклад в итоговый балл
Максимальная оценка за выполнение лабораторной работы составляет:
- 5 баллов — если лабораторная работа выполнена и подана к финальной проверке без отклонений от графика проверки;
- 3 балла — если лабораторная работа выполнена и подана к финальной проверке с отклонением от графика проверки на одну неделю;
- 0 баллов — если лабораторная работа выполнена и подана к финальной проверке с отклонением от графика проверки более, чем на одну неделю.
Максимальная оценка за защиту лабораторной работы составляет:
- 5 баллов — если лабораторная работа защищена вовремя, во время защиты студент демонстрирует глубокое владение теоретическим материалом, высокий уровень практических навыков, творческий подход к решению задания лабораторной работы, правильно и четко отвечает на вопросы преподавателя;
- 3 балла — если лабораторная работа защищена вовремя, во время защиты студент демонстрирует владение теоретическим материалом и практическими навыками на уровне, достаточном для выполнения задания лабораторной работы, отвечает на вопросы преподавателя с незначительными неточностями;
- 1.5 балла — если лабораторная работа защищена вовремя, во время защиты студент демонстрирует правильность выполнения заданий лабораторной работы, но тем не менее, не может правильно ответить на вопросы преподавателя;
- 0 баллов — если лабораторная работа защищена с опозданием более, чем на одно занятие.
Поданные к проверке лабораторные работы проверяются в соответствии с графиком:
Каждая суббота, до 16:00 по киевскому времени |
Каждый понедельник, до 17:00 по киевскому времени |
При наличии уважительной причины — в другое удобное время, по предварительному согласованию с преподавателем не позже, чем за неделю |
Поданные Pull Request также могут проверяться непосредственно на лабораторном занятии. Стоит учитывать, что приоритет предоставляется студентам, защищающим уже выполненные и проверенные работы. Проверка Pull Request кропотлива и занимает порядка 10..30 минут.
Поданные вне приведенного в таблице выше графика Pull Request, на проверку которых на занятии времени не хватило поданными вовремя не считаются, поэтому настоятельно не рекомендуется затягивать до последнего.
На основании вышеприведенных положений, можно привести рекомендуемую последовательность действий:
- Задание к лабораторной работе выполняется в удобное для студента время и Pull Request подается к первой проверке до субботы 16:00;
- Преподаватель проверяет Pull Request, и при наличии недостатков в решении, студент имеет возможность исправить их до следующей проверки в понедельник 17:00;
- На следующем лабораторном занятии студент защищает выполненную и проверенную работу, и приступает к выполнению следующей. При наличии вопросов — их будет очень удобно сразу же задать преподавателю и получить ответы и рекомендации.
И так далее для всех последующих работ
Курс предусматривает наличие консультаций. Консультации проводятся в онлайн-режиме и использованием мессенджеров Telegram и Skype (или в оффлайн-режиме, по предварительному согласованию времени проведения с преподавателем).
Рекомендуется задавать вопросы в Telegram-группу курса. Задавать вопросы в виде личных сообщений в Telegram преподавателю (@thodnev) не рекомендуется, поскольку это препятствует расспостранению полезной информации среди студентов.
Преподаватель оставляет за собой право перенаправлять личные сообщения в Telegram, в которых не указана просьба о конфиденциальности в общую группу с целью обеспечения максимального охвата аудитории.
Предварительно необходимо ознакомиться с форматированием Telegram. Вопросы, содержащие фрагменты кода в виде изображений или не моноширинного текста будут немедленно удаляться без объяснения причин.
Преподаватель оставляет за собой право не отвечать на вопросы в Telegram в нерабочее время. Время, уделяемое ответам на вопросы в Telegram составляет не менее 1.5 часа в неделю в среднем.
По предварительному согласованию личным сообщением в Telegram, возможно организовать Skype-сессию видеосвязи — уместно в случае вопросов, требующих демонстрации экрана и подобных вопросов, для ответа на которые средств Teleram недостаточно.
normal text |
normal text |
**bold text** |
bold text |
__italic text__ |
italic text |
some `inline code` part |
some inline code part |
```multi
line
code
```
|
multi
line
code
|
Ниже приведено краткое резюме правил оформления кода. Более подробно все описано в официальном Coding Style ядра, которым будем руководствоваться. В случае противоречий, приоритет имеют приведенные ниже правила. Список правил будет пополняться по мере необходимости.
Максимальная длина строки равна:
- 80 символов для модулей ядра и всего, что относится к дереву исходников ядра
- 99 сиволов для всего, что не относится к ядру
Пустые строки должны быть пустыми и не содержать других символов кроме \n. Все строки заканчиваются на \n. Каждый исходник обязательно заканчивается пустой строкой
Отступы:
- Для всего, что относится к ядру, в коде для отступа используется символ TAB. 1 отступ = 1 TAB. 1 TAB эквивалентен 8 пробелам, что необходимо выставить в редакторе
- Для всего, что не относится к ядру, для отступов используются символы пробела. 1 отступ = 1 TAB = 4 пробела
Фигурные скобки { }:
В определениях функций открывающая скобка ставится со следующей строки
inline unsigned int invert(unsigned int val) { return ~val; }
В случае с if-else, используется следующий стиль:
if (a == b) { do_first(); } else if (c == a) { do_next(); } else { do_nothing(); }
В случае с switch, case должны размещаться на том же уровне идентации, что и switch. Если используются сквозные(не содержащие break, кроме default) case, они должны быть обозначены как fall-through. Не рекомендуется злоупотреблять использованием сквозных case. Пример стиля:
switch (state) { case STATE_INIT: do_first(); break; case STATE_RUN: do_run(); /* fall through */ case STATE_NEXT: do_next() break; default: return EUNKNOWN; }
Во всех остальных случаях, открывающая скобка ставится через пробел в той же строке, а закрывающая – на уровне идентации блока открывающей. Пример:
if (a == b) { for (int i = 0; i < sz; i++) { do_smthng(); } a = sz; }
Круглые скобки ( ):
В выражениях (statements), отделяются пробелами. Пример:
if (state) { ... } for (i = k; i >= 0; i--) { ... } while (!ret) { ... } do { ... } while (i < cnt);
В определении функций и их вызовах пробелами не отделяются:
bool is_last(struct item *it) { ... } bool tst = is_last(item_ptr); while (!is_last(ptr)) { ... }
Компаундные конструкции переносятся следующим образом:
В выражениях с if логический оператор переносится на следующую строку, которая начинается с двойной идентации. Пример:
if ((LAST_ITEM == a) && (b != a) && (NOT_FIRST == c) && (k == p) && (NOT_FIRST != k) && (g == r) && ((a == r) || (b == r)) { do_something(); }
Таким образом, при чтении кода не нужно искать оператор на предыдущей строке, а за счет идентации часть тестируемого выражения не перепутать с выражениями внутри if-блока.
В длинных вызовах функций, при переносе аргументы находятся на уровне первого символа за открывающей скобкой. Закрывающая скобка ставится за последним аргументом. Если это невозможно, первый аргумент ставится на уровне идентации плюс один от уровня идентации вызова. Пример:
// first variant - aligned with opening delimiter prn("Here we have some long call" "\nThis string literal is concatenated." "\nFinally we have params: %d, %d, %d\n", some_really_really_really_long_long_param, short_param_a, short_param_b); // second variant - add an extra level of indentation to distinguish arguments some_very_very_very_long_function_call( one_indented_arg, second_indented_arg);
Бинарные и тернарные операторы (+, -, *, /, %, =, <, >, <=, >=, ==, !=, <<, >>, |, &, ^, ?, :) отделяются слева и справа пробелами. Например: a + b вместо a+b. Унарные операторы пробелом от аргумента не отделяются. Например: ++a, b->c, k = -a
McCabe complexity не должна превышать 6 для простых функций и 9 для сложных. Сложные функции, McCabe complexity которых превышает 6 должны содержать локально задокументированное описание, доказывающее, что именно такой вариант реализации является оптимальным. Пример расчета:
int somefunc(int a, int b, int c) { // McCabe = 1 at this point // statements denoted as ... here affect the control flow // McCabe is all about graph and possible paths, used to reach // from function call to return // we have two execution variants (if, else), each adds +1 to complexity if (a) { ... } else { ... } // at this point it would have been equal to 1 + 2 = 3 // but we have more to offer: // add +1 if (b) { // add +2 if (a == c) { ... } else { ... } } // at this point it would have been equal to 1 + 1 + 2 = 4 // but we have even more to offer: // add +1 if (c) { // add +3 if (b) { ... } else if (c) { ... } else { ... } // add +1 as we have two possibilities: // having return here and notreturned() acts as if-else return 0; } notreturned(); return -1; // at this point it is equal to 1 + 1 + 3 + 1 = 6 // add one more indentation level and you're screwed }
Код должен быть задокументирован согласно требованиям kernel-doc. Комментарии делятся на "внешние" и "внутренние". Внешние затем используются для автоматической сброрки документации. Внутренние же предназначены для разработчика, что бы повысить читабельность кода, объяснить какие-то неочевидные моменты.
При этом, следует избегать как недостаточной, так и излишней документированности. Например:
Излишне. Тут и так понятно, как расшифровывается cnt и где используется.
int cnt = 0; // counter while (cnt++ < k) { ... }
Недостаточно. Тут не мешало бы описать, зачем мы трактуем somevar как unsigned long long и какие возможны сайд-эффекты. А также откуда взялась эта магическая константа. В большинстве случаев, константы лучше определить как const и использовать в коде, обращаясь по имени.
tmp = *((unsigned long long *)&somevar) & 0xD0BF00AA00000000LLU;
Более подробно о необходимой степени документированности можно почитать в Coding Style.
Использование include guards, в большинстве случаев, считается плохой практикой и недопустимо. То же относится и к #pragma once. Необходимость в данных конструкциях свидетельствует о неправильной декомпозиции и наличии циклических зависимостей. Ниже проиллюстрирован пример такой зависимости и способ борьбы с ней:
Стоит заметить, что циклические зависимости могут возникать и в пределах одного файла. В этом случае, для борьбы с ними используют forward declaration. Пример структуры, содержащей в качестве одного из полей указатель на функцию, принимающую аргументом объект типа этой структуры:
/* Forward declaration */ struct niceobj; /* * Pointer named comfunc to function of the form * void name(struct niceobj *) * defined as type */ typedef void (*comfunc)(struct niceobj *); /* Here we finally declare it */ struct niceobj { /* Use the defined type */ comfunc comfuncptr; /* Simply pointer named otherfunc without type definition * This one can be used without forward declaration */ void (*otherfunc)(struct niceobj *, int); ... }; /* Here we have some function pointers used */ void comcom(struct niceobj *obj) { ... } void otherother(struct niceobj *obj, int somearg) { ... } static const struct niceobj nice = { .comfuncptr = &comcom, .otherfunc = &otherother }; ... /* Use it */ a = nice.comcom(&nice); b = nice.otherfunc(&nice, 0); /* Or explicitly like this */ a = (*nice.comcom)(&nice); b = (*nice.otherfunc)(&nice, 0);
typedef используется для opaque-объектов, внутренняя структура которых частично (лучше – полностью) сокрыта от конечного пользователя. Подразумевается, что пользователю не нужно знать как объект устроен внутри. Для работы с таким объектом пользователь использует методы. Пример такого объекта:
typedef struct unit_s { void *ptr; size_t size; } defunit_t; size_t defunit_getsize(defunit_t *unit); defunit_t *defunit_create(void *ptr, size_t size); ...
Стоит заметить, что даже у структуры, объявленной типом, есть имя (typedef struct unit_s {... вместо просто typedef struct {...). Это необходимо для упрощения отладки. Следует по-возможности избегать анонимного, особенно структур и перечислений (кроме случая, когда перечисления хранят константы).
Пример transparent-объекта, который не скрывает структуру от пользователя:
struct vecpoint { long long x; long long y; }; struct vecpoint to_vector(struct vecpoint *p1, struct vecpoint *p2) { struct vecpoint ret = { .x = p2->x - p1->x, .y = p2->y - p1->y }; return ret; }
Также возможны объекты смешанного вида, часть полей которых сокрыта от пользователя, а часть является открытой. Это очень популярно в ядре. Например, пользователь создает структуру и заполняет определенные поля. Далее вызывает метод init(), который дозаполняет остальное. В таких случаях не стоит использовать typedef, вместо этого, для сокрытия полей используется mangling-схема (когда, например, к имени приватных полей добавляется префикс или суфикс, указывающий на приватность: _, __, pv_, ...
Для всех имен используется стиль наименования lowercase_underscored_style, кроме:
- констант и макроопределений, для них используется UPPERCASE_UNDERSCORED_STYLE
- имен псеводо-ООП типов, для которых используется CamelCaseStyle
Пример псеводо-ООП:
/* Compiled with -fms-extensions */ typedef struct SomeAnimal_s { double kgweight; char *name; } SomeAnimal_t; /* Inherits Animal */ typedef struct HumanPerson_s { SomeAnimal; char *surname; } HumanPerson_t; /* ! NOTICE ! SomeAnimal is in CamelCase and is prefixed, so that all * methods belonging to it start with SomeAnimal_ * while the right part stays in lowercase_underscored_style */ void SomeAnimal_print(SomeAnimal_t *s) { printf("name: \"%s\", weight: %.2f\n", s->name, s->kgweight); } ... HumanPerson_t j = { .kgweight = 80.0, .name = "John", .surname = "Sins" }; SomeAnimal_t bee = { .kgweight = 0.25, .name = "Queen Bee" }; SomeAnimal_t *ptrs[] = {(SomeAnimal_t *)&j, (SomeAnimal_t *)&bee}; for (int i = 0; i < sizeof(ptrs)/sizeof(*ptrs); i++) { SomeAnimal_print(ptrs[i]); } /* Outputs * name: "John", weight: 55.00 * name: "Queen Bee", weight: 0.25 */
В коде, комментариях к нему, названиях переменных, а также документации не допускается использование других языков, кроме английского. Испьзование транслита также не допускается. Исключения составляют:
строки локализации, например:
struct lang lang_UA = { .exit_label = "Вийти"; .create_label = "Створити"; ... };
собственные имена. В данном случае используются правила транслитерации языка оригинала. Например:
В коде драйвера датчика 强光, он может быть записан как qiangquang_sensor
char ≠ byte. В соответствии со стандартом С:
- sizeof(short) ≤ sizeof(int) ≤ sizeof(long) ≤ sizeof(long long)
- char может быть равен или signed char или unsigned char, должен иметь ширину, как минимум 8 бит. При этом его ширина должна быть достаточной, для того, что бы уместить все символы из execution character set
- short и int должны иметь ширину, как минимум 16 бит
- long должен иметь ширину, как минимум 32 бита
- long long должен иметь ширину, как минимум 64 бита
Соответственно все вышеприведенные типы могут иметь одинаковую ширину. Если нужны байты, или другие типы фиксированной ширины, стоит использовать uint8_t, u8 и другие портативные типы.
float – тип числа с плавающей запятой одинарной точности. Обычно это 32-битный IEEE 754 single-precision формат.
Литерал для определения float это F или f, например: 3.14F. Тогда как 3.14 это double
double– тип числа с плавающей запятой двойной точности. Как правило это 64-битный IEEE 754 double-precision формат.
long double – тип числа с плавающей запятой расширенной точности. Реализации существенно отличаются
Литерал для определения long double это L или l, например: 3.14L.
- Студент при желании может добавить в код и/или сопроводительную документацию сведения об авторстве
- Студент обязан самостоятельно выбрать лицензию, под которой публикуются его наработки в репозитории.
Лицензия может применяться как ко всей директории студента в репозитории (в этом случае
LICENSE
необходимо положить в корень), либо для каждой из работ в отдельности (в этом случаеLICENSE
будет лежать в директории отдельной работы). При работе с ядром Linux и другими проектами необходимо обеспечить неконфликтность выбранной лицензии с лицензией проекта. - Заимствование элегантных решений приветствуется. В IT все крутится вокруг Code Reuse. Незачем пытаться повторить удачный алгоритм сортировки, если он уже имеется, хорошо документирован и был протестирован поколениями разработчиков
- Каждый заимствованный фрагмент должен содержать указание на: источник, автора, лицензию на использование. Заимствование изображений, фрагментов документации, экспериментальных данных и даже идей также являются заимствованиями и должны содержать вышеприведенные указания
- Заимствование не допускается, в случаях если:
- Это приводит к конфликту лицензий. Тем не менее, в таких случаях допускается реимплементация (код изучается, заимствуется идея, с должным указанием на первоисточник, код пишется заново без использования оригинального);
- Заимствованное решение является запатентованным;
- Заимствованный фрагмент является черезмерно большим (составляет существенную часть реализации);
- Заимствованный фрагмент не дает преимуществ в сравнении с "наивной имплементацией";
- За нарушение правил заимствования, преподаватель оставляет за собой право применить одну или сразу несколько из штрафных санкций:
- Указать на необходимость устранения нарушения при проверке Pull Request;
- Оценить выполнение лабораторной работы в 0 баллов;
- Применить -1 балл к итоговому рейтингу студента (не более раза за каждую лабораторную работу, нарушающую правила заимствования).
Репозиторий имеет следующую структуру:
[+] Repository |----- .etc |--[+] dk71_ivanov | |----- LICENSE (см. выше) | |----- README.rst | |--[+] lab1_threaded_applications | | |----- LICENSE (см. выше) | | |----- README.rst | | |----- .gitignore | | |----- Makefile | | |--[+] src | | | |----- somefile.c | | | |----- ... | | | | | |--[+] inc | | | |----- somefile.h | | | |----- ... | | | | | |--[+] doc | | | |----- ... | | | | | |-- .... | | | |--[+] lab2_simple_kernel_module | | |-- .... | | | |--[+] rgr_you_chose_the_name | | |-- .... | |--[+] dk72_sidorov | |-- .... | |-- ....
Директории имеют названия в нижем_кейсе_с_подчеркиваниями. Использование пробелов в названиях файлов и директорий не допускается.
Названия индивидуальных директорий студента формируются по принципу
groupname_surname
, где surname — фамилия студента, как она записана в заграничном пасспорте или водительских правах, а если они отсутствуют — в соответствии с официальными правилами транслитерации.Названия директорий лабораторных работ не могут быть произвольными и выдаются преподавателем вместе с заданием. Названия других работ формируются по принципу
prefix_name
, где name — название работы на английском языке, выбираемое студентом самостоятельноДиректория каждой работы должна содержать:
README.rst
— электронный протокол к лабораторной работе (либо электронная пояснительная записка для других видов работ);LICENSE
— в случае, если она отсутствует в корневой (индивидуальной) директории студента или имеет место конфликт лицензий;Makefile
. Допускается использование любых систем сборки (кроме модулей ядра, собираемых Kbuild). Тем не менее, основные команды сборки (такие какhelp
all
clean
tidy
и другие) должны иметь возможность запускаться из-под make. При желании использовать отличную от make систему сборки, вышеприведенные рецепты make могут просто передавать управление используемой системе. В Makefile обязательна реализация целейall
,clean
,install
(если используется) и цели по умолчанию.После запуска цели
clean
, директория не должна содержать артефактов сборки (временных файлов, а также результатов сборки, если цельtidy
отсутствует);.gitignore
должен содержать исключения всех артефактов сборки. Другими словами, после запуска сборки и последующей попытки commit всей директории, git не должен подхватывать ни один из файлов, которых не было до запуска сборки. За основу можно взять gitignore от GitHubДиректории
src
(содержит исходники),inc
(заголовки) иdoc
(документация) имеют строго определенное назначение и должны присутствовать в директориях работ по мере необходимости (складывать исходники вместе с заголовками и документацией в корень не допускается)
В любой момент времени, репозиторий должен содержать лишь минимально необходимый набор исходных файлов, документации и другого, требуемого для сборки. Попадание результатов сборки в репозиторий категорически недопустимо.
Pull Request, содержащие коммиты с бинарными файлами (кроме изображений для документации, firmware устройств и подобного) будут отклоняться без объяснения причин. То же касается и документации: например, если документация собирается в .pdf-файл из .rst, нет необходимости включать в коммит сам .pdf
В каждом Pull Request допускается наличие не более двух коммитов. Первый используется для подачи выполненой работы на проверку, а второй — для исправления замечаний (если они имеются).
При внесении изменений в ветку, они автоматически будут подхвачены в Pull Request.
История коммитов master-ветки репозитория должна быть чистой и линейной. Если делаете fork, затем серию коммитов и затем подаете Pull Request, это значит, что в итоге в master-ветку попадут все промежуточные коммиты. Для избежания такой ситуации можно делать squash merge в отдельную ветку (из которой затем подавать Pull Request) или rebase.
Правильный Pull Request
Неправильный Pull Request
Перед подачей Pull Request необходимо убедиться, что вносимые изменения касаются только текущей работы и не затрагивают других файлов и директорий
В описании Pull Request указываются ключевые особенности реализации, которые не уместно указывать в
README.rst
(не нужно дублировать или вместо README писать в описание PR). Если по данной работе уже подавался PR и был отклонен по причинам некорректного оформления, к текущему PR необходимо добавить тег RE и прилинковать предыдущий PR к текущему (так что бы GitHub подхватил).В Reviewers указываете любого из ваших коллег, с кем предварительно договорились.
Когда проводите Peer Review, пишите (используя средства GitHub) все найденные ошибки и предложения по их устранению. Автор PR, в свою очередь, может согласиться или не согласиться, высказать свою точку зрения и в итоге найти консенсус.
После Peer Review, автор PR добавляет преподавателя в Reviewers. Преподаватель указывает на не найденные предыдущим ревьюером ошибки, а также на те, которые ошибками не являются. В итоге преподаватель оставляет комментарий и студент может приступать к исправлению ошибок (если они имеются).
После исправления ошибок, преподаватель в очередной раз смотрит Pull Request и оценивает выполнение работы. Если PR был оформлен правильно (именно PR, вне зависимости от ошибок кода), после второго просмотра, преподаватель выполняет PR merge. С этого момента студент может приступать к защите работы. В случае, если {R был оформлен не правильно, он закрывается преподавателем, а студенту необходимо создать новый со ссылкой на текущий (по вышеописанной процедуре).
Все найденные и не найденные в процессе ревью ошибки учитываются, ревьюер получает бонусные баллы за их нахождение, а автор PR — за их исправление.
Настоятельно не рекомендуется злоупотреблять механизмом ревью, ведь в итоге все смотрит преподаватель.