diff --git a/01_git/scissors/Makefile b/01_git/scissors/Makefile new file mode 100644 index 0000000..341ed08 --- /dev/null +++ b/01_git/scissors/Makefile @@ -0,0 +1,18 @@ +CC=gcc +CFLAGS=-I. +CRYPTOLAGS=-lcrypto +OUT_NAME=rock-paper-scissors + +all: $(OUT_NAME) + +$(OUT_NAME): rock_paper_scissors.o main.o + $(CC) rock_paper_scissors.o main.o -o $(OUT_NAME) $(CFLAGS) $(CRYPTOLAGS) + +main.o: main.c + $(CC) -c main.c $(CFLAGS) + +rock_paper_scissors.o: rock_paper_scissors.c + $(CC) -c rock_paper_scissors.c $(CFLAGS) + +clean: + /bin/rm -rf *.o a.out $(OUT_NAME) diff --git a/01_git/scissors/main.c b/01_git/scissors/main.c new file mode 100644 index 0000000..6e584a4 --- /dev/null +++ b/01_git/scissors/main.c @@ -0,0 +1,7 @@ +#include + +int main() +{ + RPS_run_game(); + return 0; +} diff --git a/01_git/scissors/rock_paper_scissors.c b/01_git/scissors/rock_paper_scissors.c new file mode 100644 index 0000000..20b3040 --- /dev/null +++ b/01_git/scissors/rock_paper_scissors.c @@ -0,0 +1,161 @@ +#include +#include +#include +#include +#include + +#include "rock_paper_scissors.h" + +static RPS_CHOICES_E get_human_choice(void); +static RPS_CHOICES_E get_random_choice(void); +static void RPS_show_game_result(RPS_CHOICES_E comp_choice, RPS_CHOICES_E human_choice); +static void RPS_print_md5(char value); + +static RPS_CHOICE_ENTRY_S choices_arr[] = {{RPS_CHOICE_ROCK, "rock", 'r'}, + {RPS_CHOICE_PAPER, "paper", 'p'}, + {RPS_CHOICE_SCISSORS, "scissors", 's'}, + {RPS_CHOICE_UNKNOWN, "unknown", 'u'}}; + +static RPS_CHOICES_E RPS_get_random_choice(void) +{ + unsigned char buff; + RPS_CHOICES_E choice = RPS_CHOICE_UNKNOWN; + + int fd = open("/dev/urandom", O_RDONLY); + + if (!fd) + { + printf("Error opening /dev/urandom\n"); + exit(1); + } + + read(fd, &buff, sizeof buff); + close(fd); + choice = buff % RPS_N_VALUABLE_CHOICES; + + return choice; +} + + +static RPS_CHOICES_E RPS_get_human_choice(void) +{ + RPS_CHOICES_E choice = RPS_CHOICE_UNKNOWN; + char read_value = '?'; + + printf("Please choose: rock (r) - paper (p) - scissors (s)\n"); + scanf(" %c", &read_value); + + switch (read_value) + { + case 'r': + { + choice = RPS_CHOICE_ROCK; + break; + } + + case 'p': + { + choice = RPS_CHOICE_PAPER; + break; + } + case 's': + { + choice = RPS_CHOICE_SCISSORS; + break; + } + default: + { + choice = RPS_CHOICE_UNKNOWN; + break; + } + } + + return choice; +} + + +static void RPS_print_md5(char value) +{ + MD5_CTX ctx; + unsigned char md5digest[MD5_DIGEST_LENGTH]; + + MD5_Init(&ctx); + MD5_Update(&ctx, (const void *)&value, sizeof value); + MD5_Final(md5digest, &ctx); + + printf("Random choice: %c md5: ", value); + + for ( int i=0;i < MD5_DIGEST_LENGTH; i++ ) { + printf("%02x", md5digest[i]); + }; + printf("\n"); +} + + +static void RPS_show_game_result(RPS_CHOICES_E comp_choice, RPS_CHOICES_E human_choice) +{ + RPS_CHOICE_ENTRY_S *p_human_entry; + RPS_CHOICE_ENTRY_S *p_comp_entry; + int game_result; + + p_comp_entry = &choices_arr[comp_choice]; + p_human_entry = &choices_arr[human_choice]; + + printf("You choose %s, I choose %s\n", p_human_entry->long_name, p_comp_entry->long_name); + + game_result = comp_choice - human_choice; + + switch (game_result) + { + case 0: + { + printf("Draw: %s and %s\n", p_human_entry->long_name, p_comp_entry->long_name); + break; + } + + case 1: + { + printf("I win: %s beats %s\n", p_comp_entry->long_name, p_human_entry->long_name); + break; + } + case 2: + { + printf("You win: %s beats %s\n", p_human_entry->long_name, p_comp_entry->long_name); + break; + } + case -1: + { + printf("You win: %s beats %s\n", p_human_entry->long_name, p_comp_entry->long_name); + break; + } + case -2: + { + printf("I win: %s beats %s\n", p_comp_entry->long_name, p_human_entry->long_name); + break; + } + default: + { + printf("Error game result: %i\n", game_result); + exit(1); + break; + } + } +} + + +void RPS_run_game(void) +{ + RPS_CHOICES_E human_choice = RPS_CHOICE_UNKNOWN; + RPS_CHOICES_E comp_choice = RPS_CHOICE_UNKNOWN; + + comp_choice = RPS_get_random_choice(); + // Optional task: add printing md5 checksum of computer's choice before human's move. + RPS_print_md5(choices_arr[comp_choice].short_name); + + for ( ; human_choice == RPS_CHOICE_UNKNOWN; ) + { + human_choice = RPS_get_human_choice(); + } + + RPS_show_game_result(comp_choice, human_choice); +} diff --git a/01_git/scissors/rock_paper_scissors.h b/01_git/scissors/rock_paper_scissors.h new file mode 100644 index 0000000..f4cd09b --- /dev/null +++ b/01_git/scissors/rock_paper_scissors.h @@ -0,0 +1,23 @@ +#ifndef __ROCK_PAPER_SCISSORS_H_ +#define __ROCK_PAPER_SCISSORS_H_ + +#define RPS_N_VALUABLE_CHOICES 3 + +typedef enum RPS_CHOICES { + RPS_CHOICE_ROCK, + RPS_CHOICE_PAPER, + RPS_CHOICE_SCISSORS, + RPS_CHOICE_UNKNOWN +} RPS_CHOICES_E; + + +typedef struct RPS_CHOICE_ENTRY { + RPS_CHOICES_E id; + char long_name[16]; + char short_name; +} RPS_CHOICE_ENTRY_S; + + +void RPS_run_game(void); + +#endif // __ROCK_PAPER_SCISSORS_H_ diff --git a/02_bash/hwdetect/hwdetect.sh b/02_bash/hwdetect/hwdetect.sh new file mode 100755 index 0000000..5325c95 --- /dev/null +++ b/02_bash/hwdetect/hwdetect.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +FOLDER=${1:-/dev} +echo "Polling folder: $FOLDER" + +PREV=$(stat --terse --format="%n %z" ${FOLDER}/*|sort -n) + +while true +do + NEW=$(stat --terse --format="%n %z" ${FOLDER}/*|sort -n) + DELTA=$(diff <(echo "$PREV") <(echo "$NEW") | grep '^>') + + if [[ "${#DELTA}" != 0 ]] + then + echo "$DELTA" + fi + PREV=$NEW + sleep 1 +done diff --git a/03_module/Makefile b/03_module/Makefile new file mode 100644 index 0000000..8dbe20a --- /dev/null +++ b/03_module/Makefile @@ -0,0 +1,9 @@ +KERNELDIR ?= ../output/build/linux-5.10.7 + +obj-m := hello.o + +all: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) clean diff --git a/03_module/README.md b/03_module/README.md new file mode 100644 index 0000000..739d55a --- /dev/null +++ b/03_module/README.md @@ -0,0 +1,15 @@ +## module home work: create a simple loadable module with parameters + +Create a loadadle kernel module which should accept two integer parameters and provide: + - A sum of parameters upon driver load + - A substration of parameters upon driver unload + +Info about module parameters can be found at: https://devarea.com/linux-kernel-development-kernel-module-parameters/#.YZfWcpFByV4 + +Task should be performed using buildroot+qemu approach + +The task results should contain: +- The module code +- The Makefile +- Dump of the kernel logs from the target system + diff --git a/03_module/dump.txt b/03_module/dump.txt new file mode 100644 index 0000000..1eef35d --- /dev/null +++ b/03_module/dump.txt @@ -0,0 +1,11 @@ +# insmod /tmp/hello.ko n1=42 n2=-10 +hello add n1: 42 and n2: -10 result: 32 +# cd /sys/module/hello/parameters/ +# ls +n1 n2 +# cat n1 +42 +# cat n2 +-10 +# rmmod hello.ko +Bye substract n1: 42 and n2: -10 result: 52 diff --git a/03_module/hello.c b/03_module/hello.c new file mode 100644 index 0000000..7ecdbc1 --- /dev/null +++ b/03_module/hello.c @@ -0,0 +1,26 @@ +#include +#include +#include + + +static int n1=0; +module_param(n1, int, 0660); + +static int n2=0; +module_param(n2, int, 0660); + +int init_module(void) +{ + printk(KERN_INFO "hello add n1: %d and n2: %d result: %d\n", n1, n2, n1+n2); + return 0; +} + +void cleanup_module(void) +{ + printk(KERN_INFO "Bye substract n1: %d and n2: %d result: %d\n", n1, n2, n1-n2); +} + +MODULE_AUTHOR("Ivan Stepanenko "); +MODULE_DESCRIPTION("Hello world module"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1"); diff --git a/04_basic_struct/Makefile b/04_basic_struct/Makefile new file mode 100644 index 0000000..14231e1 --- /dev/null +++ b/04_basic_struct/Makefile @@ -0,0 +1,9 @@ +KERNELDIR ?= ../output/build/linux-5.10.7 + +obj-m := wimbledon.o + +all: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) clean diff --git a/04_basic_struct/README.md b/04_basic_struct/README.md new file mode 100644 index 0000000..445d6dc --- /dev/null +++ b/04_basic_struct/README.md @@ -0,0 +1,7 @@ +## Basic structure homework +Implement object with name “MyObject” which is parent of kernel_kobj. +Object should include linked_list structure. +This object should contain sysfs attribute with name “list”. +On read form attribute “list” it should show content of the objects linked list. +On write to attribute “list” it should add new string to the objects linked list. +!! Do not forget properly free all the resources during rmmod. diff --git a/04_basic_struct/dump.txt b/04_basic_struct/dump.txt new file mode 100644 index 0000000..29f5ecb --- /dev/null +++ b/04_basic_struct/dump.txt @@ -0,0 +1,37 @@ +# insmod /tmp/wimbledon.ko +wimbledon: loading out-of-tree module taints kernel. +# echo "1" > /sys/kernel/wimbledon/list +wimbledon: adding msg size: 2 to the list: '1 +' +# echo -n "2" > /sys/kernel/wimbledon/list +wimbledon: adding msg size: 1 to the list: '2' +# echo -n "33" > /sys/kernel/wimbledon/list +wimbledon: adding msg size: 2 to the list: '33' +# echo "44" > /sys/kernel/wimbledon/list +wimbledon: adding msg size: 3 to the list: '44 +' +# echo -n "55" > /sys/kernel/wimbledon/list +wimbledon: adding msg size: 2 to the list: '55' +# cat /sys/kernel/wimbledon/list +wimbledon: msg #0 size: 2 '1 +' +wimbledon: msg #1 size: 1 '2' +wimbledon: msg #2 size: 2 '33' +wimbledon: msg #3 size: 3 '44 +' +wimbledon: msg #4 size: 2 '55' +1 +23344 +55# + +# rmmod wimbledon.ko +wimbledon: freeing msg size: 2 from the list: '55' +wimbledon: freeing msg size: 3 from the list: '44 +' +wimbledon: freeing msg size: 2 from the list: '33' +wimbledon: freeing msg size: 1 from the list: '2' +wimbledon: freeing msg size: 2 from the list: '1 +' +wimbledon: list is empty: 1 +wimbledon: Module exited + diff --git a/04_basic_struct/wimbledon.c b/04_basic_struct/wimbledon.c new file mode 100644 index 0000000..c05e758 --- /dev/null +++ b/04_basic_struct/wimbledon.c @@ -0,0 +1,118 @@ +/* + * Name of this module is English wiki random article choice. + * To get away from "hello", "foo", "bar", "baz"... names. + */ +#define DEBUG +#define pr_fmt(fmt) "%s: " fmt, KBUILD_MODNAME +#include +#include +#include +#include +#include +#include +#include + + +struct msg_item { + struct list_head list; + char *msg; + size_t msg_len; +}; +static LIST_HEAD(msg_list_head); + +static ssize_t wimbledon_show(struct kobject *kobj, struct kobj_attribute *attr, char *buff); +static ssize_t wimbledon_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buff, size_t count); +static int wimbledon_init(void); +static void wimbledon_exit(void); + +static struct kobj_attribute list_attribute = + __ATTR(list, 0644, wimbledon_show, wimbledon_store); +static struct kobject *wimbledon_kobj; + + +static ssize_t wimbledon_show(struct kobject *kobj, struct kobj_attribute *attr, char *buff) +{ + struct msg_item *pos = NULL; + size_t item_number = 0; + size_t count = 0; + char *p_buff = buff; + + list_for_each_entry_reverse(pos, &msg_list_head, list) { + pr_info("msg #%lu size: %lu '%.*s'\n", + item_number, pos->msg_len, (int)pos->msg_len, (char *)pos->msg); + + memcpy(p_buff, (char *)pos->msg, pos->msg_len); + p_buff += pos->msg_len; + count += pos->msg_len; + item_number += 1; + } + return count; +} + + +static ssize_t wimbledon_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buff, size_t count) +{ + char *p_msg = NULL; + struct msg_item *p_item = NULL; + + p_msg = kmalloc(count, GFP_KERNEL); + if (!p_msg) { + pr_err("could not allocate msg cnt: %lu\n", count); + return -ENOMEM; + } + p_item = kmalloc(sizeof(struct msg_item), GFP_KERNEL); + if (!p_item) { + pr_err("could not allocate msg_item\n"); + return -ENOMEM; + } + memcpy(p_msg, buff, count); + p_item->msg = p_msg; + p_item->msg_len = count; + + pr_info("adding msg size: %lu to the list: '%.*s'\n", + p_item->msg_len, (int)p_item->msg_len, (char *)p_item->msg); + list_add(&p_item->list, &msg_list_head); + return count; +} + + +static int wimbledon_init(void) +{ + int res = 0; + wimbledon_kobj = kobject_create_and_add("wimbledon", kernel_kobj); + if (!wimbledon_kobj) + return -ENOMEM; + res = sysfs_create_file(wimbledon_kobj, &list_attribute.attr); + if (res) + kobject_put(wimbledon_kobj); + + return res; +} + + +static void wimbledon_exit(void) +{ + struct list_head *pos = NULL; + struct list_head *n = NULL; + struct msg_item *p_item = NULL; + + list_for_each_safe(pos, n, &msg_list_head) { + p_item = list_entry(pos, struct msg_item, list); + pr_info("freeing msg size: %lu from the list: '%.*s'\n", + p_item->msg_len, (int)p_item->msg_len, (char *)p_item->msg); + list_del(pos); + kfree(p_item->msg); + kfree(p_item); + } + pr_info("list is empty: %d\n", list_empty_careful(&msg_list_head)); + kobject_put(wimbledon_kobj); + pr_info("Module exited\n"); +} + +module_init(wimbledon_init); +module_exit(wimbledon_exit); + +MODULE_AUTHOR("Ivan Stepanenko "); +MODULE_DESCRIPTION("Wimbledon module"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1"); diff --git a/05_timers/README.md b/05_timers/README.md new file mode 100644 index 0000000..55b41f2 --- /dev/null +++ b/05_timers/README.md @@ -0,0 +1,10 @@ +## Homework: Linux Kernel Time Management + +1. Implement program which return absolute time in user space. +Use clock_gettime() from time.h. Try different clock id. +Find the difference. Show possible clock resolution provided by clock_getres(). + +2. Implement kernel module with API in sysfs, which returns relative +time in maximum possible resolution passed since previous read of it. +Implement kernel module with API in sysfs which returns absolute time +of previous reading with maximum resolution like ‘400.123567’ seconds. diff --git a/05_timers/kernel_module/Makefile b/05_timers/kernel_module/Makefile new file mode 100644 index 0000000..a7fac31 --- /dev/null +++ b/05_timers/kernel_module/Makefile @@ -0,0 +1,9 @@ +KERNELDIR ?= ../output/build/linux-5.10.7 + +obj-m := time_attr_presenter.o + +all: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) clean diff --git a/05_timers/kernel_module/dump.txt b/05_timers/kernel_module/dump.txt new file mode 100644 index 0000000..bb7d3f0 --- /dev/null +++ b/05_timers/kernel_module/dump.txt @@ -0,0 +1,25 @@ +# insmod /tmp/time_attr_presenter.ko +# cat /sys/kernel/time_attr_presenter/last_abs_time +-1 +# cat /sys/kernel/time_attr_presenter/last_abs_time +1639337982.183260522 +# cat /sys/kernel/time_attr_presenter/last_abs_time +1639337984.166915503 +# cat /sys/kernel/time_attr_presenter/last_relative_time +-1 +# cat /sys/kernel/time_attr_presenter/last_relative_time +0.636000000 +# cat /sys/kernel/time_attr_presenter/last_relative_time +1.812000000 +# cat /sys/kernel/time_attr_presenter/last_relative_time +0.752000000 +# rmmod time_attr_presenter.ko +time_attr_presenter: Module exited +# ls /sys/kernel/time_attr_presenter +ls: /sys/kernel/time_attr_presenter: No such file or directory + +# unix timestamps are matched with real time +date -d @1639337984.166915503 +неділя, 12 грудня 2021 21:39:44 +0200 +date -d @1639337982.183260522 +неділя, 12 грудня 2021 21:39:42 +0200 diff --git a/05_timers/kernel_module/time_attr_presenter.c b/05_timers/kernel_module/time_attr_presenter.c new file mode 100644 index 0000000..abc0510 --- /dev/null +++ b/05_timers/kernel_module/time_attr_presenter.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * 1 Implement kernel module with API in sysfs, + * which returns relative time in maximum possible + * resolution passed since previous read of it. + * 2 Implement kernel module with API in sysfs + * which returns absolute time of previous reading + * with maximum resolution like ‘400.123567’ seconds. + */ + + +#define DEBUG +#define pr_fmt(fmt) "%s: " fmt, KBUILD_MODNAME +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static ssize_t time_attr_presenter_last_abs_time_show(struct kobject *kobj, struct kobj_attribute *attr, char *buff); +static ssize_t time_attr_presenter_last_relative_time_show(struct kobject *kobj, struct kobj_attribute *attr, char *buff); +static ssize_t time_attr_presenter_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buff, size_t count); +static int time_attr_presenter_init(void); +static void time_attr_presenter_exit(void); + + +static struct kobject *time_attr_presenter_kobj; +static u64 last_read_jiffies; +static struct timespec64 last_read_time; + +static struct kobj_attribute param_abs_attribute = + __ATTR(last_abs_time, 0644, time_attr_presenter_last_abs_time_show, time_attr_presenter_store); +static struct kobj_attribute param_relative_attribute = + __ATTR(last_relative_time, 0644, time_attr_presenter_last_relative_time_show, time_attr_presenter_store); + + +static ssize_t time_attr_presenter_last_abs_time_show(struct kobject *kobj, struct kobj_attribute *attr, char *buff) +{ + if (last_read_time.tv_sec == 0 && last_read_time.tv_nsec == 0) + sprintf(buff, "-1\n"); + else + sprintf(buff, "%lli.%li\n", last_read_time.tv_sec, last_read_time.tv_nsec); + + ktime_get_real_ts64(&last_read_time); + + return strlen(buff); +} + +static ssize_t time_attr_presenter_last_relative_time_show(struct kobject *kobj, struct kobj_attribute *attr, char *buff) +{ + u64 delta = 0; + struct timespec64 ts; + + if (last_read_jiffies == 0) { + sprintf(buff, "-1\n"); + } else { + delta = get_jiffies_64() - last_read_jiffies; + jiffies_to_timespec64(delta, &ts); + sprintf(buff, "%lli.%li\n", ts.tv_sec, ts.tv_nsec); + } + last_read_jiffies = get_jiffies_64(); + + return strlen(buff); +} + + +static ssize_t time_attr_presenter_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buff, size_t count) +{ + return count; +} + + +static int time_attr_presenter_init(void) +{ + int res = 0; + + time_attr_presenter_kobj = kobject_create_and_add("time_attr_presenter", kernel_kobj); + if (!time_attr_presenter_kobj) + return -ENOMEM; + res = sysfs_create_file(time_attr_presenter_kobj, ¶m_relative_attribute.attr); + if (res) + kobject_put(time_attr_presenter_kobj); + res = sysfs_create_file(time_attr_presenter_kobj, ¶m_abs_attribute.attr); + if (res) + kobject_put(time_attr_presenter_kobj); + + last_read_jiffies = 0; + last_read_time.tv_sec = 0; + last_read_time.tv_nsec = 0; + + return res; +} + + +static void time_attr_presenter_exit(void) +{ + kobject_put(time_attr_presenter_kobj); + pr_info("Module exited\n"); +} + +module_init(time_attr_presenter_init); +module_exit(time_attr_presenter_exit); + +MODULE_AUTHOR("Ivan Stepanenko "); +MODULE_DESCRIPTION("Time attribute presenter module"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1"); diff --git a/05_timers/user_space/Makefile b/05_timers/user_space/Makefile new file mode 100644 index 0000000..4142923 --- /dev/null +++ b/05_timers/user_space/Makefile @@ -0,0 +1,11 @@ +CC=gcc +CFLAGS=-I. -std=c99 -D_POSIX_C_SOURCE=199309L +OUT_NAME=user-space-timers + +all: $(OUT_NAME) + +$(OUT_NAME): user_space_timers.c + $(CC) user_space_timers.c -o $(OUT_NAME) $(CFLAGS) + +clean: + /bin/rm -rf *.o a.out $(OUT_NAME) diff --git a/05_timers/user_space/dump1.txt b/05_timers/user_space/dump1.txt new file mode 100644 index 0000000..fa0ef03 --- /dev/null +++ b/05_timers/user_space/dump1.txt @@ -0,0 +1,41 @@ +ivan@ivan-VirtualBox:~/buildroot/buildroot-2021.02.7/05-timers$ gcc -std=c99 -D_POSIX_C_SOURCE=199309L main.c && ./a.out ; echo $? +getting clock resolution... +---------------------------------------------------------------- +|clock_id |sec |nsec | +---------------------------------------------------------------- +|CLOCK_REALTIME |0 |1 | +|CLOCK_MONOTONIC |0 |1 | +|CLOCK_PROCESS_CPUTIM|0 |1 | +|CLOCK_THREAD_CPUTIME|0 |1 | +|CLOCK_BOOTTIME |0 |1 | + +getting actual clock values... +---------------------------------------------------------------- +|clock_id |sec |nsec | +---------------------------------------------------------------- +|CLOCK_REALTIME |1638701883 |825167534 | +|CLOCK_MONOTONIC |6539 |56162158 | +|CLOCK_PROCESS_CPUTIM|0 |625613 | +|CLOCK_THREAD_CPUTIME|0 |626629 | +|CLOCK_BOOTTIME |6539 |56164193 | + +Conclusion: +*1 I have started with clock resolutions for different clock_id. +In my case all of them have 1nsec resolution which is expected for the host system. + +*2 Then I have contnued with actual clock values. +CLOCK_REALTIME is matched with localtime + +ivan@ivan-VirtualBox:~/buildroot/buildroot-2021.02.7/05-timers$ date -d @1638701883.825167534 +неділя, 5 грудня 2021 12:58:03 +0200 + +CLOCK_MONOTONIC is coresponding with the system uptime (take into account time taken for this report writing). +6539/60=108 minutes +van@ivan-VirtualBox:~$ w + 13:08:06 up 1:59, 1 user, load average: 0,05, 0,07, 0,08 + +The difference is explained by the time spent on this report writing. + +CLOCK_PROCESS_CPUTIM and CLOCK_THREAD_CPUTIME are short and very close. In this task I do not expect another result. +CLOCK_BOOTTIME is mathched with CLOCK_MONOTONIC here, but also includes suspended time which I do not have for the moment. +have not managed to get difference between CLOCK_BOOTTIME and CLOCK_MONOTONIC diff --git a/05_timers/user_space/user_space_timers.c b/05_timers/user_space/user_space_timers.c new file mode 100644 index 0000000..7f23a39 --- /dev/null +++ b/05_timers/user_space/user_space_timers.c @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Implement program which return absolute time in user space. + * Use clock_gettime() from time.h. Try different clock id. Find the difference. + * Show possible clock resolution provided by clock_getres(). + */ + +#include +#include +#include +#include +#include + +int main(void) +{ + struct timespec real_ts, mono_ts, pcpu_ts, tcpu_ts, boot_ts; + struct timespec real_tp, mono_tp, pcpu_tp, tcpu_tp, boot_tp; + + printf("getting clock resolution...\n"); + if (clock_getres(CLOCK_REALTIME, &real_ts)) + perror(strerror(errno)); + if (clock_getres(CLOCK_MONOTONIC, &mono_ts)) + perror(strerror(errno)); + if (clock_getres(CLOCK_PROCESS_CPUTIME_ID, &pcpu_ts)) + perror(strerror(errno)); + if (clock_getres(CLOCK_THREAD_CPUTIME_ID, &tcpu_ts)) + perror(strerror(errno)); + if (clock_getres(CLOCK_BOOTTIME, &boot_ts)) + perror(strerror(errno)); + + printf("----------------------------------------------------------------\n"); + printf("|%-20.20s|%-20.20s|%-20.20s|\n", "clock_id", "sec", "nsec"); + printf("----------------------------------------------------------------\n"); + printf("|%-20.20s|%-20li|%-20li|\n", "CLOCK_REALTIME", real_ts.tv_sec, real_ts.tv_nsec); + printf("|%-20.20s|%-20li|%-20li|\n", "CLOCK_MONOTONIC", mono_ts.tv_sec, mono_ts.tv_nsec); + printf("|%-20.20s|%-20li|%-20li|\n", "CLOCK_PROCESS_CPUTIME_ID", pcpu_ts.tv_sec, pcpu_ts.tv_nsec); + printf("|%-20.20s|%-20li|%-20li|\n", "CLOCK_THREAD_CPUTIME_ID", tcpu_ts.tv_sec, tcpu_ts.tv_nsec); + printf("|%-20.20s|%-20li|%-20li|\n", "CLOCK_BOOTTIME", boot_ts.tv_sec, boot_ts.tv_nsec); + printf("\n"); + + printf("getting actual clock values...\n"); + if (clock_gettime(CLOCK_REALTIME, &real_tp)) + perror(strerror(errno)); + if (clock_gettime(CLOCK_MONOTONIC, &mono_tp)) + perror(strerror(errno)); + if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &pcpu_tp)) + perror(strerror(errno)); + if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tcpu_tp)) + perror(strerror(errno)); + if (clock_gettime(CLOCK_BOOTTIME, &boot_tp)) + perror(strerror(errno)); + + printf("----------------------------------------------------------------\n"); + printf("|%-20.20s|%-20.20s|%-20.20s|\n", "clock_id", "sec", "nsec"); + printf("----------------------------------------------------------------\n"); + printf("|%-20.20s|%-20li|%-20li|\n", "CLOCK_REALTIME", real_tp.tv_sec, real_tp.tv_nsec); + printf("|%-20.20s|%-20li|%-20li|\n", "CLOCK_MONOTONIC", mono_tp.tv_sec, mono_tp.tv_nsec); + printf("|%-20.20s|%-20li|%-20li|\n", "CLOCK_PROCESS_CPUTIME_ID", pcpu_tp.tv_sec, pcpu_tp.tv_nsec); + printf("|%-20.20s|%-20li|%-20li|\n", "CLOCK_THREAD_CPUTIME_ID", tcpu_tp.tv_sec, tcpu_tp.tv_nsec); + printf("|%-20.20s|%-20li|%-20li|\n", "CLOCK_BOOTTIME", boot_tp.tv_sec, boot_tp.tv_nsec); + printf("\n"); + + return 0; +} diff --git a/06_memory/README.md b/06_memory/README.md new file mode 100644 index 0000000..4c22613 --- /dev/null +++ b/06_memory/README.md @@ -0,0 +1,22 @@ +# Memory management + +## Homework +1. Create user-space C or C++ program which tries to allocate buffers + with sizes 2^x for x in range from 0 to maximium possible value + using functions: + **malloc, calloc, alloca, (optional for C++) new **. + Measure time of each allocation/freeing. + 2^x means x power of 2 in this task. +Pull request should contains program source code and program output +in text format. + +2. Create kernel module and test allocation/freeing time for functions: + **kmalloc, kzmalloc, vmalloc, get_free_pages, + (optional and only for drivers integrated to kernel)alloc_bootmem**. + Measure the time of each allocation/freeing except alloc_bootmem. + The results should be presented in text file table with followed columns: + Buffer size, allocation time, freeing time. + Size unit is 1 byte, time unit is 1 ns. + +Pull request should contains source code of developed driver, Makefile +and program output from system log in text format. diff --git a/07_procfs/Makefile b/07_procfs/Makefile new file mode 100644 index 0000000..d8a8ac9 --- /dev/null +++ b/07_procfs/Makefile @@ -0,0 +1,9 @@ +KERNELDIR ?= ../output/build/linux-5.10.10 + +obj-m := wimbledon.o + +all: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) clean diff --git a/07_procfs/README.md b/07_procfs/README.md new file mode 100644 index 0000000..7fa3528 --- /dev/null +++ b/07_procfs/README.md @@ -0,0 +1,10 @@ +## Lesson 07 - ProcFS interface and orange pi bootup + +* Update your existing sysfs kernel module with procfs API: +* Create folder in procfs file system; +* Create entry that returns module author name; +* Create entry that returns amount of "store" callback calls; +* Create entry that returns amount of "show" callback calls. +* Build image for orange pi zero +* Attach console output from your development board + diff --git a/07_procfs/report.txt b/07_procfs/report.txt new file mode 100644 index 0000000..991775b --- /dev/null +++ b/07_procfs/report.txt @@ -0,0 +1,45 @@ +$ insmod wimbledon.ko +$ echo "a1" >> /sys/kernel/wimbledon/list +$ cat /proc/wimbeldon/nshow +0 +$ cat /proc/wimbeldon/nstore +1 +$ cat /proc/wimbeldon/auth_name +Ivan Stepanenko +$ cat /proc/wimbeldon/nshow +0 +$ cat /sys/kernel/wimbledon/list +a1 +$ cat /proc/wimbeldon/nshow +1 +$ echo "b2" >> /sys/kernel/wimbledon/list +$ cat /proc/wimbeldon/nstore +2 +$ cat /sys/kernel/wimbledon/list +a1 +b2 +$ cat /proc/wimbeldon/nshow +2 +$ uname -a +Linux orange 5.10.10 #1 SMP Mon Dec 13 18:03:39 EET 2021 armv7l GNU/Linux +$ rmmod wimbledon.ko + +$ dmesg +[ 3395.465083] wimbledon: adding msg size: 3 to the list: 'a1 +[ 3395.465083] ' +[ 3437.850684] wimbledon: msg #0 size: 3 'a1 +[ 3437.850684] ' +[ 3455.577961] wimbledon: adding msg size: 3 to the list: 'b2 +[ 3455.577961] ' +[ 3468.681388] wimbledon: msg #0 size: 3 'a1 +[ 3468.681388] ' +[ 3468.686988] wimbledon: msg #1 size: 3 'b2 +[ 3468.686988] ' +[ 3498.512991] wimbledon: freeing msg size: 3 from the list: 'b2 +[ 3498.512991] ' +[ 3498.520373] wimbledon: freeing msg size: 3 from the list: 'a1 +[ 3498.520373] ' +[ 3498.527719] wimbledon: list is empty: 1 +[ 3498.531576] wimbledon: removing procfs entries +[ 3498.536034] wimbledon: Module exited +$ diff --git a/07_procfs/wimbledon.c b/07_procfs/wimbledon.c new file mode 100644 index 0000000..e1b1325 --- /dev/null +++ b/07_procfs/wimbledon.c @@ -0,0 +1,254 @@ +#define DEBUG +#define pr_fmt(fmt) "%s: " fmt, KBUILD_MODNAME +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AUTHOR "Ivan Stepanenko " + +#define PROCFS_BUFF_SIZE 64 +#define PROCFS_DIR "wimbeldon" +#define PROCFS_FN_NSHOW "nshow" +#define PROCFS_FN_NSTORE "nstore" +#define PROCFS_FN_AUTH_NAME "auth_name" + +struct msg_item { + struct list_head list; + char *msg; + size_t msg_len; +}; +static LIST_HEAD(msg_list_head); + +static unsigned int show_cb_counter = 0; +static unsigned int store_cb_counter = 0; + + +static ssize_t wimbledon_show(struct kobject *kobj, struct kobj_attribute *attr, char *buff) +{ + struct msg_item *pos = NULL; + size_t item_number = 0; + size_t count = 0; + char *p_buff = buff; + + show_cb_counter += 1; + + list_for_each_entry_reverse(pos, &msg_list_head, list) { + pr_info("msg #%u size: %u '%.*s'\n", item_number, pos->msg_len, (int)pos->msg_len, (char *)pos->msg); + + memcpy(p_buff, (char *)pos->msg, pos->msg_len); + p_buff += pos->msg_len; + count += pos->msg_len; + item_number += 1; + } + + return count; +} + + +static ssize_t wimbledon_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buff, size_t count) +{ + char *p_msg = NULL; + struct msg_item *p_item = NULL; + + store_cb_counter += 1; + + p_msg = kmalloc(count, GFP_KERNEL); + if (!p_msg) { + pr_err("could not allocate msg cnt: %u\n", count); + return -ENOMEM; + } + p_item = kmalloc(sizeof(struct msg_item), GFP_KERNEL); + if (!p_item) { + pr_err("could not allocate msg_item\n"); + return -ENOMEM; + } + memcpy(p_msg, buff, count); + p_item->msg = p_msg; + p_item->msg_len = count; + + pr_info("adding msg size: %u to the list: '%.*s'\n", p_item->msg_len, (int)p_item->msg_len, (char *)p_item->msg); + list_add(&p_item->list, &msg_list_head); + return count; +} + +/* procfs operations */ + +static struct proc_dir_entry *procfs_root_entry, *procfs_nshow_entry, *procfs_nstore_entry, *procfs_auth_name_entry; + + +static ssize_t procfs_op_nshow_read(struct file *file, char __user *p_ubuf, size_t count, loff_t *ppos) +{ + char msg_buf[PROCFS_BUFF_SIZE]; + int len = 0; + + if(*ppos > 0) + return 0; + + len = snprintf(NULL, 0, "%u\n", show_cb_counter); + if (len < 0 || len >= sizeof(msg_buf)) { + pr_err("msg len: %i doesn't fit buffer: %i\n", len, sizeof(msg_buf)); + return -ENOMEM; + } + + snprintf(msg_buf, len+1, "%u\n", show_cb_counter); + + if (copy_to_user(p_ubuf, msg_buf, len)) { + pr_err("can't copy msg size: %i to user space\n", len); + return -EFAULT; + } + + *ppos = len; + + return len; +} + + +static ssize_t procfs_op_nstore_read(struct file *file, char __user *p_ubuf, size_t count, loff_t *ppos) +{ + char msg_buf[PROCFS_BUFF_SIZE]; + int len = 0; + + if(*ppos > 0) + return 0; + + len = snprintf(NULL, 0, "%u\n", store_cb_counter); + if (len < 0 || len >= sizeof(msg_buf)) { + pr_err("msg len: %i doesn't fit buffer: %i\n", len, sizeof(msg_buf)); + return -ENOMEM; + } + snprintf(msg_buf, len+1, "%u\n", store_cb_counter); + + if (copy_to_user(p_ubuf, msg_buf, len)) { + pr_err("can't copy msg size: %i to user space\n", len); + return -EFAULT; + } + + *ppos = len; + + return len; +} + + +static ssize_t procfs_op_auth_name_read(struct file *file, char __user *p_ubuf, size_t count, loff_t *ppos) +{ + char msg_buf[sizeof(AUTHOR)+1]; + int len = 0; + + if(*ppos > 0) + return 0; + + len = snprintf(NULL, 0, "%s\n", AUTHOR); + if (len < 0 || len >= sizeof(msg_buf)) { + pr_err("msg len: %i doesn't fit buffer: %i\n", len, sizeof(msg_buf)); + return -ENOMEM; + } + + snprintf(msg_buf, len+1, "%s\n", AUTHOR); + + if (copy_to_user(p_ubuf, msg_buf, len)) { + pr_err("can't copy msg size: %i to user space\n", len); + return -EFAULT; + } + + *ppos = len; + + return len; +} + + +static struct proc_ops procfs_nshow_ops = +{ + .proc_read = procfs_op_nshow_read +}; + +static struct proc_ops procfs_nstore_ops = +{ + .proc_read= procfs_op_nstore_read +}; + +static struct proc_ops procfs_auth_name_ops = +{ + .proc_read= procfs_op_auth_name_read +}; + +/* end procsfs operations */ + +static struct kobj_attribute list_attribute = + __ATTR(list, 0644, wimbledon_show, wimbledon_store); + +static struct kobject *wimbledon_kobj; + +static int wimbledon_init(void) +{ + int res = 0; + wimbledon_kobj = kobject_create_and_add("wimbledon", kernel_kobj); + if (!wimbledon_kobj) + return -ENOMEM; + res = sysfs_create_file(wimbledon_kobj, &list_attribute.attr); + if (res) + kobject_put(wimbledon_kobj); + + procfs_root_entry = proc_mkdir(PROCFS_DIR, NULL); + if (procfs_root_entry == NULL) { + pr_err("error creating procfs: %s entry\n", PROCFS_DIR); + return -ENOMEM; + } + procfs_nshow_entry = proc_create(PROCFS_FN_NSHOW, 0444, procfs_root_entry, &procfs_nshow_ops); + if (procfs_nshow_entry == NULL) { + pr_err("error creating procfs: %s entry\n", PROCFS_FN_NSHOW); + return -ENOMEM; + } + procfs_nstore_entry = proc_create(PROCFS_FN_NSTORE, 0444, procfs_root_entry, &procfs_nstore_ops); + if (procfs_nstore_entry == NULL) { + pr_err("error creating procfs: %s entry\n", PROCFS_FN_NSTORE); + return -ENOMEM; + } + procfs_auth_name_entry = proc_create(PROCFS_FN_AUTH_NAME, 0444, procfs_root_entry, &procfs_auth_name_ops); + if (procfs_nstore_entry == NULL) { + pr_err("error creating procfs: %s entry\n", PROCFS_FN_AUTH_NAME); + return -ENOMEM; + } + + return res; +} + +static void wimbledon_exit(void) +{ + struct list_head *pos = NULL; + struct list_head *n = NULL; + struct msg_item *p_item = NULL; + + list_for_each_safe(pos, n, &msg_list_head) { + p_item = list_entry(pos, struct msg_item, list); + pr_info("freeing msg size: %u from the list: '%.*s'\n", p_item->msg_len, (int)p_item->msg_len, (char *)p_item->msg); + list_del(pos); + kfree(p_item->msg); + kfree(p_item); + } + pr_info("list is empty: %d\n", list_empty_careful(&msg_list_head)); + kobject_put(wimbledon_kobj); + + pr_info("removing procfs entries\n"); + proc_remove(procfs_auth_name_entry); + proc_remove(procfs_nstore_entry); + proc_remove(procfs_nshow_entry); + proc_remove(procfs_root_entry); + + pr_info("Module exited\n"); +} + +module_init(wimbledon_init); +module_exit(wimbledon_exit); + +MODULE_AUTHOR(AUTHOR); +MODULE_DESCRIPTION("Shows num of store/show callbacks, stores data in the list items."); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1");