├── .gitignore ├── README.md ├── lecture_examples ├── 10_users │ ├── 1_useradd.sh │ ├── 2_not_existing_chown.sh │ ├── 3_group.sh │ ├── 4_setuid.c │ ├── 5_seteuid.c │ ├── 6_access_mask.c │ ├── 7_umask.c │ ├── 8_pgrp.c │ ├── 9_client.c │ └── 9_daemon.c ├── 2_processes │ ├── 10_5_pipe_close.c │ ├── 10_pipe.c │ ├── 11_advanced_pipe.c │ ├── 12_exec.c │ ├── 13_core_dump.c │ ├── 1_5_exit.c │ ├── 1_createprocess_win.c │ ├── 1_fork.c │ ├── 2_proc_memory.c │ ├── 3_fs_proc.c │ ├── 4_shared_mem.c │ ├── 5_argv_env.c │ ├── 6_stdout.c │ ├── 7_basic_append.c │ ├── 8_dup.c │ └── 9_fork_dup.c ├── 3_memory │ ├── 1_compact_struct.c │ └── 2_catch_sigsegv.c ├── 4_signals │ ├── 10_sigaltstack.c │ ├── 11_setjmp_problem.c │ ├── 12_libcoro.c │ ├── 12_libcoro.h │ ├── 12_libcoro_example.c │ ├── 1_basic_signal.c │ ├── 2_pause_hangs.c │ ├── 3_intr_malloc.c │ ├── 4_sigprocmask.c │ ├── 5_sigaction_info.c │ ├── 6_sigaction_mask.c │ ├── 7_sigaction_jmp.c │ ├── 8_sigaction_sigjmp.c │ └── 9_sigsuspend.c ├── 5_files │ ├── 1_dirent.c │ └── 2_fstat.c ├── 6_threads │ ├── 10_cthreads_1.c │ ├── 10_cthreads_2.c │ ├── 11_cthread_bad_detach.c │ ├── 12_cthread_detach.c │ ├── 13_simple_condvar.c │ ├── 14_condvar.c │ ├── 15_gcc_thread.c │ ├── 16_pthread_key.c │ ├── 17_thread_stacks.c │ ├── 1_clone.c │ ├── 2_non_volatile.c │ ├── 3_volatile.c │ ├── 4_not_atomic.c │ ├── 5_atomic.c │ ├── 6_bad_lock.c │ ├── 7_spin_lock.c │ ├── 8_5_cpu_reordering.c │ ├── 8_5_random_generator.h │ ├── 8_6_spinlock_acq_rel.c │ ├── 8_futex.c │ ├── 9_clone_vs_pthread.c │ └── clone.h ├── 7_ipc │ ├── 10_sem_mutex.h │ ├── 11_shm.c │ ├── 12_sem_posix.c │ ├── 13_socketpair.c │ ├── 14_sock_dgram.c │ ├── 15_sock_client.c │ ├── 15_sock_server.c │ ├── 1_simple_sort.c │ ├── 2_parallel_sort.c │ ├── 3_mem_sort.c │ ├── 4_fifo_client.c │ ├── 4_fifo_server.c │ ├── 5_key_t.c │ ├── 6_msgget.c │ ├── 7_msgsend_client.c │ ├── 7_msgsend_server.c │ ├── 7_msgsend_server_flag.c │ ├── 8_sem.c │ ├── 9_sem_event_cons.c │ └── 9_sem_event_source.c ├── 8_net │ ├── 1_socket_protocol.c │ ├── 2_getaddrinfo.c │ ├── 3_client.c │ └── 3_server.c └── 9_aio │ ├── 1_nonblock.c │ ├── 2_flock.c │ ├── 3_fcntl_lock.c │ ├── 4_client.c │ ├── 4_server_select.c │ ├── 5_server_poll.c │ ├── 6_server_kqueue.c │ └── 7_server_epoll.c ├── lectures ├── 0. Вводная лекция.pptx ├── 1. Планировщики процессов.pptx ├── 10. Пользователи, группы, права. Атрибуты и права доступа файлов и процессов.pptx ├── 2. Процесс. Режимы работы, память, ресурсы. Прерывания. Взаимодействие с ядром.pptx ├── 3. Память. Виртуальная и физическая. Уровни кэша, кэш линия.pptx ├── 4. Сигналы. Аппаратные и программные прерывания, их природа. Top and bottom halves. Сигналы и системные вызовы, контекст сигнала.pptx ├── 5. Файловая система.pptx ├── 6. Потоки. Отличие от процессов. Атомарные операции.pptx ├── 7. IPC. Pipe, FIFO. XSI и POSIX. Сокеты.pptx ├── 8. Сеть. Модели TCPIP OSI. Связь с ядром. Интерфейсы и примеры.pptx └── 9. Неблокирующие IO операции. Мультиплексирование.pptx └── tasks ├── 1 ├── checker.py ├── coro_jmp.h ├── example_jmp.c ├── example_swap.c ├── generator.py ├── task_eng.txt └── task_rus.txt ├── 2 ├── Makefile ├── checker.py ├── result15.txt ├── result20.txt ├── result25.txt ├── task_eng.txt ├── task_rus.txt └── tests.txt ├── 3 ├── Makefile ├── task3.txt ├── test.c ├── userfs.c └── userfs.h ├── 4 ├── Makefile ├── task.txt ├── test.c ├── thread_pool.c └── thread_pool.h ├── examples ├── coro_alloca.c └── reuse_port_addr │ ├── README.md │ ├── client.c │ └── server.c └── utils └── unit.h /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | lectures/~$*.pptx 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Practical examples and homeworks for "System Programming" course of lectures. 2 | 3 | Derived from: https://slides.com/gerold103/decks/sysprog 4 | -------------------------------------------------------------------------------- /lecture_examples/10_users/1_useradd.sh: -------------------------------------------------------------------------------- 1 | # Get root role. 2 | sudo su 3 | 4 | # Create a user and check that it worked. 5 | useradd -m -c "comment" sysproguser 6 | tail -n 1 /etc/passwd 7 | tail -n 1 /etc/shadow 8 | 9 | # Change password of a new user. 10 | passwd sysproguser 11 | su vladislav # your original user, before executing this script. 12 | su sysproguser 13 | 14 | # Delete the user 15 | exit 16 | userdel -r sysproguser 17 | # If this command says that there are working processes, then 18 | # kill them and repeat the command. 19 | -------------------------------------------------------------------------------- /lecture_examples/10_users/2_not_existing_chown.sh: -------------------------------------------------------------------------------- 1 | touch test.txt 2 | ls -l 3 | # User with id 2000 shall not exist. 4 | chown 2000 test.txt 5 | ls -l 6 | -------------------------------------------------------------------------------- /lecture_examples/10_users/3_group.sh: -------------------------------------------------------------------------------- 1 | # Create a new grop. 2 | groupadd testgroup 3 | tail -n 1 /etc/group 4 | 5 | # Fill the group. 6 | useradd -m sysproguser 7 | useradd -m sysproguser2 8 | usermod -aG testgroup sysproguser 9 | usermod -aG testgroup sysproguser2 10 | 11 | tail -n 3 /etc/group 12 | 13 | # Cleanup. 14 | groupdel testgroup 15 | tail -n 3 /etc/group 16 | userdel -r sysproguser 17 | userdel -r sysproguser2 18 | -------------------------------------------------------------------------------- /lecture_examples/10_users/4_setuid.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | 5 | int 6 | main() 7 | { 8 | uid_t uid, euid, suid; 9 | getresuid(&uid, &euid, &suid); 10 | printf("uid = %d, euid = %d, suid = %d\n", (int) uid, (int) euid, 11 | (int) suid); 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /lecture_examples/10_users/5_seteuid.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | uid_t uid, euid, suid; 9 | 10 | void 11 | show_uids() 12 | { 13 | getresuid(&uid, &euid, &suid); 14 | printf("uid = %d, euid = %d, suid = %d\n", (int) uid, (int) euid, 15 | (int) suid); 16 | } 17 | 18 | int 19 | main(int argc, const char **argv) 20 | { 21 | uid_t new_uid1 = atoi(argv[1]); 22 | uid_t new_uid2 = atoi(argv[2]); 23 | uid_t new_uid3 = atoi(argv[3]); 24 | show_uids(); 25 | printf("Set EUID to %d\n", (int) new_uid1); 26 | if (seteuid(new_uid1) == -1) 27 | printf("error = %s\n", strerror(errno)); 28 | 29 | show_uids(); 30 | printf("Set EUID to %d\n", (int) new_uid2); 31 | if (seteuid(new_uid2) == -1) 32 | printf("error = %s\n", strerror(errno)); 33 | 34 | show_uids(); 35 | printf("Set UID to %d\n", (int) new_uid3); 36 | if (setuid(new_uid3) == -1) 37 | printf("error = %s\n", strerror(errno)); 38 | 39 | show_uids(); 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /lecture_examples/10_users/6_access_mask.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int 8 | main() 9 | { 10 | mode_t mode = S_IRUSR | S_IXUSR; 11 | int fd = open("tmp.txt", O_CREAT | O_RDWR, mode); 12 | if (fd == -1) 13 | printf("error = %s\n", strerror(errno)); 14 | else 15 | close(fd); 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /lecture_examples/10_users/7_umask.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int 8 | main() 9 | { 10 | mode_t mode = S_IRWXU | S_IRWXO | S_IRWXG; 11 | int fd = open("tmp.txt", O_CREAT | O_RDWR, mode); 12 | if (fd == -1) 13 | printf("error = %s\n", strerror(errno)); 14 | else 15 | close(fd); 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /lecture_examples/10_users/8_pgrp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int 5 | main(int argc, const char **argv) 6 | { 7 | pid_t id = getpgrp(); 8 | pid_t pid = getpid(); 9 | int i = 0; 10 | if (argv[1][0] == 'w') 11 | printf("1\n"); 12 | else 13 | scanf("%d", &i); 14 | fprintf(stderr, "group = %d, pid = %d\n", 15 | (int) id, (int) pid); 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /lecture_examples/10_users/9_client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | int 14 | main(int argc, const char **argv) 15 | { 16 | int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 17 | if (sock == -1) { 18 | printf("error = %s\n", strerror(errno)); 19 | return -1; 20 | } 21 | struct sockaddr_in addr; 22 | addr.sin_family = AF_INET; 23 | addr.sin_port = htons(12345); 24 | inet_aton("127.0.0.1", &addr.sin_addr); 25 | if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) != 0) { 26 | printf("connect error = %s\n", strerror(errno)); 27 | close(sock); 28 | return -1; 29 | } 30 | int number; 31 | while (scanf("%d", &number) > 0) { 32 | if (write(sock, &number, sizeof(number)) == -1) { 33 | printf("error = %s\n", strerror(errno)); 34 | continue; 35 | } 36 | printf("Sent %d\n", number); 37 | number = 0; 38 | int rc = read(sock, &number, sizeof(number)); 39 | if (rc == 0) { 40 | printf("Closed connection\n"); 41 | break; 42 | } 43 | if (rc == -1) 44 | printf("error = %s\n", strerror(errno)); 45 | else 46 | printf("Received %d\n", number); 47 | } 48 | close(sock); 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /lecture_examples/10_users/9_daemon.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | int 17 | interact(int client_sock) 18 | { 19 | int buffer = 0; 20 | ssize_t size = read(client_sock, &buffer, sizeof(buffer)); 21 | if (size <= 0) 22 | return (int) size; 23 | printf("Received %d\n", buffer); 24 | buffer++; 25 | size = write(client_sock, &buffer, sizeof(buffer)); 26 | if (size > 0) 27 | printf("Sent %d\n", buffer); 28 | return (int) size; 29 | } 30 | 31 | void 32 | remove_client(struct pollfd **fds, int *fd_count, int i) 33 | { 34 | --*fd_count; 35 | struct pollfd *new_fds = malloc(*fd_count * sizeof(new_fds[0])); 36 | memcpy(new_fds, *fds, i * sizeof(new_fds[0])); 37 | memcpy(new_fds + i, *fds + i + 1, 38 | (*fd_count - i) * sizeof(new_fds[0])); 39 | free(*fds); 40 | *fds = new_fds; 41 | } 42 | 43 | int 44 | daemonize(const char *log_file) 45 | { 46 | if (fork() > 0) 47 | exit(0); 48 | int fd = open(log_file, O_CREAT | O_WRONLY | O_TRUNC, S_IRWXU); 49 | if (fd == -1) { 50 | printf("open error\n"); 51 | return -1; 52 | } 53 | int rc = dup2(fd, STDOUT_FILENO); 54 | close(fd); 55 | if (rc == -1) { 56 | printf("dup error\n"); 57 | return -1; 58 | } 59 | close(STDIN_FILENO); 60 | close(STDERR_FILENO); 61 | return setsid(); 62 | } 63 | 64 | int 65 | main(int argc, const char **argv) 66 | { 67 | if (daemonize(argv[1]) == -1) { 68 | printf("error 1 = %s\n", strerror(errno)); 69 | return -1; 70 | } 71 | int server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 72 | if (server == -1) { 73 | printf("error 2 = %s\n", strerror(errno)); 74 | return -1; 75 | } 76 | struct sockaddr_in addr; 77 | addr.sin_family = AF_INET; 78 | addr.sin_port = htons(12345); 79 | inet_aton("127.0.0.1", &addr.sin_addr); 80 | 81 | if (bind(server, (struct sockaddr *) &addr, sizeof(addr)) != 0) { 82 | printf("bind error = %s\n", strerror(errno)); 83 | return -1; 84 | } 85 | if (listen(server, 128) == -1) { 86 | printf("listen error = %s\n", strerror(errno)); 87 | return -1; 88 | } 89 | struct pollfd *fds = malloc(sizeof(fds[0])); 90 | int fd_count = 1; 91 | fds[0].fd = server; 92 | fds[0].events = POLLIN; 93 | while(1) { 94 | fflush(stdout); 95 | int nfds = poll(fds, fd_count, 2000); 96 | if (nfds == 0) { 97 | printf("Timeout\n"); 98 | continue; 99 | } 100 | if (nfds == -1) { 101 | printf("error 3 = %s\n", strerror(errno)); 102 | break; 103 | } 104 | if ((fds[0].revents & POLLIN) != 0) { 105 | int client_sock = accept(server, NULL, NULL); 106 | if (client_sock == -1) { 107 | printf("error 4 = %s\n", strerror(errno)); 108 | break; 109 | } 110 | printf("New client\n"); 111 | fd_count++; 112 | fds = realloc(fds, fd_count * sizeof(fds[0])); 113 | fds[fd_count - 1].fd = client_sock; 114 | fds[fd_count - 1].events = POLLIN; 115 | nfds--; 116 | } 117 | for (int i = 0; i < fd_count && nfds > 0; ++i) { 118 | if ((fds[i].revents & POLLIN) == 0) 119 | continue; 120 | nfds--; 121 | printf("Interact with fd %d\n", fds[i].fd); 122 | int rc = interact(fds[i].fd); 123 | if (rc == -1) { 124 | printf("error 5 = %s\n", strerror(errno)); 125 | break; 126 | } 127 | if (rc == 0) { 128 | printf("Client disconnected\n"); 129 | remove_client(&fds, &fd_count, i); 130 | } 131 | } 132 | } 133 | for (int i = 0; i < fd_count; ++i) 134 | close(fds[i].fd); 135 | free(fds); 136 | close(server); 137 | return 0; 138 | } 139 | -------------------------------------------------------------------------------- /lecture_examples/2_processes/10_5_pipe_close.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int 8 | main(int argc, const char **argv) 9 | { 10 | int channel[2]; 11 | int need_close = argc > 1 && strcmp(argv[1], "close") == 0; 12 | pipe(channel); 13 | if (fork() == 0) { 14 | char buf[16]; 15 | printf("child: started\n"); 16 | if (need_close) { 17 | printf("child: close output channel\n"); 18 | close(channel[1]); 19 | } 20 | while (read(channel[0], buf, sizeof(buf)) > 0) 21 | printf("child: read %s\n", buf); 22 | printf("child: EOF\n"); 23 | return 0; 24 | } 25 | write(channel[1], "100", 3); 26 | printf("parent: written 100\n"); 27 | if (need_close) { 28 | printf("parent: close output channel\n"); 29 | close(channel[1]); 30 | } 31 | printf("parent: waiting for child termination ...\n"); 32 | wait(NULL); 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /lecture_examples/2_processes/10_pipe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | int to_parent[2]; 9 | int to_child[2]; 10 | pipe(to_child); 11 | pipe(to_parent); 12 | char buf[16]; 13 | if (fork() == 0) { 14 | close(to_parent[0]); 15 | close(to_child[1]); 16 | read(to_child[0], buf, sizeof(buf)); 17 | printf("%d: read %s\n", (int) getpid(), buf); 18 | write(to_parent[1], "hello2", sizeof("hello2")); 19 | return 0; 20 | } 21 | close(to_parent[1]); 22 | close(to_child[0]); 23 | write(to_child[1], "hello1", sizeof("hello")); 24 | read(to_parent[0], buf, sizeof(buf)); 25 | printf("%d: read %s\n", (int) getpid(), buf); 26 | wait(NULL); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /lecture_examples/2_processes/11_advanced_pipe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | int to_child[2]; 9 | pipe(to_child); 10 | char buf[16]; 11 | dup2(to_child[0], 0); 12 | if (fork() == 0) { 13 | close(to_child[1]); 14 | int n; 15 | scanf("%d", &n); 16 | printf("%d: read %d\n", (int) getpid(), n); 17 | return 0; 18 | } 19 | close(to_child[0]); 20 | write(to_child[1], "100", sizeof("100")); 21 | wait(NULL); 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /lecture_examples/2_processes/12_exec.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | int to_child[2]; 9 | pipe(to_child); 10 | char buf[16]; 11 | dup2(to_child[0], 0); 12 | if (fork() == 0) { 13 | close(to_child[1]); 14 | return execlp("python3", "python3", "-i", NULL); 15 | } 16 | close(to_child[0]); 17 | const char cmd[] = "print(100 + 200)"; 18 | write(to_child[1], cmd, sizeof(cmd)); 19 | close(to_child[1]); 20 | wait(NULL); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /lecture_examples/2_processes/13_core_dump.c: -------------------------------------------------------------------------------- 1 | int main() 2 | { 3 | int a = 1; 4 | int b = 2; 5 | char buffer[100]; 6 | buffer[0] = 3; 7 | buffer[50] = 4; 8 | buffer[10000] = 5; 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /lecture_examples/2_processes/1_5_exit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void 5 | _start() 6 | { 7 | printf("hello, world\n"); 8 | exit(0); 9 | } 10 | -------------------------------------------------------------------------------- /lecture_examples/2_processes/1_createprocess_win.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main(int argc, char **argv) 9 | { 10 | int my_pid = GetCurrentProcessId(); 11 | if (argc != 1) { 12 | printf("I am child %d\n", my_pid); 13 | return 100; 14 | } 15 | STARTUPINFO si; 16 | PROCESS_INFORMATION pi; 17 | memset(&si, 0, sizeof(si)); 18 | si.cb = sizeof(si); 19 | memset(&pi, 0, sizeof(pi)); 20 | char cmd[128]; 21 | sprintf(cmd, "%s child", argv[0]); 22 | 23 | if (CreateProcess(NULL, cmd, NULL, NULL, false, 0, NULL, NULL, 24 | &si, &pi) == 0) { 25 | printf("error = %d\n", (int) GetLastError()); 26 | exit(-1); 27 | } 28 | printf("I am parent %d, child's pid is %d\n", my_pid, 29 | (int) pi.dwProcessId); 30 | WaitForSingleObject(pi.hProcess, INFINITE); 31 | DWORD exit_code; 32 | GetExitCodeProcess(pi.hProcess, &exit_code); 33 | printf("Child exit code %d\n", (int) exit_code); 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /lecture_examples/2_processes/1_fork.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int main() 8 | { 9 | printf("I am process %d\n", (int) getpid()); 10 | char *mem = (char *) calloc(1, 100); 11 | pid_t child_pid = fork(); 12 | pid_t my_pid = getpid(); 13 | if (child_pid == 0) { 14 | printf("%d: I am child, fork returned %d\n", 15 | (int) my_pid, (int) child_pid); 16 | printf("%d: child is terminated with code 100\n", 17 | (int) my_pid); 18 | printf("%d: memory values are set to 1\n", (int) my_pid); 19 | memset(mem, 1, 100); 20 | return 100; 21 | } 22 | printf("%d: I am parent, fork returned %d\n", 23 | (int) my_pid, (int) child_pid); 24 | int stat; 25 | pid_t wait_result = wait(&stat); 26 | printf("%d: wait returned %d and stat %d\n", (int) my_pid, 27 | (int) wait_result, stat); 28 | printf("%d: memory values are %d\n", (int) my_pid, (int) mem[0]); 29 | printf("%d: returned child code was %d\n", (int) my_pid, 30 | WEXITSTATUS(stat)); 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /lecture_examples/2_processes/2_proc_memory.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int uninitialized; 5 | 6 | const char *str = "const char *str"; 7 | const char str2[] = "const char str2[]"; 8 | 9 | void 10 | another_function(void) 11 | { 12 | char array[128]; 13 | memset(array, 0, sizeof(array)); 14 | printf("called another function, stack is %p\n", array); 15 | } 16 | 17 | void 18 | test_stack(void) 19 | { 20 | int a; 21 | printf("stack top in test_stack: %p\n", &a); 22 | const char *str3 = "const char *str3"; 23 | const char str4[] = "const char str4[]"; 24 | char str5[] = "char str5[]"; 25 | char b = 'x'; 26 | char c = 'x'; 27 | char d = 'x'; 28 | int e = 32; 29 | int f = 64; 30 | int g = 128; 31 | printf("a = %d\n", a); 32 | a = 10; 33 | } 34 | 35 | int 36 | main(void) 37 | { 38 | int a = 20; 39 | printf("stack top in main: %p\n", &a); 40 | test_stack(); 41 | test_stack(); 42 | another_function(); 43 | test_stack(); 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /lecture_examples/2_processes/3_fs_proc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int main() 10 | { 11 | int fd_me = open("3_fs_proc.c", O_RDONLY); 12 | char *shared_mem = (char *) mmap(NULL, 100, PROT_READ, 13 | MAP_FILE | MAP_SHARED, fd_me, 0); 14 | char buf[128]; 15 | 16 | sprintf(buf, "/proc/%d/maps", (int) getpid()); 17 | int fd = open(buf, O_RDONLY); 18 | printf("print %s\n", buf); 19 | if (fd == -1) { 20 | printf("exit %s\n", strerror(errno)); 21 | exit(1); 22 | } 23 | int nbyte; 24 | while ((nbyte = read(fd, buf, sizeof(buf))) > 0) 25 | printf("%.*s", nbyte, buf); 26 | printf("\n"); 27 | close(fd); 28 | munmap(shared_mem, 100); 29 | close(fd_me); 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /lecture_examples/2_processes/4_shared_mem.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | char *shared_mem = (char *) mmap(NULL, 100, PROT_READ | PROT_WRITE, 9 | MAP_ANON | MAP_SHARED, -1, 0); 10 | char *private_mem = (char *) malloc(100); 11 | shared_mem[0] = 55; 12 | private_mem[0] = 55; 13 | if (fork() == 0) { 14 | shared_mem[0] = 56; 15 | private_mem[0] = 56; 16 | goto exit; 17 | } 18 | wait(NULL); 19 | printf("shared: %d, private: %d\n", (int) shared_mem[0], 20 | (int) private_mem[0]); 21 | exit: 22 | munmap(shared_mem, 100); 23 | free(private_mem); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /lecture_examples/2_processes/5_argv_env.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char **argv, char **env) 5 | { 6 | char *path = getenv("PATH"); 7 | printf("env: %p\n", path); 8 | printf("argv: %p\n", argv); 9 | int a; 10 | printf("stack: %p\n", &a); 11 | void *m = malloc(100); 12 | printf("heap: %p\n", m); 13 | free(m); 14 | 15 | int i = 0; 16 | while (env[i] != NULL) 17 | printf("%s\n", env[i++]); 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /lecture_examples/2_processes/6_stdout.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() 5 | { 6 | char buf[] = "write to 1\n"; 7 | write(STDOUT_FILENO, buf, sizeof(buf)); 8 | printf("stdout fileno = %d\n", STDOUT_FILENO); 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /lecture_examples/2_processes/7_basic_append.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() 6 | { 7 | int fd = open("tmp.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); 8 | pid_t pid = getpid(); 9 | printf("press any key to write my pid %d\n", (int) pid); 10 | getchar(); 11 | 12 | dprintf(fd, "%d ", (int) pid); 13 | close(fd); 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /lecture_examples/2_processes/8_dup.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() 6 | { 7 | int fd = open("tmp.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); 8 | int fd2 = dup(fd); 9 | dprintf(fd2, "1 "); 10 | close(fd2); 11 | dprintf(fd, "2 "); 12 | close(fd); 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /lecture_examples/2_processes/9_fork_dup.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | int fd = open("tmp.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); 9 | int fd2 = dup(fd); 10 | if (fork() == 0) { 11 | close(fd); 12 | dprintf(fd2, "%d ", (int) getpid()); 13 | close(fd2); 14 | return 0; 15 | } 16 | close(fd2); 17 | wait(NULL); 18 | dprintf(fd, "%d ", (int) getpid()); 19 | close(fd); 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /lecture_examples/3_memory/1_compact_struct.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct complex_struct { 4 | int id; 5 | double a; 6 | long d; 7 | char buf[10]; 8 | char *long_buf; 9 | }; 10 | 11 | struct complex_struct * 12 | complex_struct_bad_new(int long_buf_len) 13 | { 14 | struct complex_struct *ret = 15 | (struct complex_struct *) malloc(sizeof(*ret)); 16 | ret->long_buf = (char *) malloc(long_buf_len); 17 | return ret; 18 | } 19 | 20 | struct complex_struct * 21 | complex_struct_good_new(int long_buf_len) 22 | { 23 | struct complex_struct *ret; 24 | int size = sizeof(*ret) + long_buf_len; 25 | ret = (struct complex_struct *) malloc(size); 26 | ret->long_buf = (char *) ret + sizeof(*ret); 27 | return ret; 28 | } 29 | 30 | int main() 31 | { 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /lecture_examples/3_memory/2_catch_sigsegv.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static jmp_buf jmp; 6 | 7 | void 8 | process_signal(int code) 9 | { 10 | printf("Process SIGSEGV, code = %d, SIGSEGV = %d\n", code, SIGSEGV); 11 | longjmp(jmp, 1); 12 | } 13 | 14 | int 15 | main() 16 | { 17 | char *p = NULL; 18 | signal(SIGSEGV, process_signal); 19 | printf("Before SIGSEGV\n"); 20 | if (setjmp(jmp) == 0) 21 | *p = 100; 22 | printf("After SIGSEGV\n"); 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /lecture_examples/4_signals/10_sigaltstack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static int finished = 0; 10 | 11 | #define handle_error() ({printf("error = %s\n", strerror(errno)); exit(-1); }) 12 | 13 | static void 14 | on_new_signal(int signum) 15 | { 16 | volatile int local_var; 17 | printf("Process signal, stack = %p\n", &local_var); 18 | ++finished; 19 | } 20 | 21 | static void 22 | stack_create(int size) 23 | { 24 | int page_size = getpagesize(); 25 | size = size + (page_size - size % page_size); 26 | if (size < SIGSTKSZ) 27 | size = SIGSTKSZ; 28 | stack_t s; 29 | s.ss_sp = malloc(size); 30 | printf("Page size = %d, new stack begin = %p, stack end = %p\n", 31 | page_size, s.ss_sp, s.ss_sp + size); 32 | s.ss_flags = 0; 33 | s.ss_size = size; 34 | if (sigaltstack(&s, NULL) != 0) 35 | handle_error(); 36 | } 37 | 38 | static void 39 | stack_destroy(void) 40 | { 41 | stack_t s; 42 | if (sigaltstack(NULL, &s) != 0) 43 | handle_error(); 44 | s.ss_flags = SS_DISABLE; 45 | if (sigaltstack(&s, NULL) != 0) 46 | handle_error(); 47 | printf("Destroy stack %p\n", s.ss_sp); 48 | free(s.ss_sp); 49 | } 50 | 51 | static void 52 | wait_signal(void) 53 | { 54 | int need = finished + 1; 55 | raise(SIGUSR1); 56 | while (finished < need) 57 | sched_yield(); 58 | } 59 | 60 | int 61 | main(int argc, char **argv) 62 | { 63 | struct sigaction act; 64 | printf("Main, stack = %p\n", &act); 65 | act.sa_handler = on_new_signal; 66 | sigemptyset(&act.sa_mask); 67 | 68 | act.sa_flags = SA_ONSTACK; 69 | stack_create(1024 * 64); 70 | if (sigaction(SIGUSR1, &act, NULL) != 0) 71 | handle_error(); 72 | wait_signal(); 73 | stack_destroy(); 74 | 75 | act.sa_flags = 0; 76 | if (sigaction(SIGUSR1, &act, NULL) != 0) 77 | handle_error(); 78 | wait_signal(); 79 | return 0; 80 | } 81 | -------------------------------------------------------------------------------- /lecture_examples/4_signals/11_setjmp_problem.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | static volatile jmp_buf buf; 7 | static volatile bool stop = false; 8 | static volatile int total = 0; 9 | 10 | static void 11 | testf2(void) 12 | { 13 | setjmp(buf); 14 | if (++total > 10) { 15 | printf("Bad exit\n"); 16 | exit(-1); 17 | } 18 | } 19 | 20 | static void 21 | testf(void) 22 | { 23 | testf2(); 24 | } 25 | 26 | int 27 | main(void) 28 | { 29 | testf(); 30 | if (! stop) { 31 | stop = true; 32 | printf("Аfter first call\n"); 33 | longjmp(buf, 1); 34 | } 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /lecture_examples/4_signals/12_libcoro.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "12_libcoro.h" 9 | 10 | #define handle_error() ({printf("Error %s\n", strerror(errno)); exit(-1);}) 11 | 12 | /** Main coroutine structure, its context. */ 13 | struct coro { 14 | /** A value, returned by func. */ 15 | int ret; 16 | /** Stack, used by the coroutine. */ 17 | void *stack; 18 | /** An argument for the function func. */ 19 | void *func_arg; 20 | /** A function to call as a coroutine. */ 21 | coro_f func; 22 | /** Last remembered coroutine context. */ 23 | sigjmp_buf ctx; 24 | /** True, if the coroutine has finished. */ 25 | bool is_finished; 26 | /** Links in the coroutine list, used by scheduler. */ 27 | struct coro *next, *prev; 28 | }; 29 | 30 | /** 31 | * Scheduler is a main coroutine - it catches and returns dead 32 | * ones to a user. 33 | */ 34 | static struct coro coro_sched; 35 | /** 36 | * True, if in that moment the scheduler is waiting for a 37 | * coroutine finish. 38 | */ 39 | static bool is_sched_waiting = false; 40 | /** Which coroutine works at this moment. */ 41 | static struct coro *coro_this_ptr = NULL; 42 | /** List of all the coroutines. */ 43 | static struct coro *coro_list = NULL; 44 | /** 45 | * Buffer, used by the coroutine constructor to escape from the 46 | * signal handler back into the constructor to rollback 47 | * sigaltstack etc. 48 | */ 49 | static sigjmp_buf start_point; 50 | 51 | /** Add a new coroutine to the beginning of the list. */ 52 | static void 53 | coro_list_add(struct coro *c) 54 | { 55 | c->next = coro_list; 56 | c->prev = NULL; 57 | if (coro_list != NULL) 58 | coro_list->prev = c; 59 | coro_list = c; 60 | } 61 | 62 | /** Remove a coroutine from the list. */ 63 | static void 64 | coro_list_delete(struct coro *c) 65 | { 66 | struct coro *prev = c->prev, *next = c->next; 67 | if (prev != NULL) 68 | prev->next = next; 69 | if (next != NULL) 70 | next->prev = prev; 71 | if (prev == NULL) 72 | coro_list = next; 73 | } 74 | 75 | int 76 | coro_status(const struct coro *c) 77 | { 78 | return c->ret; 79 | } 80 | 81 | bool 82 | coro_is_finished(const struct coro *c) 83 | { 84 | return c->is_finished; 85 | } 86 | 87 | void 88 | coro_delete(struct coro *c) 89 | { 90 | free(c->stack); 91 | free(c); 92 | } 93 | 94 | /** Switch the current coroutine to an arbitrary one. */ 95 | static void 96 | coro_yield_to(struct coro *to) 97 | { 98 | struct coro *from = coro_this_ptr; 99 | if (sigsetjmp(from->ctx, 0) == 0) 100 | siglongjmp(to->ctx, 1); 101 | coro_this_ptr = from; 102 | } 103 | 104 | void 105 | coro_yield(void) 106 | { 107 | struct coro *from = coro_this_ptr; 108 | struct coro *to = from->next; 109 | if (to == NULL) 110 | coro_yield_to(&coro_sched); 111 | else 112 | coro_yield_to(to); 113 | } 114 | 115 | void 116 | coro_sched_init(void) 117 | { 118 | memset(&coro_sched, 0, sizeof(coro_sched)); 119 | coro_this_ptr = &coro_sched; 120 | } 121 | 122 | struct coro * 123 | coro_sched_wait(void) 124 | { 125 | while (coro_list != NULL) { 126 | for (struct coro *c = coro_list; c != NULL; c = c->next) { 127 | if (c->is_finished) { 128 | coro_list_delete(c); 129 | return c; 130 | } 131 | } 132 | is_sched_waiting = true; 133 | coro_yield_to(coro_list); 134 | is_sched_waiting = false; 135 | } 136 | return NULL; 137 | } 138 | 139 | struct coro * 140 | coro_this(void) 141 | { 142 | return coro_this_ptr; 143 | } 144 | 145 | /** 146 | * The core part of the coroutines creation - this signal handler 147 | * is run on a separate stack using sigaltstack. On an invokation 148 | * it remembers its current context and jumps back to the 149 | * coroutine constructor. Later the coroutine continues from here. 150 | */ 151 | static void 152 | coro_body(int signum) 153 | { 154 | struct coro *c = coro_this_ptr; 155 | coro_this_ptr = NULL; 156 | /* 157 | * On an invokation jump back to the constructor right 158 | * after remembering the context. 159 | */ 160 | if (sigsetjmp(c->ctx, 0) == 0) 161 | siglongjmp(start_point, 1); 162 | /* 163 | * If the execution is here, then the coroutine should 164 | * finaly start work. 165 | */ 166 | coro_this_ptr = c; 167 | c->ret = c->func(c->func_arg); 168 | c->is_finished = true; 169 | /* Can not return - 'ret' address is invalid already! */ 170 | if (! is_sched_waiting) { 171 | printf("Critical error - no place to return!\n"); 172 | exit(-1); 173 | } 174 | siglongjmp(coro_sched.ctx, 1); 175 | } 176 | 177 | struct coro * 178 | coro_new(coro_f func, void *func_arg) 179 | { 180 | struct coro *c = (struct coro *) malloc(sizeof(*c)); 181 | c->ret = 0; 182 | int stack_size = 1024 * 1024; 183 | if (stack_size < SIGSTKSZ) 184 | stack_size = SIGSTKSZ; 185 | c->stack = malloc(stack_size); 186 | c->func = func; 187 | c->func_arg = func_arg; 188 | c->is_finished = false; 189 | /* 190 | * SIGUSR2 is used. First of all, block new signals to be 191 | * able to set a new handler. 192 | */ 193 | sigset_t news, olds, suss; 194 | sigemptyset(&news); 195 | sigaddset(&news, SIGUSR2); 196 | if (sigprocmask(SIG_BLOCK, &news, &olds) != 0) 197 | handle_error(); 198 | /* 199 | * New handler should jump onto a new stack and remember 200 | * that position. Afterwards the stack is disabled and 201 | * becomes dedicated to that single coroutine. 202 | */ 203 | struct sigaction newsa, oldsa; 204 | newsa.sa_handler = coro_body; 205 | newsa.sa_flags = SA_ONSTACK; 206 | sigemptyset(&newsa.sa_mask); 207 | if (sigaction(SIGUSR2, &newsa, &oldsa) != 0) 208 | handle_error(); 209 | /* Create that new stack. */ 210 | stack_t oldst, newst; 211 | newst.ss_sp = c->stack; 212 | newst.ss_size = stack_size; 213 | newst.ss_flags = 0; 214 | if (sigaltstack(&newst, &oldst) != 0) 215 | handle_error(); 216 | /* Jump onto the stack and remember its position. */ 217 | struct coro *old_this = coro_this_ptr; 218 | coro_this_ptr = c; 219 | sigemptyset(&suss); 220 | if (sigsetjmp(start_point, 1) == 0) { 221 | raise(SIGUSR2); 222 | while (coro_this_ptr != NULL) 223 | sigsuspend(&suss); 224 | } 225 | coro_this_ptr = old_this; 226 | /* 227 | * Return the old stack, unblock SIGUSR2. In other words, 228 | * rollback all global changes. The newly created stack 229 | * now is remembered only by the new coroutine, and can be 230 | * used by it only. 231 | */ 232 | if (sigaltstack(NULL, &newst) != 0) 233 | handle_error(); 234 | newst.ss_flags = SS_DISABLE; 235 | if (sigaltstack(&newst, NULL) != 0) 236 | handle_error(); 237 | if ((oldst.ss_flags & SS_DISABLE) == 0 && 238 | sigaltstack(&oldst, NULL) != 0) 239 | handle_error(); 240 | if (sigaction(SIGUSR2, &oldsa, NULL) != 0) 241 | handle_error(); 242 | if (sigprocmask(SIG_SETMASK, &olds, NULL) != 0) 243 | handle_error(); 244 | 245 | /* Now scheduler can work with that coroutine. */ 246 | coro_list_add(c); 247 | return c; 248 | } 249 | -------------------------------------------------------------------------------- /lecture_examples/4_signals/12_libcoro.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBCORO_INCLUDED 2 | #define LIBCORO_INCLUDED 3 | 4 | #include 5 | 6 | struct coro; 7 | typedef int (*coro_f)(void *); 8 | 9 | /** Make current context scheduler. */ 10 | void 11 | coro_sched_init(void); 12 | 13 | /** 14 | * Block until any coroutine has finished. It is returned. NULl, 15 | * if no coroutines. 16 | */ 17 | struct coro * 18 | coro_sched_wait(void); 19 | 20 | /** Currently working coroutine. */ 21 | struct coro * 22 | coro_this(void); 23 | 24 | /** 25 | * Create a new coroutine. It is not started, just added to the 26 | * scheduler. 27 | */ 28 | struct coro * 29 | coro_new(coro_f func, void *func_arg); 30 | 31 | /** Return status of the coroutine. */ 32 | int 33 | coro_status(const struct coro *c); 34 | 35 | /** Check if the coroutine has finished. */ 36 | bool 37 | coro_is_finished(const struct coro *c); 38 | 39 | /** Free coroutine stack and it itself. */ 40 | void 41 | coro_delete(struct coro *c); 42 | 43 | /** Switch to another not finished coroutine. */ 44 | void 45 | coro_yield(void); 46 | 47 | #endif /* LIBCORO_INCLUDED */ 48 | -------------------------------------------------------------------------------- /lecture_examples/4_signals/12_libcoro_example.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "12_libcoro.h" 6 | 7 | #define coro_count 3 8 | static struct coro *coros[coro_count]; 9 | 10 | static void 11 | recursive(int deep, int id) 12 | { 13 | printf("%d: recursive step %d\n", id, deep); 14 | coro_yield(); 15 | if (deep < 2) 16 | recursive(deep + 1, id); 17 | printf("%d: finish recursive step %d\n", id, deep); 18 | } 19 | 20 | static int 21 | coro_func(void *ptr) 22 | { 23 | int id = (int) ptr; 24 | printf("%d: coro is started\n", id); 25 | coro_yield(); 26 | recursive(0, id); 27 | return id; 28 | } 29 | 30 | static int 31 | coro_tree_func(void *ptr) 32 | { 33 | int id = (int) ptr; 34 | printf("%d: coro line is started\n", id); 35 | coro_yield(); 36 | if (id > 1) { 37 | printf("%d: coro line end\n", id); 38 | } else { 39 | printf("%d: coro line next\n", id); 40 | coro_new(coro_tree_func, (void *) (id + 1)); 41 | } 42 | coro_yield(); 43 | return id; 44 | } 45 | 46 | int 47 | main(void) 48 | { 49 | printf("Start main\n"); 50 | coro_sched_init(); 51 | for (int i = 0; i < coro_count; ++i) 52 | coros[i] = coro_new(coro_func, (void *) i); 53 | struct coro *c; 54 | while ((c = coro_sched_wait()) != NULL) { 55 | printf("Finished %d\n", coro_status(c)); 56 | coro_delete(c); 57 | } 58 | printf("Finished simple\n"); 59 | 60 | coro_new(coro_tree_func, (void *) 0); 61 | while ((c = coro_sched_wait()) != NULL) { 62 | printf("Finished %d\n", coro_status(c)); 63 | coro_delete(c); 64 | } 65 | printf("Finish main\n"); 66 | return 0; 67 | } 68 | -------------------------------------------------------------------------------- /lecture_examples/4_signals/1_basic_signal.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | static void 8 | on_new_signal(int signum) 9 | { 10 | switch(signum) { 11 | case SIGINT: 12 | printf("caught sigint\n"); 13 | break; 14 | case SIGUSR1: 15 | printf("caught usr1\n"); 16 | break; 17 | case SIGUSR2: 18 | printf("caught usr2\n"); 19 | break; 20 | default: 21 | printf("caught unknown signal\n"); 22 | exit(-1); 23 | } 24 | } 25 | 26 | int 27 | main(void) 28 | { 29 | printf("my pid: %d\n", (int) getpid()); 30 | signal(SIGINT, on_new_signal); 31 | signal(SIGUSR1, on_new_signal); 32 | signal(SIGUSR2, on_new_signal); 33 | while(true) pause(); 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /lecture_examples/4_signals/2_pause_hangs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | static bool is_sig_received = false; 8 | 9 | static void 10 | on_new_signal(int signum) 11 | { 12 | printf("Received a signal\n"); 13 | is_sig_received = true; 14 | } 15 | 16 | static void 17 | interrupt(void) 18 | { 19 | getchar(); 20 | } 21 | 22 | int 23 | main(void) 24 | { 25 | printf("my pid: %d\n", (int) getpid()); 26 | signal(SIGUSR1, on_new_signal); 27 | printf("Installed a handler\n"); 28 | interrupt(); 29 | while (! is_sig_received) { 30 | interrupt(); 31 | printf("Wait for a signal\n"); 32 | pause(); 33 | } 34 | printf("Finish\n"); 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /lecture_examples/4_signals/3_intr_malloc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static int alarm_cnt = 0; 10 | 11 | static void 12 | do_malloc(void) 13 | { 14 | for (int i = 0; i < 10; ++i) 15 | free(malloc(1024 * 1024 * i)); 16 | } 17 | 18 | static void 19 | on_new_signal(int signum) 20 | { 21 | printf("Interrupted %d times\n", ++alarm_cnt); 22 | do_malloc(); 23 | } 24 | 25 | int 26 | main(void) 27 | { 28 | printf("my pid: %d\n", (int) getpid()); 29 | signal(SIGUSR1, on_new_signal); 30 | while (true) 31 | do_malloc(); 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /lecture_examples/4_signals/4_sigprocmask.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | static int cnt = 0; 7 | 8 | static void 9 | on_new_signal(int signum) 10 | { 11 | printf("Processed a signal %dth time\n", ++cnt); 12 | } 13 | 14 | int 15 | main(void) 16 | { 17 | printf("Start\n"); 18 | signal(SIGINT, on_new_signal); 19 | sigset_t oldm, newm, pendingm; 20 | sigemptyset(&newm); 21 | sigaddset(&newm, SIGINT); 22 | printf("Block SIGINT\n"); 23 | sigprocmask(SIG_BLOCK, &newm, &oldm); 24 | getchar(); 25 | sigpending(&pendingm); 26 | if (sigismember(&pendingm, SIGINT)) 27 | printf("SIGINT is pending\n"); 28 | printf("Unblock SIGINT\n"); 29 | sigprocmask(SIG_SETMASK, &oldm, NULL); 30 | while(cnt < 4) 31 | pause(); 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /lecture_examples/4_signals/5_sigaction_info.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | static void 7 | on_new_signal(int signum, siginfo_t *info, void *context) 8 | { 9 | printf("Segfault: signum = %d, si_signo = %d, si_addr = %p, si_code = \n", 10 | signum, info->si_signo, info->si_addr); 11 | switch (info->si_code) { 12 | case SEGV_MAPERR: 13 | printf("not mapped\n"); 14 | break; 15 | case SEGV_ACCERR: 16 | printf("no permission\n"); 17 | break; 18 | default: 19 | printf("%d\n", info->si_code); 20 | break; 21 | } 22 | exit(-1); 23 | } 24 | 25 | int 26 | main(void) 27 | { 28 | struct sigaction act; 29 | act.sa_sigaction = on_new_signal; 30 | sigemptyset(&act.sa_mask); 31 | act.sa_flags = SA_SIGINFO; 32 | sigaction(SIGSEGV, &act, NULL); 33 | char *ptr = (char *) 10; 34 | *ptr = 100; 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /lecture_examples/4_signals/6_sigaction_mask.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static int finished = 0; 9 | static int id = 0; 10 | 11 | static int 12 | is_usr1_blocked(void) 13 | { 14 | sigset_t old; 15 | sigprocmask(0, NULL, &old); 16 | return sigismember(&old, SIGUSR1); 17 | } 18 | 19 | static void 20 | on_new_signal(int signum) 21 | { 22 | int my_id = ++id; 23 | printf("Begin processing %d, usr1 block = %d\n", my_id, 24 | is_usr1_blocked()); 25 | if (my_id == 1) 26 | raise(signum); 27 | printf("End processing %d\n", my_id); 28 | ++finished; 29 | } 30 | 31 | int 32 | main(int argc, char **argv) 33 | { 34 | struct sigaction act; 35 | act.sa_handler = on_new_signal; 36 | sigemptyset(&act.sa_mask); 37 | act.sa_flags = 0; 38 | for (int i = 1; i < argc; ++i) { 39 | if (strcmp(argv[1], "nodefer") == 0) 40 | act.sa_flags |= SA_NODEFER; 41 | } 42 | sigaction(SIGUSR1, &act, NULL); 43 | printf("Before raises, usr1 block = %d\n", is_usr1_blocked()); 44 | raise(SIGUSR1); 45 | while (finished != 2) 46 | sched_yield(); 47 | printf("After raises, usr1 block = %d\n", is_usr1_blocked()); 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /lecture_examples/4_signals/7_sigaction_jmp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static int finished = 0; 10 | static jmp_buf buf; 11 | 12 | static int 13 | is_usr1_blocked(void) 14 | { 15 | sigset_t old; 16 | sigprocmask(0, NULL, &old); 17 | return sigismember(&old, SIGUSR1); 18 | } 19 | 20 | static void 21 | on_new_signal(int signum) 22 | { 23 | printf("Process signal, usr1 block = %d\n", is_usr1_blocked()); 24 | ++finished; 25 | longjmp(buf, 1); 26 | } 27 | 28 | int 29 | main(void) 30 | { 31 | struct sigaction act; 32 | act.sa_handler = on_new_signal; 33 | sigemptyset(&act.sa_mask); 34 | act.sa_flags = 0; 35 | sigaction(SIGUSR1, &act, NULL); 36 | printf("Before raise, usr1 block = %d\n", is_usr1_blocked()); 37 | if (setjmp(buf) == 0) 38 | raise(SIGUSR1); 39 | while (finished != 1) 40 | sched_yield(); 41 | printf("After raise, usr1 block = %d\n", is_usr1_blocked()); 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /lecture_examples/4_signals/8_sigaction_sigjmp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | static int finished = 0; 10 | static sigjmp_buf buf; 11 | 12 | static int 13 | is_usr1_blocked(void) 14 | { 15 | sigset_t old; 16 | sigprocmask(0, NULL, &old); 17 | return sigismember(&old, SIGUSR1); 18 | } 19 | 20 | static void 21 | on_new_signal(int signum) 22 | { 23 | printf("Process signal, usr1 block = %d\n", is_usr1_blocked()); 24 | ++finished; 25 | siglongjmp(buf, 1); 26 | } 27 | 28 | int 29 | main(void) 30 | { 31 | struct sigaction act; 32 | act.sa_handler = on_new_signal; 33 | sigemptyset(&act.sa_mask); 34 | act.sa_flags = 0; 35 | sigaction(SIGUSR1, &act, NULL); 36 | printf("Before raise, usr1 block = %d\n", is_usr1_blocked()); 37 | if (sigsetjmp(buf, 1) == 0) 38 | raise(SIGUSR1); 39 | while (finished != 1) 40 | sched_yield(); 41 | printf("After raise, usr1 block = %d\n", is_usr1_blocked()); 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /lecture_examples/4_signals/9_sigsuspend.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | static int finished = 0; 8 | 9 | static int 10 | is_int_blocked(void) 11 | { 12 | sigset_t old; 13 | sigprocmask(0, NULL, &old); 14 | return sigismember(&old, SIGINT); 15 | } 16 | 17 | static void 18 | on_new_signal(int signum) 19 | { 20 | printf("Process signal, int block = %d\n", is_int_blocked()); 21 | ++finished; 22 | } 23 | 24 | int 25 | main(void) 26 | { 27 | printf("Before main, int block = %d\n", is_int_blocked()); 28 | sigset_t block, old; 29 | sigemptyset(&block); 30 | sigaddset(&block, SIGINT); 31 | sigprocmask(SIG_BLOCK, &block, &old); 32 | 33 | struct sigaction act; 34 | act.sa_handler = on_new_signal; 35 | sigemptyset(&act.sa_mask); 36 | act.sa_flags = 0; 37 | sigaction(SIGINT, &act, NULL); 38 | sigemptyset(&block); 39 | while (finished != 1) 40 | sigsuspend(&block); 41 | sigprocmask(SIG_SETMASK, &old, NULL); 42 | printf("After main, int block = %d\n", is_int_blocked()); 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /lecture_examples/5_files/1_dirent.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() 5 | { 6 | DIR *dir = opendir("."); 7 | struct dirent *dirent = readdir(dir); 8 | while (dirent != NULL) { 9 | printf("name = %s, inode number = %d, type = %d\n", 10 | dirent->d_name, (int) dirent->d_ino, 11 | (int) dirent->d_type); 12 | dirent = readdir(dir); 13 | } 14 | closedir(dir); 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /lecture_examples/5_files/2_fstat.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(int argc, char **argv) { 6 | struct stat st; 7 | stat(argv[1], &st); 8 | printf("inode = %d, protection = %d, links = "\ 9 | "%d, uid = %u, size = %d, blocks = "\ 10 | "%d\n", (int)st.st_ino, (int)st.st_mode, 11 | (int)st.st_nlink, (unsigned)st.st_uid, 12 | (int)st.st_size, (int)st.st_blocks); 13 | if ((st.st_mode & S_IFDIR) == S_IFDIR) 14 | printf("the file is directory\n"); 15 | if ((st.st_mode & S_IFREG) == S_IFREG) 16 | printf("the file is regular\n"); 17 | if ((st.st_mode & S_IFLNK) == S_IFLNK) 18 | printf("the file is symbolic link\n"); 19 | 20 | if ((st.st_mode & S_IRUSR) == S_IRUSR) 21 | printf("can read it\n"); 22 | if ((st.st_mode & S_IWUSR) == S_IWUSR) 23 | printf("can write it\n"); 24 | if ((st.st_mode & S_IXUSR) == S_IXUSR) 25 | printf("can execute it\n"); 26 | 27 | printf("my uid: %d\n", (int)getuid()); 28 | return 0; 29 | } -------------------------------------------------------------------------------- /lecture_examples/6_threads/10_cthreads_1.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "clone.h" 9 | 10 | typedef int (*cthread_f)(void *); 11 | 12 | struct cthread { 13 | int returned_code; 14 | cthread_f func; 15 | void *arg; 16 | void *stack; 17 | bool is_finished; 18 | }; 19 | 20 | int 21 | cthread_runner(void *arg) 22 | { 23 | struct cthread *thread = (struct cthread *) arg; 24 | thread->returned_code = thread->func(thread->arg); 25 | thread->is_finished = true; 26 | return 0; 27 | } 28 | 29 | void 30 | cthread_create(struct cthread *result, cthread_f func, 31 | void *arg) 32 | { 33 | result->returned_code = 0; 34 | result->func = func; 35 | result->arg = arg; 36 | result->is_finished = false; 37 | thread_create_clone(cthread_runner, (void *) result, 38 | &result->stack); 39 | } 40 | 41 | int 42 | cthread_join(volatile struct cthread *thread) 43 | { 44 | while (! thread->is_finished) 45 | sched_yield(); 46 | free(thread->stack); 47 | return thread->returned_code; 48 | } 49 | 50 | int 51 | func(void *arg) 52 | { 53 | printf("arg = %d\n", *((int *) arg)); 54 | return 200; 55 | } 56 | 57 | int 58 | main() 59 | { 60 | struct cthread thread; 61 | int arg = 100; 62 | cthread_create(&thread, func, (void *) &arg); 63 | int retcode = cthread_join(&thread); 64 | printf("thread is joined with retcode = %d\n", retcode); 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/10_cthreads_2.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "clone.h" 10 | 11 | typedef int (*cthread_f)(void *); 12 | 13 | struct cthread { 14 | int returned_code; 15 | cthread_f func; 16 | void *arg; 17 | void *stack; 18 | bool is_finished; 19 | jmp_buf jmp; 20 | }; 21 | 22 | int 23 | cthread_runner(void *arg) 24 | { 25 | struct cthread *thread = (struct cthread *) arg; 26 | if (setjmp(thread->jmp) == 0) { 27 | thread->returned_code = 28 | thread->func(thread->arg); 29 | } 30 | thread->is_finished = true; 31 | return 0; 32 | } 33 | 34 | void 35 | cthread_create(struct cthread *result, cthread_f func, 36 | void *arg) 37 | { 38 | result->returned_code = 0; 39 | result->func = func; 40 | result->arg = arg; 41 | result->is_finished = false; 42 | thread_create_clone(cthread_runner, (void *) result, 43 | &result->stack); 44 | } 45 | 46 | void 47 | cthread_exit(struct cthread *thread, int retcode) 48 | { 49 | thread->returned_code = retcode; 50 | longjmp(thread->jmp, 1); 51 | } 52 | 53 | int 54 | cthread_join(volatile struct cthread *thread) 55 | { 56 | while (! thread->is_finished) 57 | sched_yield(); 58 | free(thread->stack); 59 | return thread->returned_code; 60 | } 61 | 62 | int 63 | func(void *arg) 64 | { 65 | printf("func is started\n"); 66 | struct cthread *me = (struct cthread *) arg; 67 | cthread_exit(me, 300); 68 | printf("func returned\n"); 69 | return 0; 70 | } 71 | 72 | int 73 | main() 74 | { 75 | struct cthread thread; 76 | cthread_create(&thread, func, (void *) &thread); 77 | int retcode = cthread_join(&thread); 78 | printf("thread is joined with retcode = %d\n", retcode); 79 | return 0; 80 | } 81 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/11_cthread_bad_detach.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "clone.h" 10 | 11 | typedef int (*cthread_f)(void *); 12 | 13 | struct cthread { 14 | int returned_code; 15 | cthread_f func; 16 | void *arg; 17 | void *stack; 18 | bool is_finished; 19 | bool is_detached; 20 | jmp_buf jmp; 21 | }; 22 | 23 | void 24 | cthread_destroy(struct cthread *thread) 25 | { 26 | printf("thread is destroyed\n"); 27 | free(thread->stack); 28 | } 29 | 30 | int 31 | cthread_join(volatile struct cthread *thread) 32 | { 33 | printf("thread is joined\n"); 34 | while (! thread->is_finished) 35 | sched_yield(); 36 | cthread_destroy((struct cthread *) thread); 37 | return thread->returned_code; 38 | } 39 | 40 | int 41 | cthread_runner(void *arg) 42 | { 43 | struct cthread *thread = (struct cthread *) arg; 44 | if (setjmp(thread->jmp) == 0) { 45 | thread->returned_code = 46 | thread->func(thread->arg); 47 | } 48 | if (thread->is_detached) 49 | cthread_destroy(thread); 50 | thread->is_finished = true; 51 | return 0; 52 | } 53 | 54 | void 55 | cthread_detach(struct cthread *thread) 56 | { 57 | if (thread->is_finished) 58 | cthread_destroy(thread); 59 | sleep(1); 60 | thread->is_detached = true; 61 | } 62 | 63 | void 64 | cthread_create(struct cthread *result, cthread_f func, 65 | void *arg) 66 | { 67 | result->returned_code = 0; 68 | result->func = func; 69 | result->arg = arg; 70 | result->is_finished = false; 71 | result->is_detached = false; 72 | thread_create_clone(cthread_runner, (void *) result, 73 | &result->stack); 74 | } 75 | 76 | void 77 | cthread_exit(struct cthread *thread, int retcode) 78 | { 79 | thread->returned_code = retcode; 80 | longjmp(thread->jmp, 1); 81 | } 82 | 83 | int 84 | func(void *arg) 85 | { 86 | printf("thread started\n"); 87 | return 0; 88 | } 89 | 90 | int 91 | main() 92 | { 93 | struct cthread thread; 94 | cthread_create(&thread, func, NULL); 95 | cthread_detach(&thread); 96 | while (! thread.is_finished) 97 | sched_yield(); 98 | printf("detached thread finished\n"); 99 | return 0; 100 | } 101 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/12_cthread_detach.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "clone.h" 10 | 11 | typedef int (*cthread_f)(void *); 12 | 13 | void 14 | spin_lock(volatile bool *lock) 15 | { 16 | while (! __sync_bool_compare_and_swap(lock, 0, 1)) {}; 17 | } 18 | 19 | void 20 | spin_unlock(volatile bool *lock) 21 | { 22 | __sync_bool_compare_and_swap(lock, 1, 0); 23 | } 24 | 25 | struct cthread_stack { 26 | pid_t tid; 27 | void *stack; 28 | struct cthread_stack *next; 29 | }; 30 | 31 | struct cthread_stack *stack_list = NULL; 32 | volatile bool last_stack_lock = false; 33 | 34 | struct cthread { 35 | int returned_code; 36 | cthread_f func; 37 | void *arg; 38 | struct cthread_stack *stack; 39 | bool lock; 40 | bool is_finished; 41 | bool is_detached; 42 | jmp_buf jmp; 43 | }; 44 | 45 | void 46 | cthread_destroy(struct cthread *thread) 47 | { 48 | printf("thread is destroyed\n"); 49 | spin_lock(&last_stack_lock); 50 | struct cthread_stack *iter = stack_list; 51 | while (iter != NULL) { 52 | if (iter->tid != 0) 53 | break; 54 | struct cthread_stack *next = iter->next; 55 | free(iter->stack); 56 | free(iter); 57 | iter = next; 58 | printf("a stack is freed\n"); 59 | } 60 | thread->stack->next = iter; 61 | stack_list = thread->stack; 62 | spin_unlock(&last_stack_lock); 63 | } 64 | 65 | int 66 | cthread_join(volatile struct cthread *thread) 67 | { 68 | printf("thread is joined\n"); 69 | while (! thread->is_finished) 70 | sched_yield(); 71 | cthread_destroy((struct cthread *) thread); 72 | return thread->returned_code; 73 | } 74 | 75 | int 76 | cthread_runner(void *arg) 77 | { 78 | struct cthread *thread = (struct cthread *) arg; 79 | if (setjmp(thread->jmp) == 0) { 80 | thread->returned_code = 81 | thread->func(thread->arg); 82 | } 83 | spin_lock(&thread->lock); 84 | if (thread->is_detached) 85 | cthread_destroy(thread); 86 | thread->is_finished = true; 87 | spin_unlock(&thread->lock); 88 | return 0; 89 | } 90 | 91 | void 92 | cthread_detach(struct cthread *thread) 93 | { 94 | spin_lock(&thread->lock); 95 | if (thread->is_finished) 96 | cthread_destroy(thread); 97 | //sleep(1); 98 | thread->is_detached = true; 99 | spin_unlock(&thread->lock); 100 | } 101 | 102 | void 103 | cthread_create(struct cthread *result, cthread_f func, 104 | void *arg) 105 | { 106 | result->returned_code = 0; 107 | result->func = func; 108 | result->arg = arg; 109 | result->is_finished = false; 110 | result->is_detached = false; 111 | result->lock = false; 112 | result->stack = (struct cthread_stack *) malloc(sizeof(*result->stack)); 113 | result->stack->next = NULL; 114 | thread_create_clone_tid(cthread_runner, (void *) result, 115 | &result->stack->stack, &result->stack->tid); 116 | } 117 | 118 | void 119 | cthread_exit(struct cthread *thread, int retcode) 120 | { 121 | thread->returned_code = retcode; 122 | longjmp(thread->jmp, 1); 123 | } 124 | 125 | int 126 | func(void *arg) 127 | { 128 | printf("thread started\n"); 129 | return 0; 130 | } 131 | 132 | int 133 | main() 134 | { 135 | struct cthread thread[10]; 136 | for (int i = 0; i < 10; ++i) { 137 | cthread_create(&thread[i], func, NULL); 138 | cthread_detach(&thread[i]); 139 | } 140 | 141 | for (int i = 0; i < 10; ++i) { 142 | if (! thread[i].is_finished) { 143 | i = 0; 144 | sched_yield(); 145 | } 146 | } 147 | printf("detached threads finished\n"); 148 | 149 | return 0; 150 | } 151 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/13_simple_condvar.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct condvar { 7 | pthread_mutex_t event_lock; 8 | bool is_event_set; 9 | }; 10 | 11 | void 12 | condvar_signal(struct condvar *condvar) 13 | { 14 | printf("condvar signaled\n"); 15 | condvar->is_event_set = true; 16 | pthread_mutex_unlock(&condvar->event_lock); 17 | 18 | pthread_mutex_lock(&condvar->event_lock); 19 | condvar->is_event_set = false; 20 | printf("condvar is locked\n"); 21 | } 22 | 23 | void 24 | condvar_wait(struct condvar *condvar) 25 | { 26 | for (;;) { 27 | pthread_mutex_lock(&condvar->event_lock); 28 | if (condvar->is_event_set) { 29 | condvar->is_event_set = false; 30 | pthread_mutex_unlock(&condvar->event_lock); 31 | return; 32 | } 33 | pthread_mutex_unlock(&condvar->event_lock); 34 | } 35 | } 36 | 37 | void 38 | condvar_create(struct condvar *condvar) 39 | { 40 | pthread_mutex_init(&condvar->event_lock, NULL); 41 | pthread_mutex_lock(&condvar->event_lock); 42 | condvar->is_event_set = false; 43 | } 44 | 45 | struct condvar condvar; 46 | 47 | void * 48 | thread_f(void *arg) 49 | { 50 | int id = (int) arg; 51 | for (;;) { 52 | condvar_wait(&condvar); 53 | printf("%d processed the event\n", id); 54 | } 55 | } 56 | 57 | int 58 | main() 59 | { 60 | condvar_create(&condvar); 61 | const int thread_count = 10; 62 | pthread_t tid[thread_count]; 63 | for (int i = 0; i < thread_count; ++i) 64 | pthread_create(&tid[i], NULL, thread_f, (void *) i); 65 | while (getchar() != EOF) 66 | condvar_signal(&condvar); 67 | for (int i = 0; i < thread_count; ++i) 68 | pthread_join(tid[i], NULL); 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/14_condvar.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | struct condvar { 8 | pthread_mutex_t event_lock; 9 | bool is_event_set; 10 | int wait_count; 11 | }; 12 | 13 | void 14 | condvar_signal(struct condvar *condvar) 15 | { 16 | int wait_count = __sync_fetch_and_add(&condvar->wait_count, 0); 17 | printf("wait_count = %d\n", wait_count); 18 | if (wait_count == 0) { 19 | printf("no waiters\n"); 20 | return; 21 | } 22 | printf("condvar signaled\n"); 23 | condvar->is_event_set = true; 24 | pthread_mutex_unlock(&condvar->event_lock); 25 | 26 | while (condvar->is_event_set) {} 27 | 28 | pthread_mutex_lock(&condvar->event_lock); 29 | condvar->is_event_set = false; 30 | printf("condvar is locked\n"); 31 | } 32 | 33 | void 34 | condvar_wait(struct condvar *condvar) 35 | { 36 | __sync_add_and_fetch(&condvar->wait_count, 1); 37 | for (;;) { 38 | pthread_mutex_lock(&condvar->event_lock); 39 | if (condvar->is_event_set) { 40 | condvar->is_event_set = false; 41 | __sync_sub_and_fetch(&condvar->wait_count, 1); 42 | pthread_mutex_unlock(&condvar->event_lock); 43 | return; 44 | } 45 | pthread_mutex_unlock(&condvar->event_lock); 46 | } 47 | } 48 | 49 | void 50 | condvar_create(struct condvar *condvar) 51 | { 52 | pthread_mutex_init(&condvar->event_lock, NULL); 53 | pthread_mutex_lock(&condvar->event_lock); 54 | condvar->is_event_set = false; 55 | condvar->wait_count = 0; 56 | } 57 | 58 | struct condvar condvar; 59 | 60 | void * 61 | thread_f(void *arg) 62 | { 63 | int id = (int) arg; 64 | for (;;) { 65 | condvar_wait(&condvar); 66 | printf("%d processed the event\n", id); 67 | usleep(100000); 68 | } 69 | } 70 | 71 | int 72 | main() 73 | { 74 | condvar_create(&condvar); 75 | const int thread_count = 10; 76 | pthread_t tid[thread_count]; 77 | for (int i = 0; i < thread_count; ++i) 78 | pthread_create(&tid[i], NULL, thread_f, (void *) i); 79 | while (getchar() != EOF) 80 | condvar_signal(&condvar); 81 | for (int i = 0; i < thread_count; ++i) 82 | pthread_join(tid[i], NULL); 83 | return 0; 84 | } 85 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/15_gcc_thread.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | static __thread int a = -1; 7 | 8 | volatile int ready_to_print = 0; 9 | volatile bool print = false; 10 | 11 | void * 12 | thread_f(void *arg) 13 | { 14 | a = (int) arg; 15 | __sync_fetch_and_add(&ready_to_print, 1); 16 | while (! print) {} 17 | printf("a = %d\n", a); 18 | return NULL; 19 | } 20 | 21 | int 22 | main() 23 | { 24 | pthread_t t1, t2; 25 | a = 0; 26 | pthread_create(&t1, NULL, thread_f, (void *) 1); 27 | pthread_create(&t2, NULL, thread_f, (void *) 2); 28 | while (__sync_fetch_and_add(&ready_to_print, 0) != 2) {} 29 | print = true; 30 | pthread_join(t1, NULL); 31 | pthread_join(t2, NULL); 32 | printf("main a = %d\n", a); 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/16_pthread_key.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | volatile int ready_to_print = 0; 7 | volatile bool print = false; 8 | 9 | pthread_key_t key; 10 | 11 | void * 12 | thread_f(void *arg) 13 | { 14 | pthread_setspecific(key, arg); 15 | __sync_fetch_and_add(&ready_to_print, 1); 16 | while (! print) {} 17 | int tmp = (int) pthread_getspecific(key); 18 | printf("value = %d\n", tmp); 19 | return NULL; 20 | } 21 | 22 | int 23 | main() 24 | { 25 | pthread_t t1, t2; 26 | pthread_key_create(&key, NULL); 27 | pthread_setspecific(key, (const void *) 0); 28 | 29 | pthread_create(&t1, NULL, thread_f, (void *) 1); 30 | pthread_create(&t2, NULL, thread_f, (void *) 2); 31 | while (__sync_fetch_and_add(&ready_to_print, 0) != 2) {} 32 | print = true; 33 | pthread_join(t1, NULL); 34 | pthread_join(t2, NULL); 35 | int tmp = (int) pthread_getspecific(key); 36 | printf("main value = %d\n", tmp); 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/17_thread_stacks.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | volatile bool finish = false; 7 | volatile bool ptr_is_set = false; 8 | volatile int *a_ptr = NULL; 9 | 10 | void * 11 | thread_f(void *arg) 12 | { 13 | int a = 100; 14 | printf("child stack top = %p\n", &a); 15 | a_ptr = &a; 16 | ptr_is_set = true; 17 | while (! finish) {} 18 | return NULL; 19 | } 20 | 21 | int 22 | main() 23 | { 24 | pthread_t t; 25 | printf("main stack top = %p\n", &t); 26 | pthread_create(&t, NULL, thread_f, NULL); 27 | while (! ptr_is_set) {} 28 | printf("foreign a = %d\n", *a_ptr); 29 | finish = true; 30 | pthread_join(t, NULL); 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/1_clone.c: -------------------------------------------------------------------------------- 1 | #include "clone.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | pid_t 8 | gettid() 9 | { 10 | return syscall(SYS_gettid); 11 | } 12 | 13 | volatile bool is_finished = false; 14 | 15 | int 16 | thread_f(void *arg) 17 | { 18 | pid_t thread_id = gettid(); 19 | pid_t pid = getpid(); 20 | printf("pid %d, tid %d: new thread, arg = %d\n", 21 | (int)pid, (int)thread_id, *((int *) arg)); 22 | is_finished = true; 23 | return 0; 24 | } 25 | 26 | int 27 | main() 28 | { 29 | pid_t thread_id = gettid(); 30 | pid_t pid = getpid(); 31 | printf("pid %d, tid %d: main thread\n", 32 | (int)pid, (int)thread_id); 33 | void *stack; 34 | int arg = 100; 35 | int ret = thread_create_clone(thread_f, (void *) &arg, &stack); 36 | printf("pid %d, tid %d: clone result = %d\n", 37 | (int)pid, (int)thread_id, ret); 38 | while (! is_finished) 39 | sched_yield(); 40 | free(stack); 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/2_non_volatile.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void * 7 | thread_f(void *arg) 8 | { 9 | *((bool *)arg) = true; 10 | return NULL; 11 | } 12 | 13 | int 14 | main() 15 | { 16 | const bool is_finished = false; 17 | pthread_t tid; 18 | pthread_create(&tid, NULL, thread_f, 19 | (void *) &is_finished); 20 | while (! is_finished) 21 | sched_yield(); 22 | 23 | pthread_join(tid, NULL); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/3_volatile.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void * 7 | thread_f(void *arg) 8 | { 9 | *((bool *)arg) = true; 10 | return NULL; 11 | } 12 | 13 | int 14 | main() 15 | { 16 | const volatile bool is_finished = false; 17 | pthread_t tid; 18 | pthread_create(&tid, NULL, thread_f, 19 | (void *) &is_finished); 20 | while (! is_finished) 21 | sched_yield(); 22 | 23 | pthread_join(tid, NULL); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/4_not_atomic.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "clone.h" 8 | 9 | volatile bool is_finished = false; 10 | volatile int counter = 0; 11 | 12 | int 13 | thread_f(void *arg) 14 | { 15 | for (int i = 0; i < 100000; ++i) 16 | counter = counter + 1; 17 | is_finished = true; 18 | return 0; 19 | } 20 | 21 | int 22 | main() 23 | { 24 | void *stack; 25 | thread_create_clone(thread_f, NULL, &stack); 26 | for (int i = 0; i < 100000; ++i) 27 | counter = counter + 1; 28 | while (! is_finished) 29 | sched_yield(); 30 | printf("counter = %d\n", counter); 31 | free(stack); 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/5_atomic.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "clone.h" 8 | 9 | volatile bool is_finished = false; 10 | volatile int counter = 0; 11 | 12 | int 13 | thread_f(void *arg) 14 | { 15 | for (int i = 0; i < 100000; ++i) 16 | __sync_add_and_fetch(&counter, 1); 17 | is_finished = true; 18 | return 0; 19 | } 20 | 21 | int 22 | main() 23 | { 24 | void *stack; 25 | thread_create_clone(thread_f, NULL, &stack); 26 | for (int i = 0; i < 100000; ++i) 27 | __sync_add_and_fetch(&counter, 1); 28 | while (! is_finished) 29 | sched_yield(); 30 | printf("counter = %d\n", counter); 31 | free(stack); 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/6_bad_lock.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct complex_thing { 7 | bool lock; 8 | char string[128]; 9 | }; 10 | 11 | volatile struct complex_thing thing; 12 | 13 | volatile bool start = false; 14 | 15 | void * 16 | thread_f(void *arg) 17 | { 18 | int id = (int) arg; 19 | while (! start) {}; 20 | if (! thing.lock) { 21 | thing.lock = true; 22 | for (int i = 0; i < sizeof(thing.string) - 1; ++i) 23 | thing.string[i] = 'a' + id; 24 | } 25 | return NULL; 26 | } 27 | 28 | int 29 | main() 30 | { 31 | pthread_t tid[6]; 32 | for (int i = 0; i < 6; ++i) 33 | pthread_create(&tid[i], NULL, thread_f, (void *) i); 34 | start = true; 35 | for (int i = 0; i < 6; ++i) 36 | pthread_join(tid[i], NULL); 37 | printf("%s\n", thing.string); 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/7_spin_lock.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct complex_thing { 7 | bool lock; 8 | char string[128]; 9 | }; 10 | 11 | volatile struct complex_thing thing; 12 | 13 | volatile bool start = false; 14 | 15 | void * 16 | thread_f(void *arg) 17 | { 18 | int id = (int) arg; 19 | while (! start) {}; 20 | if (__sync_bool_compare_and_swap(&thing.lock, 0, 1)) { 21 | for (int i = 0; i < sizeof(thing.string) - 1; ++i) 22 | thing.string[i] = 'a' + id; 23 | __sync_bool_compare_and_swap(&thing.lock, 1, 0); 24 | } 25 | return NULL; 26 | } 27 | 28 | int 29 | main() 30 | { 31 | pthread_t tid[6]; 32 | for (int i = 0; i < 6; ++i) 33 | pthread_create(&tid[i], NULL, thread_f, (void *) i); 34 | start = true; 35 | for (int i = 0; i < 6; ++i) 36 | pthread_join(tid[i], NULL); 37 | printf("%s\n", thing.string); 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/8_5_cpu_reordering.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "8_5_random_generator.h" 5 | 6 | /** 7 | * Set this flag to 1 to stop reorderings, and to 0 to see them. 8 | */ 9 | #define USE_CPU_FENCE 0 10 | 11 | /** Prevent compiler reordering. */ 12 | #define compiler_barrier() ({asm volatile("" ::: "memory");}) 13 | /** Prevent CPU reordering. */ 14 | #define memory_barrier() ({asm volatile("mfence" ::: "memory");}) 15 | 16 | #if USE_CPU_FENCE 17 | #define barrier() memory_barrier() 18 | #else 19 | #define barrier() compiler_barrier() 20 | #endif 21 | 22 | static volatile bool start_t1 = false; 23 | static volatile bool start_t2 = false; 24 | static volatile bool end_t1 = false; 25 | static volatile bool end_t2 = false; 26 | 27 | static int X, Y; 28 | static int r1, r2; 29 | 30 | /** 31 | * The case - one thread sets X and reads Y. Other thread sets Y 32 | * and reads X. Logically it is impossible, that both threads see 33 | * the initial X and Y, but in fact it happens, without a proper 34 | * protection. 35 | */ 36 | 37 | void * 38 | thread1_f(void *param) 39 | { 40 | struct mersenne_twister twister; 41 | mersenne_twister_create(&twister, 1); 42 | for (;;) 43 | { 44 | /* 45 | * Key moment - both threads should start the 46 | * read-write pairs simultaneously. It increases 47 | * reproducibility. 48 | */ 49 | while(! start_t1) { } 50 | start_t1 = false; 51 | compiler_barrier(); 52 | /* 53 | * The small random delay smooths the problem, 54 | * that one thread can wakeup a bit earlier than 55 | * another, and will perform its transaction too 56 | * early. 57 | */ 58 | while (mersenne_twister_generate(&twister) % 8 != 0) {} 59 | compiler_barrier(); 60 | 61 | /** ----- The transaction ----- */ 62 | X = 1; 63 | barrier(); 64 | r1 = Y; 65 | /** --------------------------- */ 66 | 67 | compiler_barrier(); 68 | end_t1 = true; 69 | } 70 | return NULL; 71 | }; 72 | 73 | void * 74 | thread2_f(void *param) 75 | { 76 | struct mersenne_twister twister; 77 | mersenne_twister_create(&twister, 2); 78 | for (;;) 79 | { 80 | while(! start_t2) { } 81 | start_t2 = false; 82 | compiler_barrier(); 83 | 84 | while (mersenne_twister_generate(&twister) % 8 != 0) {} 85 | compiler_barrier(); 86 | 87 | /** ----- The transaction ----- */ 88 | Y = 1; 89 | barrier(); 90 | r2 = X; 91 | /** --------------------------- */ 92 | 93 | compiler_barrier(); 94 | end_t2 = true; 95 | } 96 | return NULL; 97 | }; 98 | 99 | int 100 | main() 101 | { 102 | pthread_t thread1, thread2; 103 | pthread_create(&thread1, NULL, thread1_f, NULL); 104 | pthread_create(&thread2, NULL, thread2_f, NULL); 105 | 106 | int detected = 0; 107 | for (int iterations = 1; ; iterations++) 108 | { 109 | X = 0; 110 | Y = 0; 111 | start_t1 = true; 112 | start_t2 = true; 113 | compiler_barrier(); 114 | 115 | while(! end_t1 || ! end_t2) { } 116 | compiler_barrier(); 117 | end_t1 = false; 118 | end_t2 = false; 119 | 120 | if (r1 == 0 && r2 == 0) 121 | { 122 | detected++; 123 | printf("%d reorders detected after %d iterations\n", 124 | detected, iterations); 125 | } 126 | } 127 | return 0; 128 | } 129 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/8_5_random_generator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * --------------------------------------------------------------- 5 | * Mersenne Twister 6 | * A thread-safe random number generator with good randomness in a 7 | * small number of instructions. It is used it to introduce random 8 | * timing delays. The built-in generator, random() function, gives 9 | * strange results, it makes reordering happening much less 10 | * frequent. Perhaps, the generator below is just faster. 11 | * --------------------------------------------------------------- 12 | */ 13 | #define MT_IA 397 14 | #define MT_LEN 624 15 | 16 | struct mersenne_twister 17 | { 18 | unsigned int buffer[MT_LEN]; 19 | int index; 20 | }; 21 | 22 | static unsigned int 23 | mersenne_twister_generate(struct mersenne_twister *twister) 24 | { 25 | int i = twister->index; 26 | int i2 = twister->index + 1; 27 | int j = twister->index + MT_IA; 28 | if (i2 >= MT_LEN) 29 | i2 = 0; 30 | if (j >= MT_LEN) 31 | j -= MT_LEN; 32 | 33 | unsigned int s = (twister->buffer[i] & 0x80000000) | 34 | (twister->buffer[i2] & 0x7fffffff); 35 | unsigned int r = twister->buffer[j] ^ (s >> 1) ^ ((s & 1) * 0x9908B0DF); 36 | twister->buffer[twister->index] = r; 37 | twister->index = i2; 38 | 39 | r ^= (r >> 11); 40 | r ^= (r << 7) & 0x9d2c5680UL; 41 | r ^= (r << 15) & 0xefc60000UL; 42 | r ^= (r >> 18); 43 | return r; 44 | } 45 | 46 | static void 47 | mersenne_twister_create(struct mersenne_twister *twister, unsigned int seed) 48 | { 49 | for (int i = 0; i < MT_LEN; i++) 50 | twister->buffer[i] = seed; 51 | twister->index = 0; 52 | for (int i = 0; i < MT_LEN * 100; i++) 53 | mersenne_twister_generate(twister); 54 | } 55 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/8_6_spinlock_acq_rel.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /** 8 | * Swap to 0 to look at performance difference between ACQ+REL vs 9 | * full barrier. 10 | */ 11 | #define USE_ACQUIRE_RELEASE 1 12 | 13 | #if USE_ACQUIRE_RELEASE 14 | 15 | static void 16 | spin_lock(volatile bool *lock) 17 | { 18 | while (__atomic_test_and_set(lock, __ATOMIC_ACQUIRE)) 19 | {} 20 | } 21 | 22 | static void 23 | spin_unlock(volatile bool *lock) 24 | { 25 | __atomic_clear(lock, __ATOMIC_RELEASE); 26 | } 27 | 28 | #else 29 | 30 | static void 31 | spin_lock(volatile bool *lock) 32 | { 33 | while (! __sync_bool_compare_and_swap(lock, 0, 1)) 34 | {} 35 | } 36 | 37 | static void 38 | spin_unlock(volatile bool *lock) 39 | { 40 | __sync_bool_compare_and_swap(lock, 1, 0); 41 | } 42 | 43 | #endif 44 | 45 | static volatile bool start = false; 46 | static uint64_t counter = 0; 47 | static volatile bool lock = false; 48 | 49 | void * 50 | thread_f(void *arg) 51 | { 52 | while (! start) {}; 53 | for (int i = 0; i < 10000000; ++i) { 54 | spin_lock(&lock); 55 | ++counter; 56 | spin_unlock(&lock); 57 | } 58 | return NULL; 59 | } 60 | 61 | int 62 | main() 63 | { 64 | pthread_t tid[6]; 65 | for (int i = 0; i < 6; ++i) 66 | pthread_create(&tid[i], NULL, thread_f, (void *) i); 67 | clock_t ts1 = clock(); 68 | start = true; 69 | for (int i = 0; i < 6; ++i) 70 | pthread_join(tid[i], NULL); 71 | clock_t ts2 = clock(); 72 | printf("counter = %llu, time = %lf\n", counter, 73 | ((double) (ts2 - ts1)) / CLOCKS_PER_SEC); 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/8_futex.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | int 11 | futex_wait(int *futex, int val) 12 | { 13 | return syscall(SYS_futex, futex, FUTEX_WAIT, val, 14 | NULL, NULL, 0); 15 | } 16 | 17 | int 18 | futex_wake(int *futex) 19 | { 20 | return syscall(SYS_futex, futex, FUTEX_WAKE, 1, 21 | NULL, NULL, 0); 22 | } 23 | 24 | void 25 | futex_lock(int *futex) 26 | { 27 | while (__sync_val_compare_and_swap(futex, 0, 1) != 0) 28 | futex_wait(futex, 1); 29 | } 30 | 31 | void 32 | futex_unlock(int *futex) 33 | { 34 | __sync_bool_compare_and_swap(futex, 1, 0); 35 | futex_wake(futex); 36 | } 37 | 38 | volatile int counter = 0; 39 | volatile bool start = false; 40 | volatile int futex = 0; 41 | 42 | void * 43 | thread_f(void *futex) 44 | { 45 | while (! start) {}; 46 | 47 | for (int i = 0; i < 100000; ++i) { 48 | futex_lock((int *) futex); 49 | counter = counter + 1; 50 | futex_unlock((int *) futex); 51 | } 52 | return NULL; 53 | } 54 | 55 | int 56 | main() 57 | { 58 | pthread_t tid[6]; 59 | int futex = 0; 60 | for (int i = 0; i < 6; ++i) 61 | pthread_create(&tid[i], NULL, thread_f, (void *) &futex); 62 | start = true; 63 | for (int i = 0; i < 6; ++i) 64 | pthread_join(tid[i], NULL); 65 | printf("%d\n", counter); 66 | return 0; 67 | } 68 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/9_clone_vs_pthread.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "clone.h" 10 | 11 | volatile bool is_finished = false; 12 | 13 | volatile pthread_t child_id; 14 | 15 | int 16 | thread_f(void *arg) 17 | { 18 | child_id = pthread_self(); 19 | is_finished = true; 20 | return 0; 21 | } 22 | 23 | int 24 | main() 25 | { 26 | void *stack; 27 | thread_create_clone(thread_f, NULL, &stack); 28 | 29 | while (! is_finished) 30 | sched_yield(); 31 | 32 | pthread_join(child_id, NULL); 33 | free(stack); 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /lecture_examples/6_threads/clone.h: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | 5 | static inline int 6 | thread_create_clone(int (*func)(void *), void *arg, void **stack) 7 | { 8 | int stack_size = 65 * 1024; 9 | *stack = malloc(stack_size); 10 | void *stack_top = (char *) *stack + stack_size; 11 | int flags = CLONE_VM | CLONE_FS | CLONE_FILES | 12 | CLONE_SIGHAND | CLONE_THREAD; 13 | return clone(func, stack_top, flags, arg); 14 | } 15 | 16 | static inline int 17 | thread_create_clone_tid(int (*func)(void *), void *arg, void **stack, 18 | pid_t *tid) 19 | { 20 | int stack_size = 65 * 1024; 21 | *stack = malloc(stack_size); 22 | void *stack_top = (char *) *stack + stack_size; 23 | int flags = CLONE_VM | CLONE_FS | CLONE_FILES | 24 | CLONE_SIGHAND | CLONE_THREAD | CLONE_CHILD_CLEARTID | 25 | CLONE_CHILD_SETTID; 26 | return clone(func, stack_top, flags, arg, NULL, NULL, tid); 27 | } 28 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/10_sem_mutex.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct semaphore { 4 | int counter; 5 | pthread_mutex_t mutex; 6 | pthread_cond_t cond; 7 | }; 8 | 9 | static inline void 10 | semaphore_get(struct semaphore *sem) 11 | { 12 | pthread_mutex_lock(&sem->mutex); 13 | while (sem->counter == 0) 14 | pthread_cond_wait(&sem->cond, &sem->mutex); 15 | sem->counter--; 16 | pthread_mutex_unlock(&sem->mutex); 17 | } 18 | 19 | static inline void 20 | semaphore_put(struct semaphore *sem) 21 | { 22 | pthread_mutex_lock(&sem->mutex); 23 | sem->counter++; 24 | pthread_cond_signal(&sem->cond); 25 | pthread_mutex_unlock(&sem->mutex); 26 | } 27 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/11_shm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | int 12 | main() 13 | { 14 | key_t key = ftok("./a.out", 0); 15 | int id = shmget(key, 1024, IPC_CREAT | S_IRWXU | S_IRWXO); 16 | printf("created mem with id %d\n", id); 17 | char *mem = shmat(id, NULL, 0); 18 | int align = __alignof__(pthread_mutex_t); 19 | pthread_mutex_t *mutex = (pthread_mutex_t *) 20 | (mem + align - mem % align); 21 | volatile char *data = (char *) mutex + sizeof(*mutex); 22 | *data = 0; 23 | pthread_mutexattr_t attr; 24 | pthread_mutexattr_init(&attr); 25 | pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); 26 | pthread_mutex_init(mutex, &attr); 27 | pthread_mutexattr_destroy(&attr); 28 | printf("Created mutex between processed\n"); 29 | 30 | if (fork() == 0) { 31 | printf("Child holds a lock\n"); 32 | pthread_mutex_lock(mutex); 33 | *data = 1; 34 | printf("And the child dies\n"); 35 | exit(1); 36 | } 37 | printf("Parent waits for 1 in shmem\n"); 38 | while (*data != 1) {} 39 | printf("Parent tries to lock\n"); 40 | if (pthread_mutex_lock(mutex) == EOWNERDEAD) { 41 | printf("Owner is dead, restore\n"); 42 | pthread_mutex_consistent(mutex); 43 | } 44 | printf("Destroy mutex\n"); 45 | pthread_mutex_unlock(mutex); 46 | pthread_mutex_destroy(mutex); 47 | 48 | printf("Free shmem\n"); 49 | shmdt(mem); 50 | shmctl(id, IPC_RMID, NULL); 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/12_sem_posix.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define WORKER_COUNT 10 10 | 11 | sem_t *sem; 12 | 13 | void * 14 | worker_f(void *arg) 15 | { 16 | int id = (int) arg; 17 | while (1) { 18 | sem_wait(sem); 19 | printf("Thread %d got the sem\n", id); 20 | sleep(3); 21 | sem_post(sem); 22 | sleep(1); 23 | } 24 | } 25 | 26 | int 27 | main() 28 | { 29 | retry:; 30 | pthread_t workers[WORKER_COUNT]; 31 | sem = sem_open("/my_sem", O_CREAT | O_EXCL, O_RDWR, 5); 32 | if (sem == SEM_FAILED) { 33 | if (errno == EEXIST) { 34 | sem_unlink("/my_sem"); 35 | goto retry; 36 | } 37 | printf("error = %s\n", strerror(errno)); 38 | return -1; 39 | } 40 | for (int i = 0; i < WORKER_COUNT; ++i) 41 | pthread_create(&workers[i], NULL, worker_f, (void *) i); 42 | getchar(); 43 | sem_close(sem); 44 | sem_unlink("/my_sem"); 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/13_socketpair.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define MAX_MSG_SIZE 1024 * 1024 9 | 10 | void 11 | worker(int sock) 12 | { 13 | char buffer[MAX_MSG_SIZE]; 14 | ssize_t size; 15 | while ((size = read(sock, buffer, sizeof(buffer))) > 0) 16 | printf("Client received %d\n", (int) size); 17 | close(sock); 18 | } 19 | 20 | int 21 | main(int argc, const char **argv) 22 | { 23 | if (argc < 2) { 24 | printf("No socket type\n"); 25 | return -1; 26 | } 27 | int socket_type; 28 | if (strcmp(argv[1], "stream")) 29 | socket_type = SOCK_STREAM; 30 | else 31 | socket_type = SOCK_DGRAM; 32 | int sockets[2]; 33 | socketpair(AF_UNIX, socket_type, 0, sockets); 34 | if (fork() == 0) { 35 | close(sockets[0]); 36 | worker(sockets[1]); 37 | return 0; 38 | } 39 | close(sockets[1]); 40 | char buffer[MAX_MSG_SIZE]; 41 | int size; 42 | while (scanf("%d", &size) > 0) { 43 | printf("Server sent %d\n", size); 44 | write(sockets[0], buffer, size); 45 | } 46 | close(sockets[0]); 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/14_sock_dgram.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void 9 | initiator_f(int sock, struct sockaddr_un *next_addr) 10 | { 11 | int number; 12 | while(scanf("%d", &number) > 0) { 13 | if (sendto(sock, &number, sizeof(number), 0, 14 | (struct sockaddr *) next_addr, 15 | sizeof(*next_addr)) == -1) { 16 | printf("error = %s\n", strerror(errno)); 17 | continue; 18 | } 19 | printf("Sent %d\n", number); 20 | int buffer = 0; 21 | ssize_t size = recv(sock, &buffer, sizeof(buffer), 0); 22 | if (size == -1) 23 | printf("error = %s\n", strerror(errno)); 24 | else 25 | printf("Received %d\n", buffer); 26 | } 27 | } 28 | 29 | void 30 | worker_f(int sock, struct sockaddr_un *next_addr) 31 | { 32 | while(1) { 33 | int buffer = 0; 34 | ssize_t size = recv(sock, &buffer, sizeof(buffer), 0); 35 | if (size == -1) { 36 | printf("error = %s\n", strerror(errno)); 37 | continue; 38 | } 39 | printf("Received %d\n", buffer); 40 | buffer++; 41 | if (sendto(sock, &buffer, sizeof(buffer), 0, 42 | (struct sockaddr *) next_addr, 43 | sizeof(*next_addr)) == -1) 44 | printf("error = %s\n", strerror(errno)); 45 | else 46 | printf("Sent %d\n", buffer); 47 | } 48 | } 49 | 50 | int 51 | main(int argc, const char **argv) 52 | { 53 | if (argc < 2) { 54 | printf("No id\n"); 55 | return -1; 56 | } 57 | int myid = atoi(argv[1]); 58 | int sock = socket(AF_UNIX, SOCK_DGRAM, 0); 59 | if (sock == -1) { 60 | printf("error = %s\n", strerror(errno)); 61 | return -1; 62 | } 63 | struct sockaddr_un addr; 64 | addr.sun_family = AF_UNIX; 65 | sprintf(addr.sun_path, "sock%d", myid); 66 | if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) != 0) { 67 | printf("error = %s\n", strerror(errno)); 68 | return -1; 69 | } 70 | 71 | struct sockaddr_un next_addr; 72 | next_addr.sun_family = AF_UNIX; 73 | sprintf(next_addr.sun_path, "sock%d", (myid + 1) % 3); 74 | 75 | ssize_t size; 76 | int buffer; 77 | if (myid == 0) 78 | initiator_f(sock, &next_addr); 79 | else 80 | worker_f(sock, &next_addr); 81 | return 0; 82 | } 83 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/15_sock_client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | int 12 | main() 13 | { 14 | int sock = socket(AF_UNIX, SOCK_STREAM, 0); 15 | if (sock == -1) { 16 | printf("error = %s\n", strerror(errno)); 17 | return -1; 18 | } 19 | struct sockaddr_un addr; 20 | addr.sun_family = AF_UNIX; 21 | sprintf(addr.sun_path, "%s", "sock_server"); 22 | 23 | if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) != 0) { 24 | printf("error = %s\n", strerror(errno)); 25 | return -1; 26 | } 27 | int number; 28 | while (scanf("%d", &number) > 0) { 29 | if (write(sock, &number, sizeof(number)) == -1) { 30 | printf("error = %s\n", strerror(errno)); 31 | continue; 32 | } 33 | printf("Sent %d\n", number); 34 | number = 0; 35 | int rc = read(sock, &number, sizeof(number)); 36 | if (rc == 0) { 37 | printf("Closed connection\n"); 38 | break; 39 | } 40 | if (rc == -1) 41 | printf("error = %s\n", strerror(errno)); 42 | else 43 | printf("Received %d\n", number); 44 | } 45 | close(sock); 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/15_sock_server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | void * 13 | worker_f(void *arg) 14 | { 15 | printf("New client created\n"); 16 | int client_sock = (int) arg; 17 | while(1) { 18 | int buffer = 0; 19 | ssize_t size = read(client_sock, &buffer, sizeof(buffer)); 20 | if (size == -1) { 21 | printf("error = %s\n", strerror(errno)); 22 | continue; 23 | } 24 | if (size == 0) { 25 | printf("Closed connection\n"); 26 | break; 27 | } 28 | printf("Received %d\n", buffer); 29 | buffer++; 30 | if (write(client_sock, &buffer, sizeof(buffer)) == -1) 31 | printf("error = %s\n", strerror(errno)); 32 | else 33 | printf("Sent %d\n", buffer); 34 | } 35 | close(client_sock); 36 | return NULL; 37 | } 38 | 39 | int 40 | main(int argc, const char **argv) 41 | { 42 | int sock = socket(AF_UNIX, SOCK_STREAM, 0); 43 | if (sock == -1) { 44 | printf("error = %s\n", strerror(errno)); 45 | return -1; 46 | } 47 | struct sockaddr_un addr; 48 | addr.sun_family = AF_UNIX; 49 | sprintf(addr.sun_path, "%s", "sock_server"); 50 | if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) != 0) { 51 | printf("error = %s\n", strerror(errno)); 52 | return -1; 53 | } 54 | if (listen(sock, 128) == -1) { 55 | printf("error = %s\n", strerror(errno)); 56 | return -1; 57 | } 58 | pthread_attr_t attr; 59 | pthread_attr_init(&attr); 60 | pthread_attr_setdetachstate(&attr, 1); 61 | while(1) { 62 | pthread_t worker_thread; 63 | int client_sock = accept(sock, NULL, NULL); 64 | if (client_sock == -1) { 65 | printf("error = %s\n", strerror(errno)); 66 | continue; 67 | } 68 | int rc = pthread_create(&worker_thread, &attr, worker_f, 69 | (void *) client_sock); 70 | if (rc != 0) { 71 | printf("error = %s\n", strerror(rc)); 72 | close(client_sock); 73 | } 74 | } 75 | pthread_attr_destroy(&attr); 76 | close(sock); 77 | return 0; 78 | } 79 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/1_simple_sort.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct worker { 10 | int *array; 11 | int size; 12 | }; 13 | 14 | int 15 | cmp(const void *a, const void *b) 16 | { 17 | return *(int *)a - *(int *)b; 18 | } 19 | 20 | void 21 | sorter(struct worker *worker, const char *filename) 22 | { 23 | FILE *file = fopen(filename, "r"); 24 | int size = 0; 25 | int capacity = 1024; 26 | int *array = malloc(capacity * sizeof(int)); 27 | while (fscanf(file, "%d", &array[size]) > 0) { 28 | ++size; 29 | if (size == capacity) { 30 | capacity *= 2; 31 | array = realloc(array, capacity * sizeof(int)); 32 | } 33 | } 34 | qsort(array, size, sizeof(int), cmp); 35 | fclose(file); 36 | worker->array = array; 37 | worker->size = size; 38 | } 39 | 40 | int 41 | main(int argc, const char **argv) 42 | { 43 | struct timeval start; 44 | gettimeofday(&start, NULL); 45 | int nfiles = argc - 1; 46 | struct worker *workers = malloc(sizeof(struct worker) * nfiles); 47 | struct worker *w = workers; 48 | int total_size = 0; 49 | for (int i = 0; i < nfiles; ++i, ++w) { 50 | sorter(w, argv[i + 1]); 51 | total_size += w->size; 52 | } 53 | int *total_array = malloc(total_size * sizeof(int)); 54 | int *pos = total_array; 55 | w = workers; 56 | for (int i = 0; i < nfiles; ++i, ++w) { 57 | memcpy(pos, w->array, w->size * sizeof(int)); 58 | pos += w->size; 59 | } 60 | struct timeval tmp; 61 | gettimeofday(&tmp, NULL); 62 | uint64_t microsecs = tmp.tv_sec * 1000000 + tmp.tv_usec - 63 | start.tv_sec * 1000000 + start.tv_usec; 64 | printf("presort time = %lf\n", (microsecs + 0.0) / 1000000); 65 | return 0; 66 | } 67 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/2_parallel_sort.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | struct worker { 12 | int fd[2]; 13 | int *array; 14 | int size; 15 | int id; 16 | }; 17 | 18 | int 19 | cmp(const void *a, const void *b) 20 | { 21 | return *(int *)a - *(int *)b; 22 | } 23 | 24 | void 25 | sorter(struct worker *worker, const char *filename) 26 | { 27 | close(worker->fd[0]); 28 | FILE *file = fopen(filename, "r"); 29 | int size = 0; 30 | int capacity = 1024; 31 | int *array = malloc(capacity * sizeof(int)); 32 | while (fscanf(file, "%d", &array[size]) > 0) { 33 | ++size; 34 | if (size == capacity) { 35 | capacity *= 2; 36 | array = realloc(array, capacity * sizeof(int)); 37 | } 38 | } 39 | qsort(array, size, sizeof(int), cmp); 40 | fclose(file); 41 | printf("Worker %d sorted %d numbers\n", worker->id, size); 42 | write(worker->fd[1], &size, sizeof(size)); 43 | write(worker->fd[1], array, sizeof(int) * size); 44 | close(worker->fd[1]); 45 | free(array); 46 | } 47 | 48 | void 49 | read_from_pipe(int fd, char *in, int size) 50 | { 51 | int total = 0; 52 | int rc = read(fd, in, size); 53 | while (rc > 0 && size > 0) { 54 | size -= rc; 55 | in += rc; 56 | total += rc; 57 | rc = read(fd, in, size); 58 | } 59 | } 60 | 61 | int 62 | main(int argc, const char **argv) 63 | { 64 | struct timeval start; 65 | gettimeofday(&start, NULL); 66 | int nfiles = argc - 1; 67 | struct worker *workers = malloc(sizeof(struct worker) * nfiles); 68 | struct worker *w = workers; 69 | for (int i = 0; i < nfiles; ++i, ++w) { 70 | pipe(w->fd); 71 | w->id = i; 72 | if (fork() == 0) { 73 | sorter(w, argv[i + 1]); 74 | free(workers); 75 | return 0; 76 | } 77 | close(w->fd[1]); 78 | } 79 | int total_size = 0; 80 | w = workers; 81 | for (int i = 0; i < nfiles; ++i, ++w) { 82 | read_from_pipe(w->fd[0], (char *) &w->size, sizeof(w->size)); 83 | w->array = malloc(w->size * sizeof(int)); 84 | read_from_pipe(w->fd[0], (char *) w->array, 85 | w->size * sizeof(int)); 86 | printf("Got %d numbers from worker %d\n", w->size, w->id); 87 | close(w->fd[0]); 88 | wait(NULL); 89 | total_size += w->size; 90 | } 91 | int *total_array = malloc(total_size * sizeof(int)); 92 | int *pos = total_array; 93 | w = workers; 94 | for (int i = 0; i < nfiles; ++i, ++w) { 95 | memcpy(pos, w->array, w->size * sizeof(int)); 96 | pos += w->size; 97 | } 98 | struct timeval tmp; 99 | gettimeofday(&tmp, NULL); 100 | uint64_t microsecs = tmp.tv_sec * 1000000 + tmp.tv_usec - 101 | start.tv_sec * 1000000 + start.tv_usec; 102 | printf("presort time = %lf\n", (microsecs + 0.0) / 1000000); 103 | return 0; 104 | } 105 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/3_mem_sort.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define MEM_SIZE 65536 14 | #define IS_READABLE 0 15 | #define IS_WRITABLE 1 16 | #define MEM_META_SIZE 2 17 | 18 | struct worker { 19 | char *mem; 20 | int *array; 21 | int size; 22 | int id; 23 | }; 24 | 25 | int 26 | cmp(const void *a, const void *b) 27 | { 28 | return *(int *)a - *(int *)b; 29 | } 30 | 31 | void 32 | write_to_shared_mem(char *mem, const char *src, int size) 33 | { 34 | volatile char *is_readable = &mem[IS_READABLE]; 35 | volatile char *is_writable = &mem[IS_WRITABLE]; 36 | mem += MEM_META_SIZE; 37 | int mem_size = MEM_SIZE - MEM_META_SIZE; 38 | int saved_size = mem_size; 39 | char *saved_mem = mem; 40 | while (1) { 41 | while (! *is_writable) 42 | sched_yield(); 43 | int to_copy = mem_size > size ? size : mem_size; 44 | memcpy(mem, src, to_copy); 45 | size -= to_copy; 46 | mem_size -= to_copy; 47 | mem += to_copy; 48 | src += to_copy; 49 | 50 | *is_writable = 0; 51 | *is_readable = 1; 52 | if (size == 0) 53 | break; 54 | mem = saved_mem; 55 | mem_size = saved_size; 56 | } 57 | } 58 | 59 | void 60 | read_from_shared_mem(char *mem, char *dst, int size) 61 | { 62 | volatile char *is_readable = &mem[IS_READABLE]; 63 | volatile char *is_writable = &mem[IS_WRITABLE]; 64 | mem += MEM_META_SIZE; 65 | int mem_size = MEM_SIZE - MEM_META_SIZE; 66 | int saved_size = mem_size; 67 | char *saved_mem = mem; 68 | while (1) { 69 | while (! *is_readable) 70 | sched_yield(); 71 | int to_copy = mem_size > size ? size : mem_size; 72 | memcpy(dst, mem, to_copy); 73 | size -= to_copy; 74 | mem_size -= to_copy; 75 | mem += to_copy; 76 | dst += to_copy; 77 | 78 | *is_readable = 0; 79 | *is_writable = 1; 80 | if (size == 0) 81 | break; 82 | mem = saved_mem; 83 | mem_size = saved_size; 84 | } 85 | } 86 | 87 | void 88 | sorter(struct worker *worker, const char *filename) 89 | { 90 | FILE *file = fopen(filename, "r"); 91 | int size = 0; 92 | int capacity = 1024; 93 | int *array = malloc(capacity * sizeof(int)); 94 | while (fscanf(file, "%d", &array[size]) > 0) { 95 | ++size; 96 | if (size == capacity) { 97 | capacity *= 2; 98 | array = realloc(array, capacity * sizeof(int)); 99 | } 100 | } 101 | qsort(array, size, sizeof(int), cmp); 102 | fclose(file); 103 | printf("Worker %d sorted %d numbers\n", worker->id, size); 104 | write_to_shared_mem(worker->mem, (char *) &size, sizeof(size)); 105 | write_to_shared_mem(worker->mem, (char *) array, sizeof(int) * size); 106 | free(array); 107 | } 108 | 109 | int 110 | main(int argc, const char **argv) 111 | { 112 | struct timeval start; 113 | gettimeofday(&start, NULL); 114 | int nfiles = argc - 1; 115 | struct worker *workers = malloc(sizeof(struct worker) * nfiles); 116 | struct worker *w = workers; 117 | for (int i = 0; i < nfiles; ++i, ++w) { 118 | w->id = i; 119 | w->mem = mmap(NULL, MEM_SIZE, PROT_READ | PROT_WRITE, 120 | MAP_ANON | MAP_SHARED, -1, 0); 121 | w->mem[IS_READABLE] = 0; 122 | w->mem[IS_WRITABLE] = 1; 123 | if (fork() == 0) { 124 | sorter(w, argv[i + 1]); 125 | free(workers); 126 | return 0; 127 | } 128 | } 129 | int total_size = 0; 130 | w = workers; 131 | for (int i = 0; i < nfiles; ++i, ++w) { 132 | read_from_shared_mem(w->mem, (char *) &w->size, 133 | sizeof(w->size)); 134 | w->array = malloc(w->size * sizeof(int)); 135 | read_from_shared_mem(w->mem, (char *) w->array, 136 | w->size * sizeof(int)); 137 | printf("Got %d numbers from worker %d\n", w->size, w->id); 138 | wait(NULL); 139 | total_size += w->size; 140 | } 141 | int *total_array = malloc(total_size * sizeof(int)); 142 | int *pos = total_array; 143 | w = workers; 144 | for (int i = 0; i < nfiles; ++i, ++w) { 145 | memcpy(pos, w->array, w->size * sizeof(int)); 146 | pos += w->size; 147 | } 148 | struct timeval tmp; 149 | gettimeofday(&tmp, NULL); 150 | uint64_t microsecs = tmp.tv_sec * 1000000 + tmp.tv_usec - 151 | start.tv_sec * 1000000 + start.tv_usec; 152 | printf("presort time = %lf\n", (microsecs + 0.0) / 1000000); 153 | return 0; 154 | } 155 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/4_fifo_client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int main() 10 | { 11 | int fd = open("/tmp/fifo_server", O_WRONLY); 12 | pid_t pid = getpid(); 13 | write(fd, &pid, sizeof(pid)); 14 | printf("my pid %d is sent to server\n", (int) pid); 15 | close(fd); 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/4_fifo_server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | int main() 11 | { 12 | mkfifo("/tmp/fifo_server", S_IRWXU | S_IRWXO); 13 | int fd = open("/tmp/fifo_server", O_RDONLY); 14 | while (1) { 15 | pid_t new_client; 16 | if (read(fd, &new_client, sizeof(pid_t)) > 0) 17 | printf("new client %d\n", (int) new_client); 18 | else 19 | sched_yield(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/5_key_t.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void 7 | try_key(const char *path, int id) 8 | { 9 | key_t key = ftok(path, id); 10 | printf("key(\"%s\", %d) = ", path, id); 11 | if (key == -1) 12 | printf("%s\n", strerror(errno)); 13 | else 14 | printf("%d\n", (int) key); 15 | } 16 | 17 | int main(int argc, const char **argv) 18 | { 19 | try_key("/not/exising/path", 100); 20 | try_key(argv[0], 100); 21 | try_key(argv[0], 101); 22 | try_key(argv[0], 101 + 1024); 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/6_msgget.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | int main(int argc, const char **argv) 11 | { 12 | key_t key = ftok(argv[0], 0); 13 | int queue_id = msgget(key, IPC_CREAT | S_IRWXU | S_IRWXO); 14 | printf("key = %d, queue_id = %d\n", (int) key, queue_id); 15 | 16 | struct msqid_ds queue_stat; 17 | msgctl(queue_id, IPC_STAT, &queue_stat); 18 | printf("message count = %d\n", (int) queue_stat.msg_qnum); 19 | printf("max queue bytes = %d\n", (int) queue_stat.msg_qbytes); 20 | 21 | int rc = msgget(key, IPC_CREAT | IPC_EXCL); 22 | if (rc == -1) 23 | printf("second msgget returned '%s'\n", strerror(errno)); 24 | 25 | msgctl(queue_id, IPC_RMID, NULL); 26 | 27 | #ifdef IPC_INFO 28 | printf("\nSystem wide settings:\n"); 29 | struct msginfo info; 30 | msgctl(0, IPC_INFO, (struct msqid_ds *) &info); 31 | 32 | printf("max message size = %d\n", info.msgmax); 33 | printf("max queue bytes = %d\n", info.msgmnb); 34 | printf("max number of message queues = %d\n", info.msgmni); 35 | #endif 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/7_msgsend_client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int 9 | pack_msg(char *msg, long type, const char *data, int data_size) 10 | { 11 | char *pos = msg; 12 | memcpy(pos, &type, sizeof(type)); 13 | pos += sizeof(type); 14 | memcpy(pos, &data_size, sizeof(data_size)); 15 | pos += sizeof(data_size); 16 | memcpy(pos, data, data_size); 17 | pos += data_size; 18 | return pos - msg; 19 | } 20 | 21 | void 22 | send_msg(int queue_id, long type, const char *data, int data_size) 23 | { 24 | char msg[512]; 25 | int size = pack_msg(msg, type, data, data_size); 26 | msgsnd(queue_id, msg, size, 0); 27 | printf("sent %d bytes\n", size); 28 | } 29 | 30 | int 31 | main(int argc, const char **argv) 32 | { 33 | key_t key = ftok("./server", 0); 34 | int queue_id = msgget(key, 0); 35 | printf("connected to queue %d\n", queue_id); 36 | 37 | const char *text = "hello, world"; 38 | send_msg(queue_id, 100, text, strlen(text) + 1); 39 | 40 | text = "hello, world of type 50"; 41 | send_msg(queue_id, 50, text, strlen(text) + 1); 42 | 43 | text = "hello, world of type 60"; 44 | send_msg(queue_id, 60, text, strlen(text) + 1); 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/7_msgsend_server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | int 11 | unpack_msg(const char *msg, int size, long *type, const char **data, 12 | int *data_size) 13 | { 14 | const char *pos = msg; 15 | memcpy(type, pos, sizeof(*type)); 16 | pos += sizeof(type); 17 | memcpy(data_size, pos, sizeof(*data_size)); 18 | pos += sizeof(*data_size); 19 | *data = pos; 20 | pos += *data_size; 21 | return pos - msg; 22 | } 23 | 24 | void 25 | recv_msg(int queue_id, long type) 26 | { 27 | char msg[512]; 28 | ssize_t recv = msgrcv(queue_id, msg, sizeof(msg), type, 0); 29 | printf("received %d\n", (int) recv); 30 | const char *data; 31 | int data_size; 32 | unpack_msg(msg, recv, &type, &data, &data_size); 33 | printf("type = %ld, size = %d, str = %s\n", type, data_size, data); 34 | } 35 | 36 | int main(int argc, const char **argv) 37 | { 38 | key_t key = ftok("./server", 0); 39 | int queue_id = msgget(key, IPC_CREAT | S_IRWXU | S_IRWXO); 40 | printf("connected to queue %d\n", queue_id); 41 | 42 | while (1) { 43 | struct msqid_ds queue_stat; 44 | if (msgctl(queue_id, IPC_STAT, &queue_stat) != 0) { 45 | printf("error = %s\n", strerror(errno)); 46 | msgctl(queue_id, IPC_RMID, NULL); 47 | return -1; 48 | } 49 | if (queue_stat.msg_qnum > 0) { 50 | printf("message count = %d\n", 51 | (int) queue_stat.msg_qnum); 52 | break; 53 | } 54 | sched_yield(); 55 | } 56 | 57 | recv_msg(queue_id, 0); 58 | 59 | recv_msg(queue_id, 60); 60 | 61 | recv_msg(queue_id, -200); 62 | 63 | msgctl(queue_id, IPC_RMID, NULL); 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/7_msgsend_server_flag.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | int 11 | unpack_msg(const char *msg, int size, long *type, const char **data, 12 | int *data_size) 13 | { 14 | const char *pos = msg; 15 | memcpy(type, pos, sizeof(*type)); 16 | pos += sizeof(type); 17 | memcpy(data_size, pos, sizeof(*data_size)); 18 | pos += sizeof(*data_size); 19 | *data = pos; 20 | pos += *data_size; 21 | return pos - msg; 22 | } 23 | 24 | void 25 | recv_msg(int queue_id, long type) 26 | { 27 | char msg[512]; 28 | ssize_t recv; 29 | while(1) { 30 | recv = msgrcv(queue_id, msg, sizeof(msg), type, IPC_NOWAIT); 31 | if (recv != -1) 32 | break; 33 | if (errno != ENOMSG) { 34 | printf("error = %s\n", strerror(errno)); 35 | return; 36 | } 37 | } 38 | printf("received %d\n", (int) recv); 39 | const char *data; 40 | int data_size; 41 | unpack_msg(msg, recv, &type, &data, &data_size); 42 | printf("type = %ld, size = %d, str = %s\n", type, data_size, data); 43 | } 44 | 45 | int main(int argc, const char **argv) 46 | { 47 | key_t key = ftok("./server", 0); 48 | int queue_id = msgget(key, IPC_CREAT | S_IRWXU | S_IRWXO); 49 | printf("connected to queue %d\n", queue_id); 50 | 51 | recv_msg(queue_id, 0); 52 | 53 | recv_msg(queue_id, 60); 54 | 55 | recv_msg(queue_id, -200); 56 | 57 | msgctl(queue_id, IPC_RMID, NULL); 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/8_sem.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | void 11 | print_all_sems(int semid) 12 | { 13 | unsigned short values[3]; 14 | if (semctl(semid, -1, GETALL, values) == -1) { 15 | printf("error = %s\n", strerror(errno)); 16 | } else { 17 | printf("Values are %d %d %d\n", values[0], values[1], 18 | values[2]); 19 | } 20 | } 21 | 22 | void 23 | init_sems(int semid) 24 | { 25 | unsigned short values[3] = {1, 2, 3}; 26 | semctl(semid, -1, SETALL, values); 27 | printf("Sem array is initialized\n"); 28 | print_all_sems(semid); 29 | } 30 | 31 | int 32 | main() 33 | { 34 | key_t key = ftok("./a.out", 0); 35 | int semid = semget(key, 3, IPC_CREAT | IPC_EXCL | S_IRWXU | S_IRWXO); 36 | 37 | if (semid == -1) { 38 | if (errno != EEXIST) { 39 | printf("error = %s\n", strerror(errno)); 40 | return -1; 41 | } 42 | printf("Sem array already exists\n"); 43 | semid = semget(key, 0, 0); 44 | if (semid == -1) { 45 | printf("error = %s\n", strerror(errno)); 46 | return -1; 47 | } 48 | printf("Connected to %d\n", semid); 49 | } else { 50 | printf("Created sems with id %d\n", semid); 51 | init_sems(semid); 52 | } 53 | 54 | struct sembuf ops[100], *op; 55 | int ops_count = 0, sem_num; 56 | bool is_inside_txn = false; 57 | char cmd, *line = NULL; 58 | size_t line_size = 0; 59 | while (1) { 60 | getline(&line, &line_size, stdin); 61 | if (line == NULL) 62 | continue; 63 | /* Trim '\n'. */ 64 | line[strlen(line) - 1] = 0; 65 | if (strcmp(line, "begin") == 0) { 66 | is_inside_txn = true; 67 | } else if (strcmp(line, "commit") == 0) { 68 | if (semop(semid, ops, ops_count) == -1) { 69 | printf("error = %s\n", strerror(errno)); 70 | } else { 71 | is_inside_txn = false; 72 | ops_count = 0; 73 | print_all_sems(semid); 74 | } 75 | } else if (strcmp(line, "rollback") == 0) { 76 | is_inside_txn = false; 77 | ops_count = 0; 78 | } else if (strcmp(line, "delete") == 0) { 79 | if (semctl(semid, -1, IPC_RMID) == -1) 80 | printf("error = %s\n", strerror(errno)); 81 | return 0; 82 | } else if (strcmp(line, "quit") == 0) { 83 | return 0; 84 | } else if (strcmp(line, "show") == 0) { 85 | print_all_sems(semid); 86 | } else { 87 | sscanf(line, "%d %c", &sem_num, &cmd); 88 | op = &ops[ops_count]; 89 | if (cmd == '-') { 90 | op->sem_op = -1; 91 | } else if (cmd == '+') { 92 | op->sem_op = 1; 93 | } else { 94 | printf("Unknown operation\n"); 95 | continue; 96 | } 97 | op->sem_num = sem_num - 1; 98 | op->sem_flg = SEM_UNDO; 99 | if (! is_inside_txn) { 100 | if (semop(semid, op, 1) == -1) 101 | printf("error = %s\n", strerror(errno)); 102 | else 103 | print_all_sems(semid); 104 | } else { 105 | ops_count++; 106 | } 107 | } 108 | } 109 | return 0; 110 | } 111 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/9_sem_event_cons.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | int 13 | unpack_msg(const char *msg, int size, long *type, const char **data, 14 | int *data_size) 15 | { 16 | const char *pos = msg; 17 | memcpy(type, pos, sizeof(*type)); 18 | pos += sizeof(type); 19 | memcpy(data_size, pos, sizeof(*data_size)); 20 | pos += sizeof(*data_size); 21 | *data = pos; 22 | pos += *data_size; 23 | return pos - msg; 24 | } 25 | 26 | void 27 | recv_msg(int queueid, long type) 28 | { 29 | char msg[512]; 30 | ssize_t recv = msgrcv(queueid, msg, sizeof(msg), type, 0); 31 | printf("Received %d\n", (int) recv); 32 | const char *data; 33 | int data_size; 34 | unpack_msg(msg, recv, &type, &data, &data_size); 35 | printf("Type = %ld, size = %d, str = %s\n", type, data_size, data); 36 | } 37 | 38 | void 39 | read_data(int queueid) 40 | { 41 | for (int i = 0; i < 3; ++i) 42 | recv_msg(queueid, i + 1); 43 | } 44 | 45 | void 46 | unlock_events(int semid) 47 | { 48 | struct sembuf op; 49 | op.sem_flg = SEM_UNDO; 50 | op.sem_num = 3; 51 | op.sem_op = 1; 52 | semop(semid, &op, 1); 53 | } 54 | 55 | void 56 | lock_events(int semid) 57 | { 58 | struct sembuf op[4]; 59 | for (int i = 0; i < 4; ++i) { 60 | op[i].sem_flg = SEM_UNDO; 61 | op[i].sem_num = i; 62 | op[i].sem_op = -1; 63 | } 64 | semop(semid, op, 4); 65 | } 66 | 67 | int 68 | main() 69 | { 70 | key_t key = ftok("./event_gen", 0); 71 | int semid = semget(key, 4, 0); 72 | int queueid = msgget(key, 0); 73 | 74 | while(1) { 75 | lock_events(semid); 76 | read_data(queueid); 77 | unlock_events(semid); 78 | } 79 | return 0; 80 | } 81 | -------------------------------------------------------------------------------- /lecture_examples/7_ipc/9_sem_event_source.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | int 13 | sem_get_or_create(key_t key) 14 | { 15 | int semid = semget(key, 4, IPC_CREAT | IPC_EXCL | S_IRWXU | S_IRWXO); 16 | if (semid == -1) { 17 | if (errno != EEXIST) { 18 | printf("error = %s\n", strerror(errno)); 19 | return -1; 20 | } 21 | semid = semget(key, 0, 0); 22 | if (semid == -1) { 23 | printf("error = %s\n", strerror(errno)); 24 | return -1; 25 | } 26 | printf("Connected to sems %d\n", semid); 27 | } else { 28 | printf("Created sems with id %d\n", semid); 29 | unsigned short values[4] = {0, 0, 0, 1}; 30 | semctl(semid, -1, SETALL, values); 31 | } 32 | return semid; 33 | } 34 | 35 | void 36 | sem_inc(int semid, int myid) 37 | { 38 | struct sembuf op; 39 | op.sem_flg = SEM_UNDO; 40 | op.sem_num = myid - 1; 41 | op.sem_op = 1; 42 | semop(semid, &op, 1); 43 | } 44 | 45 | int 46 | msg_queue_get_or_create(key_t key) 47 | { 48 | int queueid = msgget(key, IPC_CREAT | S_IRWXU | S_IRWXO); 49 | if (queueid == -1) 50 | printf("error = %s\n", strerror(errno)); 51 | else 52 | printf("Created/connected to queue %d\n", queueid); 53 | return queueid; 54 | } 55 | 56 | int 57 | pack_msg(char *msg, long type, const char *data, int data_size) 58 | { 59 | char *pos = msg; 60 | memcpy(pos, &type, sizeof(type)); 61 | pos += sizeof(type); 62 | memcpy(pos, &data_size, sizeof(data_size)); 63 | pos += sizeof(data_size); 64 | memcpy(pos, data, data_size); 65 | pos += data_size; 66 | return pos - msg; 67 | } 68 | 69 | void 70 | send_msg(int queueid, int myid, const char *data, int data_size) 71 | { 72 | char msg[512]; 73 | int size = pack_msg(msg, myid, data, data_size); 74 | msgsnd(queueid, msg, size, 0); 75 | printf("Sent %d bytes\n", size); 76 | } 77 | 78 | int 79 | main(int argc, const char **argv) 80 | { 81 | if (argc < 2) { 82 | printf("No id\n"); 83 | return -1; 84 | } 85 | key_t key = ftok("./event_gen", 0); 86 | int semid = sem_get_or_create(key); 87 | if (semid == -1) 88 | return -1; 89 | int queueid = msg_queue_get_or_create(key); 90 | if (queueid == -1) 91 | return -1; 92 | 93 | int myid = atoi(argv[1]); 94 | 95 | char *line = NULL; 96 | size_t line_size = 0; 97 | while(1) { 98 | if (getline(&line, &line_size, stdin) <= 0) 99 | break; 100 | send_msg(queueid, myid, line, strlen(line)); 101 | sem_inc(semid, myid); 102 | } 103 | if (myid == 1) { 104 | msgctl(queueid, IPC_RMID, NULL); 105 | semctl(semid, -1, IPC_RMID); 106 | } 107 | return 0; 108 | } 109 | -------------------------------------------------------------------------------- /lecture_examples/8_net/1_socket_protocol.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void 9 | try_protocol(int type, int protocol, const char *protocol_name) 10 | { 11 | int sock = socket(AF_INET, type, protocol); 12 | if (sock == -1) { 13 | printf("%s: error = %s\n", protocol_name, strerror(errno)); 14 | } else { 15 | printf("%s: success\n", protocol_name); 16 | close(sock); 17 | } 18 | } 19 | 20 | void 21 | try_type(int type, const char *type_name) 22 | { 23 | printf("\nTry %s type\n", type_name); 24 | try_protocol(type, IPPROTO_TCP, "TCP"); 25 | try_protocol(type, IPPROTO_IP, "IP"); 26 | try_protocol(type, IPPROTO_SCTP, "SCTP"); 27 | try_protocol(type, IPPROTO_RAW, "RAW"); 28 | try_protocol(type, IPPROTO_UDP, "UDP"); 29 | } 30 | 31 | int 32 | main() 33 | { 34 | try_type(SOCK_DGRAM, "DGRAM"); 35 | try_type(SOCK_RAW, "RAW"); 36 | try_type(SOCK_STREAM, "STREAM"); 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /lecture_examples/8_net/2_getaddrinfo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int 8 | main() 9 | { 10 | struct addrinfo *addr, *iter; 11 | int rc = getaddrinfo("yandex.ru", NULL, NULL, &addr); 12 | if (rc != 0) { 13 | printf("Error = %s\n", gai_strerror(rc)); 14 | return -1; 15 | } 16 | printf("Families: inet = %d, inet6 = %d\n", AF_INET, AF_INET6); 17 | printf("Socket types: dgram = %d, stream = %d, raw = %d\n", SOCK_DGRAM, 18 | SOCK_STREAM, SOCK_RAW); 19 | printf("Protocols: tcp = %d, udp = %d\n\n", IPPROTO_TCP, IPPROTO_UDP); 20 | for (iter = addr; iter != NULL; iter = iter->ai_next) { 21 | printf("family = %d, socktype = %d, protocol = %d", 22 | iter->ai_family, iter->ai_socktype, iter->ai_protocol); 23 | if (iter->ai_family == AF_INET) { 24 | char buf[128]; 25 | struct sockaddr_in *tmp = 26 | (struct sockaddr_in *)iter->ai_addr; 27 | inet_ntop(AF_INET, &tmp->sin_addr, buf, sizeof(buf)); 28 | printf(", ip = %s", buf); 29 | } 30 | printf("\n"); 31 | } 32 | freeaddrinfo(addr); 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /lecture_examples/8_net/3_client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | int 14 | main(int argc, const char **argv) 15 | { 16 | int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 17 | if (sock == -1) { 18 | printf("error = %s\n", strerror(errno)); 19 | return -1; 20 | } 21 | struct addrinfo *addr; 22 | struct addrinfo filter; 23 | memset(&filter, 0, sizeof(filter)); 24 | filter.ai_family = AF_INET; 25 | filter.ai_socktype = SOCK_STREAM; 26 | int rc = getaddrinfo(argv[1], argv[2], &filter, &addr); 27 | if (rc != 0) { 28 | printf("addrinfo error = %s\n", gai_strerror(rc)); 29 | close(sock); 30 | return -1; 31 | } 32 | if (addr == NULL) { 33 | printf("not found a server\n"); 34 | freeaddrinfo(addr); 35 | close(sock); 36 | return -1; 37 | } 38 | rc = connect(sock, addr->ai_addr, addr->ai_addrlen); 39 | freeaddrinfo(addr); 40 | if (rc != 0) { 41 | printf("connect error = %s\n", strerror(errno)); 42 | close(sock); 43 | return -1; 44 | } 45 | int number; 46 | while (scanf("%d", &number) > 0) { 47 | if (write(sock, &number, sizeof(number)) == -1) { 48 | printf("error = %s\n", strerror(errno)); 49 | continue; 50 | } 51 | printf("Sent %d\n", number); 52 | number = 0; 53 | int rc = read(sock, &number, sizeof(number)); 54 | if (rc == 0) { 55 | printf("Closed connection\n"); 56 | break; 57 | } 58 | if (rc == -1) 59 | printf("error = %s\n", strerror(errno)); 60 | else 61 | printf("Received %d\n", number); 62 | } 63 | close(sock); 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /lecture_examples/8_net/3_server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void * 15 | worker_f(void *arg) 16 | { 17 | printf("New client created\n"); 18 | int client_sock = (int) arg; 19 | while(1) { 20 | int buffer = 0; 21 | ssize_t size = read(client_sock, &buffer, sizeof(buffer)); 22 | if (size == -1) { 23 | printf("error = %s\n", strerror(errno)); 24 | continue; 25 | } 26 | if (size == 0) { 27 | printf("Closed connection\n"); 28 | break; 29 | } 30 | printf("Received %d\n", buffer); 31 | buffer++; 32 | if (write(client_sock, &buffer, sizeof(buffer)) == -1) 33 | printf("error = %s\n", strerror(errno)); 34 | else 35 | printf("Sent %d\n", buffer); 36 | } 37 | close(client_sock); 38 | return NULL; 39 | } 40 | 41 | int 42 | main(int argc, const char **argv) 43 | { 44 | int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 45 | if (sock == -1) { 46 | printf("error = %s\n", strerror(errno)); 47 | return -1; 48 | } 49 | struct sockaddr_in addr; 50 | addr.sin_family = AF_INET; 51 | addr.sin_port = htons(12345); 52 | inet_aton("127.0.0.1", &addr.sin_addr); 53 | 54 | if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) != 0) { 55 | printf("bind error = %s\n", strerror(errno)); 56 | return -1; 57 | } 58 | if (listen(sock, 128) == -1) { 59 | printf("listen error = %s\n", strerror(errno)); 60 | return -1; 61 | } 62 | pthread_attr_t attr; 63 | pthread_attr_init(&attr); 64 | pthread_attr_setdetachstate(&attr, 1); 65 | while(1) { 66 | pthread_t worker_thread; 67 | int client_sock = accept(sock, NULL, NULL); 68 | if (client_sock == -1) { 69 | printf("error = %s\n", strerror(errno)); 70 | continue; 71 | } 72 | int rc = pthread_create(&worker_thread, &attr, worker_f, 73 | (void *) client_sock); 74 | if (rc != 0) { 75 | printf("error = %s\n", strerror(rc)); 76 | close(client_sock); 77 | } 78 | } 79 | pthread_attr_destroy(&attr); 80 | close(sock); 81 | return 0; 82 | } 83 | -------------------------------------------------------------------------------- /lecture_examples/9_aio/1_nonblock.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void 8 | make_fd_nonblocking(int fd) 9 | { 10 | int old_flags = fcntl(fd, F_GETFL); 11 | fcntl(fd, F_SETFL, old_flags | O_NONBLOCK); 12 | } 13 | 14 | int 15 | main(int argc, const char **argv) 16 | { 17 | make_fd_nonblocking(STDIN_FILENO); 18 | int value = 0; 19 | int rc = scanf("%d", &value); 20 | printf("scanf rc = %d\n", rc); 21 | printf("scanf error = %s\n", strerror(errno)); 22 | rc = read(STDIN_FILENO, &value, sizeof(value)); 23 | printf("read rc = %d\n", rc); 24 | printf("read error = %s\n", strerror(errno)); 25 | 26 | while (1) { 27 | rc = scanf("%d", &value); 28 | if (rc > 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) 29 | break; 30 | } 31 | printf("value = %d\n", value); 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /lecture_examples/9_aio/2_flock.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int 9 | main() 10 | { 11 | int fd = open("tmp.txt", O_CREAT | O_RDWR, S_IRWXU); 12 | if (fd == -1) { 13 | printf("error = %s\n", strerror(errno)); 14 | return -1; 15 | } 16 | char cmd[100]; 17 | while (scanf("%s", cmd) > 0) { 18 | int rc; 19 | if (strcmp(cmd, "excl") == 0) { 20 | rc = flock(fd, LOCK_EX); 21 | } else if (strcmp(cmd, "exclnb") == 0) { 22 | rc = flock(fd, LOCK_EX | LOCK_NB); 23 | } else if (strcmp(cmd, "shared") == 0) { 24 | rc = flock(fd, LOCK_SH); 25 | } else if (strcmp(cmd, "sharednb") == 0) { 26 | rc = flock(fd, LOCK_SH | LOCK_NB); 27 | } else if (strcmp(cmd, "unlock") == 0) { 28 | rc = flock(fd, LOCK_UN); 29 | } else if (strcmp(cmd, "write") == 0) { 30 | rc = write(fd, "data", 4); 31 | } else { 32 | printf("unknown command\n"); 33 | continue; 34 | } 35 | if (rc == -1) 36 | printf("error = %s\n", strerror(errno)); 37 | else 38 | printf("ok\n"); 39 | } 40 | close(fd); 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /lecture_examples/9_aio/3_fcntl_lock.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int 9 | write_symbols(int fd, char *cmd) 10 | { 11 | int n; 12 | char c; 13 | sscanf(cmd, "%d %c", &n, &c); 14 | for (int i = 0; i < n; ++i) { 15 | if (write(fd, &c, sizeof(c)) == -1) 16 | return -1; 17 | } 18 | return 0; 19 | } 20 | 21 | int 22 | char_to_whence(char whence) 23 | { 24 | if (whence == 's') 25 | return SEEK_SET; 26 | if (whence == 'e') 27 | return SEEK_END; 28 | return SEEK_CUR; 29 | } 30 | 31 | int 32 | char_to_type(char type) 33 | { 34 | if (type == 'r') 35 | return F_RDLCK; 36 | if (type == 'w') 37 | return F_WRLCK; 38 | return F_UNLCK; 39 | } 40 | 41 | int 42 | do_lock(int fd, bool block, char *cmd) 43 | { 44 | char whence, type; 45 | int start, len; 46 | sscanf(cmd, "%c %c %d %d", &type, &whence, &start, &len); 47 | printf("type = %c, whence = %c, start = %d, len = %d\n", type, whence, 48 | start, len); 49 | struct flock fl; 50 | fl.l_type = char_to_type(type); 51 | fl.l_whence = char_to_whence(whence); 52 | fl.l_start = start; 53 | fl.l_len = len; 54 | return fcntl(fd, block ? F_SETLKW : F_SETLK, &fl); 55 | } 56 | 57 | int 58 | get_lock(int fd, char *cmd) 59 | { 60 | char whence, type; 61 | int start, len; 62 | sscanf(cmd, "%c %c %d %d", &type, &whence, &start, &len); 63 | printf("type = %c, whence = %c, start = %d, len = %d\n", type, whence, 64 | start, len); 65 | struct flock fl; 66 | fl.l_type = char_to_type(type); 67 | fl.l_whence = char_to_whence(whence); 68 | fl.l_start = start; 69 | fl.l_len = len; 70 | if (fcntl(fd, F_GETLK, &fl) == -1) 71 | return -1; 72 | if (fl.l_type == F_UNLCK) 73 | printf("no lock on this region\n"); 74 | else 75 | printf("process %d holds the lock\n", (int)fl.l_pid); 76 | return 0; 77 | } 78 | 79 | int 80 | main() 81 | { 82 | printf("my pid = %d\n", (int) getpid()); 83 | int fd = open("tmp.txt", O_CREAT | O_RDWR, S_IRWXU); 84 | if (fd == -1) { 85 | printf("error = %s\n", strerror(errno)); 86 | return -1; 87 | } 88 | char *buf = NULL; 89 | size_t size = 0; 90 | while (getline(&buf, &size, stdin) > 0) { 91 | char *line = buf; 92 | line[strlen(line) - 1] = 0; 93 | int rc; 94 | printf("line = %s\n", line); 95 | char *cmd = strsep(&line, " \n"); 96 | printf("args = %s\n", line); 97 | printf("cmd = %s\n", cmd); 98 | if (strcmp(cmd, "write") == 0) { 99 | rc = write_symbols(fd, line); 100 | } else if (strcmp(cmd, "lock") == 0) { 101 | rc = do_lock(fd, false, line); 102 | } else if (strcmp(cmd, "lockb") == 0) { 103 | rc = do_lock(fd, true, line); 104 | } else if (strcmp(cmd, "getlock") == 0) { 105 | rc = get_lock(fd, line); 106 | } else { 107 | printf("unknown command\n"); 108 | continue; 109 | } 110 | if (rc == -1) 111 | printf("error = %s\n", strerror(errno)); 112 | else 113 | printf("ok\n"); 114 | } 115 | close(fd); 116 | return 0; 117 | } 118 | -------------------------------------------------------------------------------- /lecture_examples/9_aio/4_client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | int 14 | main(int argc, const char **argv) 15 | { 16 | int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 17 | if (sock == -1) { 18 | printf("error = %s\n", strerror(errno)); 19 | return -1; 20 | } 21 | struct sockaddr_in addr; 22 | addr.sin_family = AF_INET; 23 | addr.sin_port = htons(12345); 24 | inet_aton("127.0.0.1", &addr.sin_addr); 25 | if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) != 0) { 26 | printf("connect error = %s\n", strerror(errno)); 27 | close(sock); 28 | return -1; 29 | } 30 | int number; 31 | while (scanf("%d", &number) > 0) { 32 | if (write(sock, &number, sizeof(number)) == -1) { 33 | printf("error = %s\n", strerror(errno)); 34 | continue; 35 | } 36 | printf("Sent %d\n", number); 37 | number = 0; 38 | int rc = read(sock, &number, sizeof(number)); 39 | if (rc == 0) { 40 | printf("Closed connection\n"); 41 | break; 42 | } 43 | if (rc == -1) 44 | printf("error = %s\n", strerror(errno)); 45 | else 46 | printf("Received %d\n", number); 47 | } 48 | close(sock); 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /lecture_examples/9_aio/4_server_select.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | int 15 | interact(int client_sock) 16 | { 17 | int buffer = 0; 18 | ssize_t size = read(client_sock, &buffer, sizeof(buffer)); 19 | if (size <= 0) 20 | return (int) size; 21 | printf("Received %d\n", buffer); 22 | buffer++; 23 | size = write(client_sock, &buffer, sizeof(buffer)); 24 | if (size > 0) 25 | printf("Sent %d\n", buffer); 26 | return (int) size; 27 | } 28 | 29 | int 30 | fill_fdset(fd_set *set, int *clients, int client_count, int server) 31 | { 32 | int max_fd = server; 33 | FD_ZERO(set); 34 | FD_SET(server, set); 35 | for (int i = 0; i < client_count; ++i) { 36 | FD_SET(clients[i], set); 37 | if (clients[i] > max_fd) 38 | max_fd = clients[i]; 39 | } 40 | return max_fd; 41 | } 42 | 43 | void 44 | remove_client(int **clients, int *client_count, int i) 45 | { 46 | --*client_count; 47 | int *new_clients = malloc(*client_count * sizeof(int)); 48 | memcpy(new_clients, *clients, i * sizeof(int)); 49 | memcpy(new_clients + i, *clients + i + 1, 50 | (*client_count - i) * sizeof(int)); 51 | free(*clients); 52 | *clients = new_clients; 53 | } 54 | 55 | int 56 | main(int argc, const char **argv) 57 | { 58 | int server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 59 | if (server == -1) { 60 | printf("error = %s\n", strerror(errno)); 61 | return -1; 62 | } 63 | struct sockaddr_in addr; 64 | addr.sin_family = AF_INET; 65 | addr.sin_port = htons(12345); 66 | inet_aton("127.0.0.1", &addr.sin_addr); 67 | 68 | if (bind(server, (struct sockaddr *) &addr, sizeof(addr)) != 0) { 69 | printf("bind error = %s\n", strerror(errno)); 70 | return -1; 71 | } 72 | if (listen(server, 128) == -1) { 73 | printf("listen error = %s\n", strerror(errno)); 74 | return -1; 75 | } 76 | int *clients = NULL; 77 | int client_count = 0; 78 | fd_set readset; 79 | while(1) { 80 | int max_fd = fill_fdset(&readset, clients, client_count, 81 | server); 82 | struct timeval timeval; 83 | timeval.tv_sec = 2; 84 | timeval.tv_usec = 0; 85 | int nfds = select(max_fd + 1, &readset, NULL, NULL, &timeval); 86 | if (nfds == 0) { 87 | printf("Timeout\n"); 88 | continue; 89 | } 90 | if (nfds == -1) { 91 | printf("error = %s\n", strerror(errno)); 92 | break; 93 | } 94 | if (FD_ISSET(server, &readset)) { 95 | int client_sock = accept(server, NULL, NULL); 96 | if (client_sock == -1) { 97 | printf("error = %s\n", strerror(errno)); 98 | break; 99 | } 100 | printf("New client\n"); 101 | client_count++; 102 | clients = realloc(clients, client_count * sizeof(int)); 103 | clients[client_count - 1] = client_sock; 104 | nfds--; 105 | } 106 | for (int i = 0; i < client_count && nfds > 0; ++i) { 107 | if (! FD_ISSET(clients[i], &readset)) 108 | continue; 109 | nfds--; 110 | printf("Interact with fd %d\n", clients[i]); 111 | int rc = interact(clients[i]); 112 | if (rc == -1) { 113 | printf("error = %s\n", strerror(errno)); 114 | break; 115 | } 116 | if (rc == 0) { 117 | printf("Client disconnected\n"); 118 | remove_client(&clients, &client_count, i); 119 | } 120 | } 121 | } 122 | close(server); 123 | for (int i = 0; i < client_count; ++i) 124 | close(clients[i]); 125 | free(clients); 126 | return 0; 127 | } 128 | -------------------------------------------------------------------------------- /lecture_examples/9_aio/5_server_poll.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | int 16 | interact(int client_sock) 17 | { 18 | int buffer = 0; 19 | ssize_t size = read(client_sock, &buffer, sizeof(buffer)); 20 | if (size <= 0) 21 | return (int) size; 22 | printf("Received %d\n", buffer); 23 | buffer++; 24 | size = write(client_sock, &buffer, sizeof(buffer)); 25 | if (size > 0) 26 | printf("Sent %d\n", buffer); 27 | return (int) size; 28 | } 29 | 30 | void 31 | remove_client(struct pollfd **fds, int *fd_count, int i) 32 | { 33 | --*fd_count; 34 | struct pollfd *new_fds = malloc(*fd_count * sizeof(new_fds[0])); 35 | memcpy(new_fds, *fds, i * sizeof(new_fds[0])); 36 | memcpy(new_fds + i, *fds + i + 1, 37 | (*fd_count - i) * sizeof(new_fds[0])); 38 | free(*fds); 39 | *fds = new_fds; 40 | } 41 | 42 | int 43 | main(int argc, const char **argv) 44 | { 45 | int server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 46 | if (server == -1) { 47 | printf("error = %s\n", strerror(errno)); 48 | return -1; 49 | } 50 | struct sockaddr_in addr; 51 | addr.sin_family = AF_INET; 52 | addr.sin_port = htons(12345); 53 | inet_aton("127.0.0.1", &addr.sin_addr); 54 | 55 | if (bind(server, (struct sockaddr *) &addr, sizeof(addr)) != 0) { 56 | printf("bind error = %s\n", strerror(errno)); 57 | return -1; 58 | } 59 | if (listen(server, 128) == -1) { 60 | printf("listen error = %s\n", strerror(errno)); 61 | return -1; 62 | } 63 | struct pollfd *fds = malloc(sizeof(fds[0])); 64 | int fd_count = 1; 65 | fds[0].fd = server; 66 | fds[0].events = POLLIN; 67 | while(1) { 68 | int nfds = poll(fds, fd_count, 2000); 69 | if (nfds == 0) { 70 | printf("Timeout\n"); 71 | continue; 72 | } 73 | if (nfds == -1) { 74 | printf("error = %s\n", strerror(errno)); 75 | break; 76 | } 77 | if ((fds[0].revents & POLLIN) != 0) { 78 | int client_sock = accept(server, NULL, NULL); 79 | if (client_sock == -1) { 80 | printf("error = %s\n", strerror(errno)); 81 | break; 82 | } 83 | printf("New client\n"); 84 | fd_count++; 85 | fds = realloc(fds, fd_count * sizeof(fds[0])); 86 | fds[fd_count - 1].fd = client_sock; 87 | fds[fd_count - 1].events = POLLIN; 88 | nfds--; 89 | } 90 | for (int i = 0; i < fd_count && nfds > 0; ++i) { 91 | if ((fds[i].revents & POLLIN) == 0) 92 | continue; 93 | nfds--; 94 | printf("Interact with fd %d\n", fds[i].fd); 95 | int rc = interact(fds[i].fd); 96 | if (rc == -1) { 97 | printf("error = %s\n", strerror(errno)); 98 | break; 99 | } 100 | if (rc == 0) { 101 | printf("Client disconnected\n"); 102 | remove_client(&fds, &fd_count, i); 103 | } 104 | } 105 | } 106 | for (int i = 0; i < fd_count; ++i) 107 | close(fds[i].fd); 108 | free(fds); 109 | close(server); 110 | return 0; 111 | } 112 | -------------------------------------------------------------------------------- /lecture_examples/9_aio/6_server_kqueue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | int 18 | interact(int client_sock) 19 | { 20 | int buffer = 0; 21 | ssize_t size = read(client_sock, &buffer, sizeof(buffer)); 22 | if (size <= 0) 23 | return (int) size; 24 | printf("Received %d\n", buffer); 25 | buffer++; 26 | size = write(client_sock, &buffer, sizeof(buffer)); 27 | if (size > 0) 28 | printf("Sent %d\n", buffer); 29 | return (int) size; 30 | } 31 | 32 | int 33 | main(int argc, const char **argv) 34 | { 35 | int server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 36 | if (server == -1) { 37 | printf("error = %s\n", strerror(errno)); 38 | return -1; 39 | } 40 | struct sockaddr_in addr; 41 | addr.sin_family = AF_INET; 42 | addr.sin_port = htons(12345); 43 | inet_aton("127.0.0.1", &addr.sin_addr); 44 | 45 | if (bind(server, (struct sockaddr *) &addr, sizeof(addr)) != 0) { 46 | printf("bind error = %s\n", strerror(errno)); 47 | return -1; 48 | } 49 | if (listen(server, 128) == -1) { 50 | printf("listen error = %s\n", strerror(errno)); 51 | return -1; 52 | } 53 | int kq = kqueue(); 54 | if (kq == -1) { 55 | printf("error = %s\n", strerror(errno)); 56 | close(server); 57 | return -1; 58 | } 59 | struct kevent new_ev; 60 | EV_SET(&new_ev, server, EVFILT_READ, EV_ADD, 0, 0, 0); 61 | if (kevent(kq, &new_ev, 1, 0, 0, NULL) == -1) { 62 | printf("error = %s\n", strerror(errno)); 63 | close(server); 64 | return -1; 65 | } 66 | struct timespec timeout; 67 | timeout.tv_sec = 2; 68 | timeout.tv_nsec = 0; 69 | while(1) { 70 | int nfds = kevent(kq, NULL, 0, &new_ev, 1, &timeout); 71 | if (nfds == 0) { 72 | printf("Timeout\n"); 73 | continue; 74 | } 75 | if (nfds == -1) { 76 | printf("error = %s\n", strerror(errno)); 77 | break; 78 | } 79 | if (new_ev.ident == server) { 80 | int client_sock = accept(server, NULL, NULL); 81 | if (client_sock == -1) { 82 | printf("error = %s\n", strerror(errno)); 83 | break; 84 | } 85 | printf("New client\n"); 86 | EV_SET(&new_ev, client_sock, EVFILT_READ, EV_ADD, 87 | 0, 0, 0); 88 | if (kevent(kq, &new_ev, 1, 0, 0, NULL) == -1) { 89 | printf("error = %s\n", strerror(errno)); 90 | break; 91 | } 92 | } else { 93 | printf("Interact with fd %d\n", (int)new_ev.ident); 94 | int rc = interact(new_ev.ident); 95 | if (rc == -1) { 96 | printf("error = %s\n", strerror(errno)); 97 | break; 98 | } 99 | if ((new_ev.flags & EV_EOF) != 0) { 100 | printf("Client disconnected\n"); 101 | close(new_ev.ident); 102 | } 103 | } 104 | } 105 | close(kq); 106 | close(server); 107 | return 0; 108 | } 109 | -------------------------------------------------------------------------------- /lecture_examples/9_aio/7_server_epoll.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | int 18 | interact(int client_sock) 19 | { 20 | int buffer = 0; 21 | ssize_t size = read(client_sock, &buffer, sizeof(buffer)); 22 | if (size <= 0) 23 | return (int) size; 24 | printf("Received %d\n", buffer); 25 | buffer++; 26 | size = write(client_sock, &buffer, sizeof(buffer)); 27 | if (size > 0) 28 | printf("Sent %d\n", buffer); 29 | return (int) size; 30 | } 31 | 32 | int 33 | main(int argc, const char **argv) 34 | { 35 | int server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 36 | if (server == -1) { 37 | printf("error = %s\n", strerror(errno)); 38 | return -1; 39 | } 40 | struct sockaddr_in addr; 41 | addr.sin_family = AF_INET; 42 | addr.sin_port = htons(12345); 43 | inet_aton("127.0.0.1", &addr.sin_addr); 44 | 45 | if (bind(server, (struct sockaddr *) &addr, sizeof(addr)) != 0) { 46 | printf("bind error = %s\n", strerror(errno)); 47 | return -1; 48 | } 49 | if (listen(server, 128) == -1) { 50 | printf("listen error = %s\n", strerror(errno)); 51 | return -1; 52 | } 53 | int ep = epoll_create(1); 54 | if (ep == -1) { 55 | printf("error = %s\n", strerror(errno)); 56 | close(server); 57 | return -1; 58 | } 59 | struct epoll_event new_ev; 60 | new_ev.data.fd = server; 61 | new_ev.events = EPOLLIN; 62 | if (epoll_ctl(ep, EPOLL_CTL_ADD, server, &new_ev) == -1) { 63 | printf("error = %s\n", strerror(errno)); 64 | close(server); 65 | return -1; 66 | } 67 | while(1) { 68 | int nfds = epoll_wait(ep, &new_ev, 1, 2000); 69 | if (nfds == 0) { 70 | printf("Timeout\n"); 71 | continue; 72 | } 73 | if (nfds == -1) { 74 | printf("error = %s\n", strerror(errno)); 75 | break; 76 | } 77 | if (new_ev.data.fd == server) { 78 | int client_sock = accept(server, NULL, NULL); 79 | if (client_sock == -1) { 80 | printf("error = %s\n", strerror(errno)); 81 | break; 82 | } 83 | printf("New client\n"); 84 | new_ev.data.fd = client_sock; 85 | new_ev.events = EPOLLIN; 86 | if (epoll_ctl(ep, EPOLL_CTL_ADD, client_sock, 87 | &new_ev) == -1) { 88 | printf("error = %s\n", strerror(errno)); 89 | break; 90 | } 91 | } else { 92 | printf("Interact with fd %d\n", (int)new_ev.data.fd); 93 | int rc = interact(new_ev.data.fd); 94 | if (rc == -1) { 95 | printf("error = %s\n", strerror(errno)); 96 | break; 97 | } 98 | if (rc == 0) { 99 | printf("Client disconnected\n"); 100 | close(new_ev.data.fd); 101 | epoll_ctl(ep, EPOLL_CTL_DEL, new_ev.data.fd, 102 | NULL); 103 | } 104 | } 105 | } 106 | close(ep); 107 | close(server); 108 | return 0; 109 | } 110 | -------------------------------------------------------------------------------- /lectures/0. Вводная лекция.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarantool/sysprog/5179a94abfca859214371cb67064715a83cc14e4/lectures/0. Вводная лекция.pptx -------------------------------------------------------------------------------- /lectures/1. Планировщики процессов.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarantool/sysprog/5179a94abfca859214371cb67064715a83cc14e4/lectures/1. Планировщики процессов.pptx -------------------------------------------------------------------------------- /lectures/10. Пользователи, группы, права. Атрибуты и права доступа файлов и процессов.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarantool/sysprog/5179a94abfca859214371cb67064715a83cc14e4/lectures/10. Пользователи, группы, права. Атрибуты и права доступа файлов и процессов.pptx -------------------------------------------------------------------------------- /lectures/2. Процесс. Режимы работы, память, ресурсы. Прерывания. Взаимодействие с ядром.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarantool/sysprog/5179a94abfca859214371cb67064715a83cc14e4/lectures/2. Процесс. Режимы работы, память, ресурсы. Прерывания. Взаимодействие с ядром.pptx -------------------------------------------------------------------------------- /lectures/3. Память. Виртуальная и физическая. Уровни кэша, кэш линия.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarantool/sysprog/5179a94abfca859214371cb67064715a83cc14e4/lectures/3. Память. Виртуальная и физическая. Уровни кэша, кэш линия.pptx -------------------------------------------------------------------------------- /lectures/4. Сигналы. Аппаратные и программные прерывания, их природа. Top and bottom halves. Сигналы и системные вызовы, контекст сигнала.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarantool/sysprog/5179a94abfca859214371cb67064715a83cc14e4/lectures/4. Сигналы. Аппаратные и программные прерывания, их природа. Top and bottom halves. Сигналы и системные вызовы, контекст сигнала.pptx -------------------------------------------------------------------------------- /lectures/5. Файловая система.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarantool/sysprog/5179a94abfca859214371cb67064715a83cc14e4/lectures/5. Файловая система.pptx -------------------------------------------------------------------------------- /lectures/6. Потоки. Отличие от процессов. Атомарные операции.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarantool/sysprog/5179a94abfca859214371cb67064715a83cc14e4/lectures/6. Потоки. Отличие от процессов. Атомарные операции.pptx -------------------------------------------------------------------------------- /lectures/7. IPC. Pipe, FIFO. XSI и POSIX. Сокеты.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarantool/sysprog/5179a94abfca859214371cb67064715a83cc14e4/lectures/7. IPC. Pipe, FIFO. XSI и POSIX. Сокеты.pptx -------------------------------------------------------------------------------- /lectures/8. Сеть. Модели TCPIP OSI. Связь с ядром. Интерфейсы и примеры.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarantool/sysprog/5179a94abfca859214371cb67064715a83cc14e4/lectures/8. Сеть. Модели TCPIP OSI. Связь с ядром. Интерфейсы и примеры.pptx -------------------------------------------------------------------------------- /lectures/9. Неблокирующие IO операции. Мультиплексирование.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarantool/sysprog/5179a94abfca859214371cb67064715a83cc14e4/lectures/9. Неблокирующие IO операции. Мультиплексирование.pptx -------------------------------------------------------------------------------- /tasks/1/checker.py: -------------------------------------------------------------------------------- 1 | import random 2 | import argparse 3 | 4 | maxint = 1 << 31 5 | 6 | parser = argparse.ArgumentParser(description = "Check that a file contains "\ 7 | "not decreasing sequence of "\ 8 | "numbers") 9 | parser.add_argument('-f', type=str, required=True, help="file name") 10 | args = parser.parse_args() 11 | 12 | 13 | f = open(args.f, 'r') 14 | data = f.read() 15 | f.close() 16 | 17 | data = data.split() 18 | prev_number = -(1 << 31 - 1) 19 | for i in range(0, len(data)): 20 | try: 21 | v = int(data[i]) 22 | if v < prev_number: 23 | print('Error on numbers {} {}'.format(prev_number, v)) 24 | exit(1) 25 | prev_number = v 26 | except: 27 | pass 28 | 29 | print('All is ok') 30 | -------------------------------------------------------------------------------- /tasks/1/coro_jmp.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #ifndef CORO_LOCAL_DATA 8 | #error "You are expected to specify what you want to store in a coroutine "\ 9 | "via CORO_LOCAL_DATA" 10 | #endif 11 | 12 | /** 13 | * Coroutines library. It allows to split execution of a task 14 | * into a set of coroutines. Possible example of usage: 15 | * 16 | * 17 | * foreach (coro : coros) 18 | * coro_init(coro); 19 | * coro_call(func_to_split); 20 | * 21 | * 22 | * void other_func1() 23 | * { 24 | * ... 25 | * coro_return(); 26 | * } 27 | * 28 | * void other_func2() 29 | * { 30 | * coro_call(func1); 31 | * ... 32 | * coro_return(); 33 | * } 34 | * 35 | * void func_to_split() 36 | * { 37 | * ... 38 | * coro_call(other_func1); 39 | * ... 40 | * coro_yield(); 41 | * ... 42 | * coro_call(other_func2); 43 | * ... 44 | * coro_finish(); 45 | * coro_wait_all(); 46 | * } 47 | */ 48 | 49 | /** 50 | * This struct describes one single coroutine. It stores its 51 | * local variables and a position, where it stands now. 52 | */ 53 | struct coro { 54 | /** 55 | * Important thing - execution position where that 56 | * coroutine stopped last time. 57 | */ 58 | jmp_buf exec_point; 59 | /** 60 | * Stack of points remembered before a call of a function 61 | * from the coroutine. Before each new call stack position 62 | * is remembered here, and the function returns here via 63 | * longjmp. 64 | */ 65 | jmp_buf *ret_points; 66 | /** Number of used points. */ 67 | int ret_count; 68 | /** Maximal number of points to store. */ 69 | int ret_capacity; 70 | /** 71 | * This flag is set when the coroutine has finished its 72 | * task. It is used to wait until all the coroutines are 73 | * finished. 74 | */ 75 | bool is_finished; 76 | CORO_LOCAL_DATA; 77 | }; 78 | 79 | /** 80 | * In your code it should be dynamic, not const. Here it is 3 for 81 | * simplicity. 82 | */ 83 | #define coro_count 3 84 | static struct coro coros[coro_count]; 85 | 86 | /** 87 | * Index of the currently working coroutine. It is used to learn 88 | * which coroutine should be scheduled next, and to get your 89 | * current local variables. 90 | */ 91 | static int curr_coro_i = 0; 92 | 93 | /** Get currently working coroutine. */ 94 | #define coro_this() (&coros[curr_coro_i]) 95 | 96 | /** Declare that this curoutine has finished. */ 97 | #define coro_finish() ({ coro_this()->is_finished = true; }) 98 | 99 | /** 100 | * This macro stops the current coroutine and switches to another 101 | * one. Check is not in a function, because setjmp result can not 102 | * be used after 'return'. You should keep it as macros. In your 103 | * code instead of real call and 'return' use coro_call() and 104 | * coro_return(). 105 | */ 106 | #define coro_yield() ({ \ 107 | int old_i = curr_coro_i; \ 108 | curr_coro_i = (curr_coro_i + 1) % coro_count; \ 109 | if (setjmp(coros[old_i].exec_point) == 0) \ 110 | longjmp(coros[curr_coro_i].exec_point, 1); \ 111 | }) 112 | 113 | /** Initialize a coroutine. */ 114 | #define coro_init(coro) ({ \ 115 | (coro)->is_finished = false; \ 116 | (coro)->ret_count = 0; \ 117 | (coro)->ret_capacity = 0; \ 118 | (coro)->ret_points = NULL; \ 119 | setjmp((coro)->exec_point); \ 120 | }) 121 | 122 | /** 123 | * Call a function, but do it safely, creating a point to jump 124 | * back from that function, instead of 'return'. 125 | */ 126 | #define coro_call(func, ...) ({ \ 127 | struct coro *c = coro_this(); \ 128 | if (c->ret_count + 1 > c->ret_capacity) { \ 129 | int new_cap = (c->ret_capacity + 1) * 2; \ 130 | int new_size = new_cap * sizeof(jmp_buf); \ 131 | c->ret_points = \ 132 | (jmp_buf *) realloc(c->ret_points, \ 133 | new_size); \ 134 | assert(c->ret_points != NULL); \ 135 | c->ret_capacity = new_cap; \ 136 | } \ 137 | if (setjmp(c->ret_points[c->ret_count]) == 0) { \ 138 | ++c->ret_count; \ 139 | func(__VA_ARGS__); \ 140 | } \ 141 | }) 142 | 143 | /** 144 | * Return from a function, previously called via coro_call(). 145 | * Tricky thing - it does not use 'return', because it taints 146 | * all the jmp_buf's. Instead, it jumps out of that function. 147 | */ 148 | #define coro_return() ({ \ 149 | struct coro *c = coro_this(); \ 150 | longjmp(c->ret_points[--c->ret_count], 1); \ 151 | }) 152 | 153 | /** Wait until all the coroutines have finished. */ 154 | #define coro_wait_all() do { \ 155 | bool is_all_finished = true; \ 156 | for (int i = 0; i < coro_count; ++i) { \ 157 | if (! coros[i].is_finished) { \ 158 | fprintf(stderr, "Coro %d: still active,"\ 159 | " re-scheduling\n", i); \ 160 | is_all_finished = false; \ 161 | break; \ 162 | } \ 163 | } \ 164 | if (is_all_finished) { \ 165 | fprintf(stderr, "No more active coros to " \ 166 | "schedule.\n"); \ 167 | break; \ 168 | } \ 169 | coro_yield(); \ 170 | } while (true) 171 | -------------------------------------------------------------------------------- /tasks/1/example_jmp.c: -------------------------------------------------------------------------------- 1 | #define CORO_LOCAL_DATA struct { \ 2 | int deep; \ 3 | char local_data[128]; \ 4 | int arg; \ 5 | } 6 | #include "coro_jmp.h" 7 | 8 | /** 9 | * You can compile and run this example using the commands: 10 | * 11 | * $> gcc example_jmp.c 12 | * $> ./a.out 13 | */ 14 | 15 | /** 16 | * A function, called from inside of coroutines, and even 17 | * recursively. 18 | */ 19 | static void 20 | other_function(int arg) 21 | { 22 | printf("Coro %d: entered function, deep = %d, arg = %d\n", curr_coro_i, 23 | coro_this()->deep, arg); 24 | coro_this()->arg = arg; 25 | coro_yield(); 26 | printf("Coro %d: after yield arg = %d, but coro_this()->arg = %d\n", 27 | curr_coro_i, arg, coro_this()->arg); 28 | /* 29 | * Here I've decided to call it recursively 2 times in 30 | * each coro. 31 | */ 32 | if (++coro_this()->deep < 2) 33 | coro_call(other_function, curr_coro_i * 10); 34 | coro_return(); 35 | } 36 | 37 | /** 38 | * Coroutine body. This code is executed by all the corutines at 39 | * the same time, but with different struct coro. Here you 40 | * implement your solution. 41 | */ 42 | static void 43 | my_coroutine() 44 | { 45 | /* 46 | * Note - all the data access is done via 'coro_this()'. 47 | * It is not safe to store anything in local variables 48 | * here. 49 | */ 50 | sprintf(coro_this()->local_data, "Local data for coro id%d", 51 | curr_coro_i); 52 | fprintf(stderr, "Coro %d: before re-schedule\n", curr_coro_i); 53 | coro_yield(); 54 | fprintf(stderr, "Coro %d: after first re-schedule\n", curr_coro_i); 55 | coro_yield(); 56 | fprintf(stderr, "Coro %d: after second re-schedule\n", curr_coro_i); 57 | /* Other functions can be called, but via coro_call(). */ 58 | coro_call(other_function, curr_coro_i * 10); 59 | fprintf(stderr, "Coro %d: this is local data: %s\n", curr_coro_i, 60 | coro_this()->local_data); 61 | coro_finish(); 62 | coro_wait_all(); 63 | } 64 | 65 | int 66 | main(int argc, char **argv) 67 | { 68 | for (int i = 0; i < coro_count; ++i) { 69 | if (coro_init(&coros[i]) != 0) 70 | break; 71 | } 72 | coro_call(my_coroutine); 73 | printf("Finished\n"); 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /tasks/1/example_swap.c: -------------------------------------------------------------------------------- 1 | #define _XOPEN_SOURCE /* Mac compatibility. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /** 9 | * You can compile and run this example using the commands: 10 | * 11 | * $> gcc example_swap.c 12 | * $> ./a.out 13 | */ 14 | 15 | /** 16 | * Here you have 3 contexts - one for each coroutine. First 17 | * belongs to main coroutine - the one, who starts all others. 18 | * Next two are worker coroutines - they do real work. In your 19 | * code you will have more than 2. 20 | */ 21 | static ucontext_t uctx_main, uctx_func1, uctx_func2; 22 | 23 | #define handle_error(msg) \ 24 | do { perror(msg); exit(EXIT_FAILURE); } while (0) 25 | 26 | #define stack_size 1024 * 1024 27 | 28 | /** 29 | * Coroutine body. This code is executed by all the corutines at 30 | * the same time and with different stacks. So you can store any 31 | * local variables and arguments right here. Here you implement 32 | * your solution. 33 | */ 34 | static void 35 | my_coroutine(int id) 36 | { 37 | printf("func%d: started\n", id); 38 | if (id == 1) { 39 | printf("coroutine1: swapcontext(&uctx_func1, &uctx_func2)\n"); 40 | if (swapcontext(&uctx_func1, &uctx_func2) == -1) 41 | handle_error("swapcontext"); 42 | } else { 43 | printf("coroutine2: swapcontext(&uctx_func2, &uctx_func1)\n"); 44 | if (swapcontext(&uctx_func2, &uctx_func1) == -1) 45 | handle_error("swapcontext"); 46 | } 47 | printf("func%d: returning\n", id); 48 | } 49 | 50 | /** 51 | * Below you can see 3 different ways of how to allocate stack. 52 | * You can choose any. All of them do in fact the same. 53 | */ 54 | 55 | static void * 56 | allocate_stack_sig() 57 | { 58 | void *stack = malloc(stack_size); 59 | stack_t ss; 60 | ss.ss_sp = stack; 61 | ss.ss_size = stack_size; 62 | ss.ss_flags = 0; 63 | sigaltstack(&ss, NULL); 64 | return stack; 65 | } 66 | 67 | static void * 68 | allocate_stack_mmap() 69 | { 70 | return mmap(NULL, stack_size, PROT_READ | PROT_WRITE | PROT_EXEC, 71 | MAP_ANON | MAP_PRIVATE, -1, 0); 72 | } 73 | 74 | static void * 75 | allocate_stack_mprot() 76 | { 77 | void *stack = malloc(stack_size); 78 | mprotect(stack, stack_size, PROT_READ | PROT_WRITE | PROT_EXEC); 79 | return stack; 80 | } 81 | 82 | enum stack_type { 83 | STACK_MMAP, 84 | STACK_SIG, 85 | STACK_MPROT 86 | }; 87 | 88 | /** 89 | * Use this wrapper to choose your favourite way of stack 90 | * allocation. 91 | */ 92 | static void * 93 | allocate_stack(enum stack_type t) 94 | { 95 | switch(t) { 96 | case STACK_MMAP: 97 | return allocate_stack_mmap(); 98 | case STACK_SIG: 99 | return allocate_stack_sig(); 100 | case STACK_MPROT: 101 | return allocate_stack_mprot(); 102 | } 103 | } 104 | 105 | int 106 | main(int argc, char *argv[]) 107 | { 108 | /* First of all, create a stack for each coroutine. */ 109 | char *func1_stack = allocate_stack(STACK_SIG); 110 | char *func2_stack = allocate_stack(STACK_MPROT); 111 | 112 | /* 113 | * Below is just initialization of coroutine structures. 114 | * They are not started yet. Just created. 115 | */ 116 | if (getcontext(&uctx_func1) == -1) 117 | handle_error("getcontext"); 118 | /* 119 | * Here you specify a stack, allocated earlier. Unique for 120 | * each coroutine. 121 | */ 122 | uctx_func1.uc_stack.ss_sp = func1_stack; 123 | uctx_func1.uc_stack.ss_size = stack_size; 124 | /* 125 | * Important - here you specify, to which context to 126 | * switch after this coroutine is finished. The code below 127 | * says, that when 'uctx_func1' is finished, it should 128 | * switch to 'uctx_main'. 129 | */ 130 | uctx_func1.uc_link = &uctx_main; 131 | makecontext(&uctx_func1, my_coroutine, 1, 1); 132 | 133 | if (getcontext(&uctx_func2) == -1) 134 | handle_error("getcontext"); 135 | uctx_func2.uc_stack.ss_sp = func2_stack; 136 | uctx_func2.uc_stack.ss_size = stack_size; 137 | /* Successor context is f1(), unless argc > 1. */ 138 | uctx_func2.uc_link = (argc > 1) ? NULL : &uctx_func1; 139 | makecontext(&uctx_func2, my_coroutine, 1, 2); 140 | 141 | /* 142 | * And here it starts. The first coroutine to start is 143 | * 'uctx_func2'. 144 | */ 145 | printf("main: swapcontext(&uctx_main, &uctx_func2)\n"); 146 | if (swapcontext(&uctx_main, &uctx_func2) == -1) 147 | handle_error("swapcontext"); 148 | 149 | printf("main: exiting\n"); 150 | return 0; 151 | } 152 | -------------------------------------------------------------------------------- /tasks/1/generator.py: -------------------------------------------------------------------------------- 1 | import random 2 | import argparse 3 | 4 | maxint = 1 << 31 5 | 6 | parser = argparse.ArgumentParser(description = "Generate random numbers file") 7 | parser.add_argument('-f', type=str, required=True, help="file name") 8 | parser.add_argument('-c', type=int, required=True, help='number count') 9 | parser.add_argument('-m', type=int, default=maxint, help='maximal number') 10 | args = parser.parse_args() 11 | random.seed() 12 | 13 | 14 | f = open(args.f, 'w') 15 | 16 | for i in range(0, args.c): 17 | f.write(str(random.randint(0, args.m))) 18 | if i + 1 != args.c: 19 | f.write(' ') 20 | 21 | f.close() 22 | -------------------------------------------------------------------------------- /tasks/1/task_eng.txt: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------ 2 | Merge sort in coroutines. 3 | Language: С. 4 | Deadline: 2 weeks. 5 | ------------------------------------------------------------------ 6 | 7 | Files are stored on a disk. They have numbers in ASCII in 8 | arbitrary order separated by whitespaces. Each file should be 9 | sorted, and then results should be merged into a new file. That 10 | is, merge sort should be implemented. 11 | 12 | Sort of each file should be done in its dedicated coroutine, in 13 | its own context. Algorithm of how to sort a file can be chosen 14 | any. 15 | 16 | You can assume, that all files fit into the main memory, even 17 | together. 18 | 19 | Coroutines should be implemented using swapcontext() or 20 | setjump()/longjump(). Sort should be implemented by yourself 21 | without usage of built-in sorting functions like qsort(), 22 | system("sort ...") etc. It is not allowed to implement sort slower 23 | or equal to quadratical complexity, like bubble sort. Work with 24 | files should be done via a numeric file descriptor using 25 | open()/read()/write()/close() functions, or via FILE* descriptor 26 | and functions fopen()/fscanf()/fprintf()/fclose(). It is not 27 | allowed to use iostream, ostream, istream and other STL helpers. 28 | 29 | Now some information about each implementation way, one of which 30 | should be chosen. Don't be afraid of functions like mmap(), 31 | setjump()/longjump(), mprotect(), sigaltstack(), which are used 32 | below and in examples example_jmp.c, example_swap.c. Understanding 33 | of them will come gradually during the course. Now just use them. 34 | 35 | * setjump()/longjump() - a 'long', runtime-defined goto, which 36 | allows to jump into an arbitrary place in stack. Each process has 37 | a memory segment called stack. When any function is called, the 38 | stack is increased, arguments and local variables are saved on it. 39 | When the function is finished, the stack is decreased back. With 40 | usage of longjump() you need to remember, that all coroutines are 41 | going to have one shared stack, and they will see local variables 42 | of each other. So you need to save all needed variables in some 43 | kind of a context, not related to the stack. For example, into a 44 | C struct, allocated on the heap. In example_jmp.c you can see, 45 | that function my_coroutine() does not have local variables - all 46 | is stored in struct task. Besides, result of setjump() becomes 47 | invalid right after return from the function called setjump(), 48 | because the stack is decreased and the label, created in 49 | setjump() is freed. So all the coroutine code shouldn't do return. 50 | Either the whole coroutine code should be a one big function, or 51 | a special macros should be used - coro_return(), like in 52 | example_jmp.c. The 'goto' semantic is easy to understand, and 53 | there is nothing bad in choosing setjump() implementation. Even 54 | though it may look a bit crutchy. 55 | 56 | * swapcontext() - function to switch between different execution 57 | contexts. Instead of one stack shared between coroutines there is 58 | a dedicated stack for each coroutine. Also function swapcontext() 59 | takes responsibility of doing the same as setjump()/longjump(), so 60 | no need to worry about inability to store local variables on the 61 | stack. Each coroutine is described by struct ucontext_t, where you 62 | should put function pointer to execute on a new stack, and stack 63 | memory. To allocate stack memory there are multiple ways. Each can 64 | be found in example_swap.c and below: 65 | 66 | 1. Malloc() + mprotect() with flags PROT_READ | PROT_WRITE | 67 | PROT_EXEC. Just malloc() is not enough, because that memory 68 | does not have all permissions needed for being a stack. The 69 | problem is that stack memory should be executable, so has the 70 | flag PROT_EXEC. But the heap, from where malloc() takes memory, 71 | is not executable by default. 72 | Example: 73 | 74 | void *stack = malloc(stack_size); 75 | mprotect(stack, stack_size, PROT_READ | PROT_WRITE | 76 | PROT_EXEC); 77 | ucontext_t context; 78 | context.uc_stack.ss_sp = stack; 79 | context.uc_stack.ss_size = stack_size; 80 | 81 | 2. Malloc() + sigaltstack(). This is the most canonical way, which 82 | is used in coroutine libraries even when context switch has 83 | nothing to do with swapcontext(), in assembly. 84 | 85 | void *stack = malloc(stack_size); 86 | stack_t ss; 87 | ss.ss_sp = stack; 88 | ss.ss_size = stack_size; 89 | ss.ss_flags = 0; 90 | sigaltstack(&ss, NULL); 91 | ucontext_t context; 92 | context.uc_stack.ss_sp = stack; 93 | context.uc_stack.ss_size = stack_size; 94 | 95 | 3. Mmap() with PROT_READ | PROT_WRITE | PROT_EXEC. This is almost 96 | the same as the first way, but a little less efficient in a 97 | common case. 98 | 99 | void *stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE | 100 | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0); 101 | ucontext_t context; 102 | context.uc_stack.ss_sp = stack; 103 | context.uc_stack.ss_size = stack_size; 104 | 105 | Take into account that stack size usually should be at least 32KB. 106 | On Mac it may need 64KB. 107 | 108 | The coroutines should have a scheduling policy regulating when to 109 | switch to a next coroutine. There are several options. 110 | 111 | - Easy: 15 points. Switch after each line to a next coroutine 112 | in a cycle until all are finished. 113 | 114 | - Middle: 20 points. Each from N coroutines is given T / N 115 | milliseconds, where T - target latency, given as a command 116 | line parameter. After each line check whether the time quantum 117 | is finished, and switch if yes. 118 | 119 | - Hard: 25 points. The same as middle, but disk read should be 120 | asynchronous. See aio_read() in 'man'. 121 | 122 | Input: names of files to sort, via command line arguments. For the 123 | middle complexity option there is also a target latency in the 124 | first argument. All time is given in microseconds. 125 | 126 | Output: total work time, and work time for each coroutine. Time 127 | should be printed in microseconds. For the middle complexity it is 128 | necessary also to print how many context switches was done for 129 | each coroutine so as to ensure, that target latency really affects 130 | things. 131 | 132 | For testing you can create your files or generate them using 133 | generator.py script. A example, which should work for the easy 134 | complexity: 135 | 136 | $> python3 generator.py -f test1.txt -c 10000 -m 10000 137 | $> python3 generator.py -f test2.txt -c 10000 -m 10000 138 | $> python3 generator.py -f test3.txt -c 10000 -m 10000 139 | $> python3 generator.py -f test4.txt -c 10000 -m 10000 140 | $> python3 generator.py -f test5.txt -c 10000 -m 10000 141 | $> python3 generator.py -f test6.txt -c 100000 -m 10000 142 | 143 | $> ./main test1.txt test2.txt test3.txt test4.txt test5.txt test6.txt 144 | 145 | For checking the result you can use a script checker.py. All 146 | scripts assume working in python 3. 147 | 148 | Where to begin? Here is a recommended plan for doing the task: 149 | 150 | - Implement normal sort of one file. Test this code; 151 | 152 | - Extend the solution to implement merge sort, without coroutines. 153 | Check it on the real tests. This will allow to concentrate on 154 | adding coroutines, and not to waste time on debugging both 155 | coroutines and sorting at the same time; 156 | 157 | - Add coroutines, using examples example_jmp.c and example_swap.c. 158 | -------------------------------------------------------------------------------- /tasks/1/task_rus.txt: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------ 2 | Сортировка слиянием через корутины. 3 | Язык: С. 4 | Время: 2 недели. 5 | ------------------------------------------------------------------ 6 | 7 | На диске лежат файлы, в них числа в строковом виде в произвольном 8 | порядке разделенные пробелом. Нужно отсортировать каждый файл и 9 | затем слить их в один большой. То есть выполнить сортировку 10 | слиянием. 11 | 12 | Сортировка одного отдельно взятого файла должна выполняться в 13 | С корутине, в собственном контексте. Алгоритм сортировки файла - 14 | любой. 15 | 16 | Считать, что все файлы в память помещаются, даже одновременно. 17 | 18 | Корутины должны быть реализованы через swapcontext() или 19 | setjmp()/longjump(). Сортировка должна быть реализована 20 | самостоятельно, без использования встроенных библиотечных функций 21 | сортировки вида qsort, system("sort ...") и прочих. Запрещено 22 | реализовывать квадратичные сортировки вроде пузырька. Нужно 23 | работать с файлами через числовой файловый дескриптор и функции 24 | read/write, или через FILE * и функции подобные fscanf, fprintf и 25 | тд. Нельзя использовать iostream, ostream, istream и прочие IO из 26 | STL. 27 | 28 | Далее немного информации о каждом способе реализации, один из 29 | которых вам надо выбрать. Не пугайтесь непонятных функций вроде 30 | mmap, setjump/longjump, mprotect, sigaltstack, которые встретятся 31 | ниже и в примерах example_jmp.c, example_swap.c. Понимание их 32 | работы придет постепенно в процессе прохождения курса. А пока надо 33 | просто начать ими пользоваться. 34 | 35 | * setjump/longjump - это "длинный" goto, который позволяет прыгать 36 | в произвольное место стека программы. У каждого процесса есть 37 | область памяти, называемая стек. Когда программа вызывает любую 38 | функцию, она увеличивает стек, кладет на него аргументы и 39 | локальные переменные этой функции. Когда функция кончается, то 40 | стек уменьшается обратно. При использовании longjump придется 41 | учитывать, что у всех корутин будет общий стек, и они будут 42 | видеть локальные переменные друг друга. Значит нужно хранить все 43 | используемые переменные внутри структуры, описывающей корутину. В 44 | примере example_jmp.c можно видеть, что внутри функции 45 | my_coroutine нет ни одной локальной переменной - все лежит в 46 | структуре struct task. Кроме того, результат setjump становится 47 | невалиден сразу после возврата из функции, сделавшей setjump, так 48 | как стек сворачивается и метка, созданная в setjump, станет 49 | испорченной, а потому вся часть кода, выполняющаяся в корутине, 50 | должна быть реализована через макросы или одну гигантскую функцию. 51 | Однако сементика, похожая на goto, может кому-то показаться более 52 | "родной", и нет ничего плохого в выборе реализации через setjump. 53 | 54 | * swapcontext - это функция, которая позволяет подменять стек и 55 | указатель текущего места выполнения программы. То есть вместо 56 | одного стека, разделяемого между корутинами, у каждой корутины 57 | стек будет свой, в отличие от longjump реализации. Функция 58 | swapcontext берет на себя все то, что делает setjump/longjump, так 59 | что не нужно беспокоиться, что одна корутина поменяет переменные 60 | стека другой. Каждая корутина описывается структурой ucontext_t, 61 | где вам нужно положить указатель на функцию, которую будет 62 | выполнять корутина, и указатель на память для стека. Выделить ее 63 | можно тремя способами, каждый из которых можно найти в примере 64 | example_swap.c, и которые описаны ниже: 65 | 66 | 1. использовать обычный malloc() + mprotect() с флагами 67 | PROT_READ | PROT_WRITE | PROT_EXEC. Если на результате маллока 68 | не позвать mprotect, то эта память будет непригодна для стека. 69 | Дело в том, что память стека должна быть исполняемой, то есть 70 | иметь флаг PROT_EXEC. А куча, откуда берет память malloc, не 71 | имеет этого флага по умолчанию. 72 | Пример: 73 | 74 | void *stack = malloc(stack_size); 75 | mprotect(stack, stack_size, PROT_READ | PROT_WRITE | 76 | PROT_EXEC); 77 | ucontext_t context; 78 | context.uc_stack.ss_sp = stack; 79 | context.uc_stack.ss_size = stack_size; 80 | 81 | 2. использовать malloc() + sigaltstack(). Это наиболее каноничный 82 | способ, который используется в библиотеках корутин даже тогда, 83 | когда переключение контекстов делается без swapcontext(), на 84 | ассемблере. 85 | 86 | void *stack = malloc(stack_size); 87 | stack_t ss; 88 | ss.ss_sp = stack; 89 | ss.ss_size = stack_size; 90 | ss.ss_flags = 0; 91 | sigaltstack(&ss, NULL); 92 | ucontext_t context; 93 | context.uc_stack.ss_sp = stack; 94 | context.uc_stack.ss_size = stack_size; 95 | 96 | 3. использовать mmap() с флагами PROT_READ | PROT_WRITE | 97 | PROT_EXEC. Это почти тоже самое, что первый способ, но только 98 | чуть менее эффективный в общем случае. 99 | 100 | void *stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE | 101 | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0); 102 | ucontext_t context; 103 | context.uc_stack.ss_sp = stack; 104 | context.uc_stack.ss_size = stack_size; 105 | 106 | Учтите, что размер стека обычно должен быть не меньше 32Kb, а на 107 | Mac от 64Kb. 108 | 109 | У корутин должен быть планировщик, который будет их переключать. 110 | Есть несколько вариантов. 111 | 112 | - 15 баллов: после каждой выполненной строки делается 113 | переключение. 114 | - 20 баллов: каждой из N корутин выдается по T / N миллисекунд, 115 | где T - target latency, подаваемое на вход. После каждой 116 | строки проверять, что у процесса кончилось время. Если да, то 117 | переключать. 118 | - 25 баллов: тоже, что на 15, но чтение с диска должно быть 119 | асинхронным. Смотреть aio_read в man или google.com. 120 | 121 | Вход: имена файлов, которые надо отсортировать, через аргументы 122 | командной строки. Для варианта на 20 баллов добавляется target 123 | latency первым аргументом. Все время вводится в микросекундах. 124 | 125 | Выход: сколько программа работала суммарно и сколько каждая 126 | корутина. Вывести время в микросекундах. Для задания на 20 баллов 127 | нужно еще выводить сколько раз каждая задача отдала управление 128 | другой, чтобы убедиться, что target latency соблюдается. 129 | 130 | Для тестирования программы вы можете использовать свои файлы или 131 | генерировать их с помощью скрипта generator.py. Пример, который 132 | должен у вас работать на 15 баллов: 133 | 134 | $> python3 generator.py -f test1.txt -c 10000 -m 10000 135 | $> python3 generator.py -f test2.txt -c 10000 -m 10000 136 | $> python3 generator.py -f test3.txt -c 10000 -m 10000 137 | $> python3 generator.py -f test4.txt -c 10000 -m 10000 138 | $> python3 generator.py -f test5.txt -c 10000 -m 10000 139 | $> python3 generator.py -f test6.txt -c 100000 -m 10000 140 | 141 | $> ./main test1.txt test2.txt test3.txt test4.txt test5.txt test6.txt 142 | 143 | Для проверки результата можно использовать скрипт checker.py. Все 144 | скрипты написаны с расчетом на python 3. 145 | 146 | С чего начать? Рекомендуемый план выполнения задания такой: 147 | 148 | - реализовать обычную сортировку, которая бы сортировала один файл; 149 | протестировать этот код; 150 | 151 | - расширить решение, сделав обычную сортировку слиянием, без корутин; проверить 152 | уже на реальных тестах, что она работает. Это позволит далее сосредоточиться 153 | на только добавлении корутин, и не тратить время на отладку сортировки; 154 | 155 | - добавить "корутинность", использовав примеры example_jmp.c и example_swap.c. 156 | 157 | -------------------------------------------------------------------------------- /tasks/2/Makefile: -------------------------------------------------------------------------------- 1 | CC = cc 2 | CFLAGS = -I . 3 | CFLAGS += -W -Wall -Wextra -Werror -Wfloat-equal 4 | CFLAGS += -Wundef -Wpointer-arith -Wcast-align -Wshadow 5 | CFLAGS += -Wstrict-overflow=5 -Wwrite-strings -Waggregate-return 6 | CFLAGS += -Wswitch-enum -Wunreachable-code -Winit-self 7 | CFLAGS += -Wno-unused-parameter -pedantic -O3 8 | LDFLAGS = 9 | 10 | BASE_SOURCES = main.c 11 | SOURCES = $(BASE_SOURCES) 12 | OBJS = $(SOURCES:.c=.o) 13 | EXECUTABLE = task_2 14 | 15 | all: test 16 | 17 | build: $(EXECUTABLE) 18 | 19 | $(EXECUTABLE): $(OBJS) 20 | $(CC) $(LDFLAGS) $(OBJS) -o $@ 21 | 22 | .c.o: 23 | $(CC) $(CFLAGS) -c $< -o $@ 24 | 25 | test: build 26 | python checker.py -e ./$(EXECUTABLE) 27 | 28 | clean: 29 | rm -rf $(EXECUTABLE) $(OBJS) 30 | 31 | .PHONY: clean 32 | -------------------------------------------------------------------------------- /tasks/2/checker.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import argparse 3 | import sys 4 | import os 5 | 6 | parser = argparse.ArgumentParser(description='Tests for shell') 7 | parser.add_argument('-e', type=str, default='./a.out', 8 | help='executable shell file') 9 | parser.add_argument('-r', type=str, help='result file') 10 | parser.add_argument('-t', action='store_true', default=False, 11 | help='run without checks') 12 | parser.add_argument('--max', type=int, choices=[15, 20, 25], default=15, 13 | help='max points number') 14 | args = parser.parse_args() 15 | 16 | p = subprocess.Popen([args.e], shell=False, stdin=subprocess.PIPE, 17 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT, 18 | bufsize=0) 19 | 20 | tests = [ 21 | [ 22 | "mkdir testdir", 23 | "cd testdir", 24 | 'pwd | tail -c 8', 25 | ' pwd | tail -c 8', 26 | ], 27 | [ 28 | "touch \"my file with whitespaces in name.txt\"", 29 | "ls", 30 | "echo '123 456 \" str \"'", 31 | "echo '123 456 \" str \"' > \"my file with whitespaces in name.txt\"", 32 | "cat my\\ file\\ with\\ whitespaces\\ in\\ name.txt", 33 | "echo \"test\" >> \"my file with whitespaces in name.txt\"", 34 | "cat \"my file with whitespaces in name.txt\"", 35 | "echo 'truncate' > \"my file with whitespaces in name.txt\"", 36 | "cat \"my file with whitespaces in name.txt\"", 37 | "echo \"test 'test'' \\\\\" >> \"my file with whitespaces in name.txt\"", 38 | "cat \"my file with whitespaces in name.txt\"" 39 | ], 40 | [ 41 | "# Comment", 42 | "echo 123\\\n456", 43 | ], 44 | [ 45 | "rm my\\ file\\ with\\ whitespaces\\ in\\ name.txt", 46 | "echo 123 | grep 2", 47 | "echo 123\\\n456\\\n| grep 2", 48 | "echo \"123\n456\n7\n\" | grep 4", 49 | "echo 'source string' | sed 's/source/destination/g'", 50 | "echo 'source string' | sed 's/source/destination/g' | sed 's/string/value/g'", 51 | "echo 'source string' |\\\nsed 's/source/destination/g'\\\n| sed 's/string/value/g'", 52 | "echo 'test' | exit 123 | grep 'test2'", 53 | "echo 'source string' | sed 's/source/destination/g' | sed 's/string/value/g' > result.txt", 54 | "cat result.txt", 55 | ], 56 | [ 57 | "false && echo 123", 58 | "true && echo 123", 59 | "true || false && echo 123", 60 | "true || false || true && echo 123", 61 | "false || echo 123" 62 | ], 63 | [ 64 | "sleep 0.5 && echo 'back sleep is done' &", 65 | "echo 'next sleep is done'", 66 | "sleep 0.5", 67 | ] 68 | ] 69 | 70 | def finish(code): 71 | os.system('rm -rf testdir') 72 | sys.exit(code) 73 | 74 | prefix = '--------------------------------' 75 | command = '' 76 | for section_i, section in enumerate(tests, 1): 77 | if section_i == 5 and args.max == 15: 78 | break 79 | if section_i == 6 and args.max != 25: 80 | break 81 | command += 'echo "{}Section {}"\n'.format(prefix, section_i) 82 | for test_i, test in enumerate(section, 1): 83 | command += 'echo "$> Test {}"\n'.format(test_i) 84 | command += '{}\n'.format(test) 85 | 86 | try: 87 | output = p.communicate(command.encode(), 3)[0].decode() 88 | except subprocess.TimeoutExpired: 89 | print('Too long no output. Probably you forgot to process EOF') 90 | finish(-1) 91 | 92 | if args.t: 93 | print(output) 94 | finish(0) 95 | 96 | output = output.splitlines() 97 | result = args.r 98 | if not result: 99 | result = 'result{}.txt'.format(args.max) 100 | with open(result) as f: 101 | etalon = f.read().splitlines() 102 | etalon_len = len(etalon) 103 | output_len = len(output) 104 | line_count = min(etalon_len, output_len) 105 | is_error = False 106 | for i in range(line_count): 107 | print(output[i]) 108 | if output[i] != etalon[i]: 109 | print('Error in line {}. '\ 110 | 'Expected:\n{}'.format(i + 1, etalon[i])) 111 | is_error = True 112 | break 113 | if not is_error and etalon_len != output_len: 114 | print('Different line count. Got {}, '\ 115 | 'expected {}'.format(output_len, etalon_len)) 116 | is_error = True 117 | p.terminate() 118 | if is_error: 119 | print('{}\nThe tests did not pass'.format(prefix)) 120 | finish(-1) 121 | else: 122 | print('{}\nThe tests passed'.format(prefix)) 123 | finish(0) 124 | -------------------------------------------------------------------------------- /tasks/2/result15.txt: -------------------------------------------------------------------------------- 1 | --------------------------------Section 1 2 | $> Test 1 3 | $> Test 2 4 | $> Test 3 5 | testdir 6 | $> Test 4 7 | testdir 8 | --------------------------------Section 2 9 | $> Test 1 10 | $> Test 2 11 | my file with whitespaces in name.txt 12 | $> Test 3 13 | 123 456 " str " 14 | $> Test 4 15 | $> Test 5 16 | 123 456 " str " 17 | $> Test 6 18 | $> Test 7 19 | 123 456 " str " 20 | test 21 | $> Test 8 22 | $> Test 9 23 | truncate 24 | $> Test 10 25 | $> Test 11 26 | truncate 27 | test 'test'' \ 28 | --------------------------------Section 3 29 | $> Test 1 30 | $> Test 2 31 | 123456 32 | --------------------------------Section 4 33 | $> Test 1 34 | $> Test 2 35 | 123 36 | $> Test 3 37 | 123456 38 | $> Test 4 39 | 456 40 | $> Test 5 41 | destination string 42 | $> Test 6 43 | destination value 44 | $> Test 7 45 | destination value 46 | $> Test 8 47 | $> Test 9 48 | $> Test 10 49 | destination value 50 | -------------------------------------------------------------------------------- /tasks/2/result20.txt: -------------------------------------------------------------------------------- 1 | --------------------------------Section 1 2 | $> Test 1 3 | $> Test 2 4 | $> Test 3 5 | testdir 6 | $> Test 4 7 | testdir 8 | --------------------------------Section 2 9 | $> Test 1 10 | $> Test 2 11 | my file with whitespaces in name.txt 12 | $> Test 3 13 | 123 456 " str " 14 | $> Test 4 15 | $> Test 5 16 | 123 456 " str " 17 | $> Test 6 18 | $> Test 7 19 | 123 456 " str " 20 | test 21 | $> Test 8 22 | $> Test 9 23 | truncate 24 | $> Test 10 25 | $> Test 11 26 | truncate 27 | test 'test'' \ 28 | --------------------------------Section 3 29 | $> Test 1 30 | $> Test 2 31 | 123456 32 | --------------------------------Section 4 33 | $> Test 1 34 | $> Test 2 35 | 123 36 | $> Test 3 37 | 123456 38 | $> Test 4 39 | 456 40 | $> Test 5 41 | destination string 42 | $> Test 6 43 | destination value 44 | $> Test 7 45 | destination value 46 | $> Test 8 47 | $> Test 9 48 | $> Test 10 49 | destination value 50 | --------------------------------Section 5 51 | $> Test 1 52 | $> Test 2 53 | 123 54 | $> Test 3 55 | 123 56 | $> Test 4 57 | 123 58 | $> Test 5 59 | 123 60 | -------------------------------------------------------------------------------- /tasks/2/result25.txt: -------------------------------------------------------------------------------- 1 | --------------------------------Section 1 2 | $> Test 1 3 | $> Test 2 4 | $> Test 3 5 | testdir 6 | $> Test 4 7 | testdir 8 | --------------------------------Section 2 9 | $> Test 1 10 | $> Test 2 11 | my file with whitespaces in name.txt 12 | $> Test 3 13 | 123 456 " str " 14 | $> Test 4 15 | $> Test 5 16 | 123 456 " str " 17 | $> Test 6 18 | $> Test 7 19 | 123 456 " str " 20 | test 21 | $> Test 8 22 | $> Test 9 23 | truncate 24 | $> Test 10 25 | $> Test 11 26 | truncate 27 | test 'test'' \ 28 | --------------------------------Section 3 29 | $> Test 1 30 | $> Test 2 31 | 123456 32 | --------------------------------Section 4 33 | $> Test 1 34 | $> Test 2 35 | 123 36 | $> Test 3 37 | 123456 38 | $> Test 4 39 | 456 40 | $> Test 5 41 | destination string 42 | $> Test 6 43 | destination value 44 | $> Test 7 45 | destination value 46 | $> Test 8 47 | $> Test 9 48 | $> Test 10 49 | destination value 50 | --------------------------------Section 5 51 | $> Test 1 52 | $> Test 2 53 | 123 54 | $> Test 3 55 | 123 56 | $> Test 4 57 | 123 58 | $> Test 5 59 | 123 60 | --------------------------------Section 6 61 | $> Test 1 62 | $> Test 2 63 | next sleep is done 64 | $> Test 3 65 | back sleep is done 66 | -------------------------------------------------------------------------------- /tasks/2/task_eng.txt: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------ 2 | Shell. 3 | Language: С. 4 | Deadline: 2 weeks. 5 | ------------------------------------------------------------------ 6 | 7 | You need to implement a simplified version of a command line 8 | console. It should take lines like this: 9 | 10 | > command_name param1 param2 ... 11 | 12 | and execute them by calling 'command_name' with the given 13 | parameters. So be a terminal, basically. The program should 14 | correctly handle quoted strings even if there are whitespaces 15 | inside it. And you need to handle comments (by trimming them). 16 | 17 | Besides, the console should support pipes expressed as symbol '|', 18 | and output redirection using '>' and '>>'. 19 | 20 | Length of command line input is not limited, so you can't read it 21 | into a buffer of a fixed size. 22 | 23 | The program should print all the same what would be printed by a 24 | real terminal. 25 | 26 | It is forbidden to use functions like system(), popen() or use any 27 | other way to access existing terminal-like read-to-use functions. 28 | 29 | You need to use functions pipe(), dup/dup2(), fork(), wait, open, 30 | close, at least one of execl/execle/execlp/execv/execvp/execvP. 31 | 32 | Input examples: 33 | 34 | * Print process list and find 'init' string in them: 35 | 36 | > ps aux | grep init 37 | 38 | * Execute code in python and search a string in its output: 39 | 40 | > echo "print('result is ', 123 + 456)" | python -i | grep result 41 | 42 | * Print escaped quote into a file and print it: 43 | 44 | > echo "some text\" with quote" > test.txt 45 | > cat test.txt 46 | 47 | * Append to a file: 48 | 49 | > echo "first data" > test.txt 50 | > echo "second" >> test.txt 51 | > cat test.txt 52 | 53 | * Start an interactive python console and do something in it: 54 | 55 | > python 56 | >>> print(1 + 2) 57 | >>> 3 58 | 59 | Points: 60 | 61 | - 15 points: all described above, 62 | - 20 points: support operators && and ||, 63 | - 25 points: 20 + support &. 64 | 65 | Input: commands and their arguments, input/output redirection operators. 66 | 67 | Output: the same what a real terminal prints. 68 | 69 | Where to begin? The recommended implementation plan is below: 70 | 71 | - Implement command parser. This is an independent part of a solution, and at 72 | the same time the simplest in terms of understanding what to do. It is worth 73 | doing it in a separate source file, and test independently. Your parser 74 | should turn a command like "command arg1 arg2 ... argN" into separate 75 | "command" and an array of its arguments. For example, in a form of const char 76 | **args, int arg_count. The parser should specially handle |, considering it a 77 | command separator. 78 | For example, let the parser to parse a command into 79 | 80 | struct cmd { 81 | const char *name; 82 | const char **argv; 83 | int argc; 84 | } 85 | 86 | The the parser should return an array of struct cmd. If there are no |, it 87 | will contain 1 element; 88 | 89 | - Implement command execution without |, >, >>. Just one command; 90 | 91 | - Add support for |, >, >>. 92 | 93 | Architecture of the solution may be the following: there is a process-terminal, 94 | which reads user commands. On each command it does fork(). The new child 95 | executes the command using exec*() functions. The parent process waits for the 96 | child termination. For | the terminal opens a pipe, which is used to link 97 | input of one child and output of another. For > and >> the terminal opens a 98 | file, and using dup/dup2() changes standard output for a child. 99 | -------------------------------------------------------------------------------- /tasks/2/task_rus.txt: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------ 2 | Shell. 3 | Язык: С. 4 | Время: 2 недели. 5 | ------------------------------------------------------------------ 6 | 7 | Нужно написать упрощенную версию командной строки. Она должна 8 | принимать на вход строки вида: 9 | 10 | > command_name param1 param2 ... 11 | 12 | и выполнять их при помощи вызова команды command_name с 13 | параметрами. То есть работать как терминал. При этом нужно, чтобы 14 | программа правильно обрабатывала строки, заключенные в кавычки, 15 | как одну большую строку, даже если там есть пробелы. Комментарии 16 | тоже должны обрабатываться верно, то есть обрезаться. 17 | 18 | Кроме того, должен поддерживаться конвейер, или иначе говоря pipe, 19 | который делается символом |, а так же перенаправление вывода в 20 | файл (>, >>). 21 | 22 | Длина входной строки не ограничена, сохранять ее в буфер заранее 23 | предаллоцированного размера нельзя. 24 | 25 | На выход программа должна печатать все то же, что и обычный 26 | терминал. 27 | 28 | Запрещается использовать функции вроде system(), popen(), или еще 29 | каким-то образом пытаться обращаться к терминалу или 30 | автоматическому созданию конвейеров. 31 | 32 | Необходимо использовать функции pipe(), dup/dup2(), fork(), wait, 33 | open, close, как минимум одну из функций семейства exec: execl, 34 | execle, execlp, execv, execvp, execvP. 35 | 36 | Примеры ввода: 37 | 38 | * Распечатать список процессов и найти среди них строку init. 39 | 40 | > ps aux | grep init 41 | 42 | * Выполнить код в python и выполнить поиск по его результату: 43 | 44 | > echo "print('result is ', 123 + 456)" | python -i | grep result 45 | 46 | * Печать экранированной кавычки в файл и его вывод: 47 | 48 | > echo "some text\" with quote" > test.txt 49 | > cat test.txt 50 | 51 | * Дописывание в файл: 52 | 53 | > echo "first data" > test.txt 54 | > echo "second" >> test.txt 55 | > cat test.txt 56 | 57 | * Запустить интерактивную консоль python и что-то в ней сделать: 58 | 59 | > python 60 | >>> print(1 + 2) 61 | >>> 3 62 | 63 | Разбалловка: 64 | 65 | - 15 баллов: все описанное выше, 66 | - 20 баллов: поддержка операторов && и ||, 67 | - 25 баллов: то же, что на 20 + поддержка &. 68 | 69 | Вход: команды и их аргументы, операторы перенаправления 70 | выводов/вводов. 71 | 72 | Выход: то же, что выводит терминал. 73 | 74 | С чего начать? Рекомендуемый план выполнения задания такой: 75 | 76 | - реализовать парсер команд. Это достаточно самостоятельная часть решения, и в 77 | то же время самая простая в плане понимания что делать. Стоит сделать ее в 78 | отдельном файле, и отдельно протестировать. Ваш парсер должен превращать 79 | введенную команду вида "command arg1 arg2 ... argN" в отдельно "command" и 80 | некий массив ее аргументов, например, вида const char **args, int arg_count. 81 | Парсер должен особым образом обрабатывать |, считая его разделителем команд. 82 | Наример, пусть одну команду парсер разбирает в 83 | 84 | struct cmd { 85 | const char *name; 86 | const char **argv; 87 | int argc; 88 | } 89 | 90 | Тогда парсер должен возвращать массив struct cmd. Если нет |, то в нем будет 91 | 1 элемент; 92 | 93 | - реализовать выполнение команд без |, >, >>. Просто чтобы выполнялась одна 94 | команда; 95 | 96 | - добавить |, >, >>. 97 | 98 | Архитектура решения может быть следующей: есть процесс-терминал, который 99 | читает команды пользователя. На каждую команду он делает fork(). Появившийся 100 | потомок запускает команду используя exec* функции. Родительский процесс ждет 101 | завершения ребенка. Для | терминал создает pipe, которым связывает вывод одного 102 | потомка с вводом другого. Для > и >> терминал открывает файл и при помощи 103 | dup/dup2 подменяет им стандартный поток вывода потомка. 104 | -------------------------------------------------------------------------------- /tasks/2/tests.txt: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------01 2 | $> mkdir testdir 3 | 4 | $> cd testdir 5 | 6 | $> pwd 7 | .../testdir 8 | 9 | $> pwd 10 | .../testdir 11 | 12 | ----------------------------------------------------------------02 13 | $> touch "my file with whitespaces in name.txt" 14 | 15 | $> ls 16 | my file with whitespaces in name.txt 17 | 18 | $> echo '123 456 \" str \"' 19 | 123 456 \" str \" 20 | 21 | $> echo '123 456 \" str \"' > "my file with whitespaces in name.txt" 22 | 23 | $> cat my\ file\ with\ whitespaces\ in\ name.txt 24 | 123 456 \" str \" 25 | 26 | $> echo "test" >> "my file with whitespaces in name.txt" 27 | 28 | $> cat "my file with whitespaces in name.txt" 29 | 123 456 \" str \" 30 | test 31 | 32 | $> echo 'truncate' > "my file with whitespaces in name.txt" 33 | 34 | $> cat "my file with whitespaces in name.txt" 35 | truncate 36 | 37 | $> echo "test 'test'' \\" >> "my file with whitespaces in name.txt" 38 | 39 | $> cat "my file with whitespaces in name.txt" 40 | truncate 41 | test 'test'' \ 42 | 43 | ----------------------------------------------------------------03 44 | 45 | $> # Comment 46 | 47 | $> echo 123\ 48 | 456 49 | 50 | 123456 51 | 52 | ----------------------------------------------------------------04 53 | 54 | $> rm my\ file\ with\ whitespaces\ in\ name.txt 55 | 56 | $> echo 123 | grep 2 57 | 123 58 | 59 | $> echo 123\ 60 | 456\ 61 | | grep 2 62 | 123456 63 | 64 | $> echo "123 65 | 456 66 | 7 67 | " | grep 4 68 | 456 69 | 70 | $> echo 'source string' | sed 's/source/destination/g' 71 | destination string 72 | 73 | $> echo 'source string' | sed 's/source/destination/g' | sed 's/string/value/g' 74 | destination value 75 | 76 | $> echo 'source string' |\ 77 | sed 's/source/destination/g'\ 78 | | sed 's/string/value/g' 79 | destination value 80 | 81 | $> echo 'test' | exit 123 | grep 'test' 82 | 83 | $> echo 'source string' | sed 's/source/destination/g' | sed 's/string/value/g' > result.txt 84 | 85 | $> cat result.txt 86 | destination value 87 | 88 | ----------------------------------------------------------------05 89 | 90 | $> false && echo 123 91 | 92 | $> true && echo 123 93 | 123 94 | 95 | $> true || false && echo 123 96 | 123 97 | 98 | $> true || false || true && echo 123 99 | 123 100 | 101 | $> false || echo 123 102 | 123 103 | 104 | ----------------------------------------------------------------06 105 | 106 | $> sleep 0.5 && echo 'back sleep is done' & 107 | 108 | $> echo 'next sleep is done' 109 | next sleep is done 110 | 111 | $> sleep 0.5 112 | back sleep is done 113 | -------------------------------------------------------------------------------- /tasks/3/Makefile: -------------------------------------------------------------------------------- 1 | all: test.o userfs.o 2 | gcc test.o userfs.o 3 | 4 | test.o: test.c 5 | gcc -c test.c -o test.o -I ../utils 6 | 7 | userfs.o: userfs.c 8 | gcc -c userfs.c -o userfs.o 9 | 10 | clean: 11 | rm -f test.o userfs.o 12 | -------------------------------------------------------------------------------- /tasks/3/task3.txt: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------ 2 | Файловая система. 3 | Язык: С. 4 | Время: 2 недели. 5 | ------------------------------------------------------------------ 6 | 7 | Нужно реализовать свою файловую систему в памяти. Не пугайтесь, 8 | это не сложно. Для старта предоставляется шаблон интерфейса с 9 | некоторыми уже реализованными структурами, в файлах userfs.h и 10 | userfs.c. 11 | 12 | Файловая система называется UserFS, и очень примитивна. В ней нет 13 | папок - все файлы лежат в "руте". Файлы можно создавать, удалять, 14 | открывать на них дескрипторы и закрывать их. Каждый файл похож по 15 | структуре на файловую систему FAT: это список блоков. В userfs.c 16 | можно посмотреть структуры, которые описывают блок и файл, где 17 | лежит их список. Блоки фиксированного размера, размер файла 18 | ограничен. 19 | 20 | На диске не хранится ничего - все в оперативной памяти, на куче. 21 | Файлы можно читать/писать по дескриптору. API похоже на реальное 22 | из libc: 23 | 24 | int 25 | ufs_open(const char *filename, int flags); 26 | 27 | ssize_t 28 | ufs_write(int fd, const char *buf, size_t size); 29 | 30 | ssize_t 31 | ufs_read(int fd, char *buf, size_t size); 32 | 33 | int 34 | ufs_close(int fd); 35 | 36 | int 37 | ufs_delete(const char *filename); 38 | 39 | Прочитать описания этих функций можно в userfs.h. 40 | 41 | Ваша основная задача - реализовать увеличение файла по мере записи 42 | в него, и реализовать файловый дескриптор. Рассмотрим пару 43 | примеров. 44 | 45 | Есть код: 46 | 47 | int fd = ufs_open("any_file_name", UFS_CREATE); 48 | 49 | После этой строки внутри userfs.c создается struct file с именем 50 | "any_file_name", если его еще нет. Затем создается файловый 51 | дескриптор struct filedesc. 52 | 53 | const char *data = "bla bla bla"; 54 | ufs_write(fd, data, strlen(data)); 55 | 56 | Файл пустой, в нем нет блоков, поэтому вам нужно аллоцировать 57 | нужное число блоков struct block. В данном случае это 1. В него 58 | копируем данные. Ваш файл начинает выглядеть так: 59 | 60 | file: 61 | +---------------------+ 62 | | bla bla bla| | -> NULL. 63 | +---------------------+ 64 | ^ 65 | filedesc - дескриптор указывает сюда. Например, 66 | в дескрипторе вы можете хранить 67 | номер блока и смещение в нем. 68 | 69 | Затем я продолжаю писать, но уже больше данных: 70 | 71 | char buf[1024]; 72 | memset(buf, 0, sizeof(buf)); 73 | ufs_write(fd, buf, sizeof(buf)); 74 | 75 | Вот что получится: 76 | 77 | file: 78 | +---------------------+ +---------------------+ 79 | | bla bla bla 0 0 0 0 | -> | 0 0 0 0 0 0 0 0 0 0 | -> 80 | +---------------------+ +---------------------+ 81 | 82 | +---------------------+ 83 | -> | 0 0 0 0 0 0| | -> NULL. 84 | +---------------------+ 85 | ^ 86 | filedesc 87 | 88 | То есть понадобилось дозаполнить уже имеющийся блок, и создать еще 89 | два блока. Они просто добавились в конец списка. 90 | 91 | Тоже самое с чтениями - дескриптор читает последовательно 92 | перепрыгивая на следующий блок, когда дочитал предыдущий. 93 | 94 | Поскольку задача - реализация библиотеки, то программы main нет, а 95 | значит и принимать на вход некуда. Вы можете писать тесты на С, в 96 | отдельном файле, где будет main, и куда будет делаться include 97 | вашего решения. Например, создается файл main.c, который делает 98 | include "userfs.h" и в функции main делает какие-то тесты. Это все 99 | собирается так: 100 | 101 | gcc userfs.c main.c 102 | 103 | Варианты решения: 104 | 105 | - 15 баллов: реализовать все функции из userfs.c, как описано 106 | выше. 107 | 108 | - +5 баллов: реализовать режимы открытия файла: на чтение, на 109 | запись, на чтение-запись. Когда файл открывается, дескриптор в 110 | себе хранит режим. Когда зовется функция read/write, то режим 111 | проверяется. Вот и все. Флаги должны быть названы UFS_READ_ONLY, 112 | UFS_WRITE_ONLY, UFS_READ_WRITE. По умолчанию файл должен 113 | открываться на UFS_READ_WRITE. В случае ошибки доступа должна 114 | выставляться ошибка UFS_ERR_NO_PERMISSION. 115 | 116 | - +5 баллов: реализовать resize. Появляется функция 117 | 118 | ufs_resize(int fd, size_t new_size) 119 | 120 | Она увеличивет или уменьшает размер файла. Если файл 121 | уменьшается, и часть файловых дескрипторов начинает указывать за 122 | пределы размера файла, то они сдвигаются на его новый конец. 123 | 124 | Добавочные пункты на +5 баллов друг друга не включают. То есть 125 | можно не делать ни одного, можно сделать первый, или второй, или 126 | оба для +10. 127 | -------------------------------------------------------------------------------- /tasks/3/userfs.c: -------------------------------------------------------------------------------- 1 | #include "userfs.h" 2 | #include 3 | 4 | enum { 5 | BLOCK_SIZE = 512, 6 | MAX_FILE_SIZE = 1024 * 1024 * 1024, 7 | }; 8 | 9 | /** Global error code. Set from any function on any error. */ 10 | static enum ufs_error_code ufs_error_code = UFS_ERR_NO_ERR; 11 | 12 | struct block { 13 | /** Block memory. */ 14 | char *memory; 15 | /** How many bytes are occupied. */ 16 | int occupied; 17 | /** Next block in the file. */ 18 | struct block *next; 19 | /** Previous block in the file. */ 20 | struct block *prev; 21 | 22 | /* PUT HERE OTHER MEMBERS */ 23 | }; 24 | 25 | struct file { 26 | /** Double-linked list of file blocks. */ 27 | struct block *block_list; 28 | /** 29 | * Last block in the list above for fast access to the end 30 | * of file. 31 | */ 32 | struct block *last_block; 33 | /** How many file descriptors are opened on the file. */ 34 | int refs; 35 | /** File name. */ 36 | const char *name; 37 | /** Files are stored in a double-linked list. */ 38 | struct file *next; 39 | struct file *prev; 40 | 41 | /* PUT HERE OTHER MEMBERS */ 42 | }; 43 | 44 | /** List of all files. */ 45 | static struct file *file_list = NULL; 46 | 47 | struct filedesc { 48 | struct file *file; 49 | 50 | /* PUT HERE OTHER MEMBERS */ 51 | }; 52 | 53 | /** 54 | * An array of file descriptors. When a file descriptor is 55 | * created, its pointer drops here. When a file descriptor is 56 | * closed, its place in this array is set to NULL and can be 57 | * taken by next ufs_open() call. 58 | */ 59 | static struct filedesc **file_descriptors = NULL; 60 | static int file_descriptor_count = 0; 61 | static int file_descriptor_capacity = 0; 62 | 63 | enum ufs_error_code 64 | ufs_errno() 65 | { 66 | return ufs_error_code; 67 | } 68 | 69 | int 70 | ufs_open(const char *filename, int flags) 71 | { 72 | /* IMPLEMENT THIS FUNCTION */ 73 | ufs_error_code = UFS_ERR_NOT_IMPLEMENTED; 74 | return -1; 75 | } 76 | 77 | ssize_t 78 | ufs_write(int fd, const char *buf, size_t size) 79 | { 80 | /* IMPLEMENT THIS FUNCTION */ 81 | ufs_error_code = UFS_ERR_NOT_IMPLEMENTED; 82 | return -1; 83 | } 84 | 85 | ssize_t 86 | ufs_read(int fd, char *buf, size_t size) 87 | { 88 | /* IMPLEMENT THIS FUNCTION */ 89 | ufs_error_code = UFS_ERR_NOT_IMPLEMENTED; 90 | return -1; 91 | } 92 | 93 | int 94 | ufs_close(int fd) 95 | { 96 | /* IMPLEMENT THIS FUNCTION */ 97 | ufs_error_code = UFS_ERR_NOT_IMPLEMENTED; 98 | return -1; 99 | } 100 | 101 | int 102 | ufs_delete(const char *filename) 103 | { 104 | /* IMPLEMENT THIS FUNCTION */ 105 | ufs_error_code = UFS_ERR_NOT_IMPLEMENTED; 106 | return -1; 107 | } 108 | -------------------------------------------------------------------------------- /tasks/3/userfs.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /** 4 | * User-defined in-memory filesystem. It is as simple as possible. 5 | * Each file lies in the memory as an array of blocks. A file 6 | * has an unique file name, and there are no directories, so the 7 | * FS is a monolithic flat contiguous folder. 8 | */ 9 | 10 | /** 11 | * Here you should specify which features do you want to implement 12 | * via macros: NEED_OPEN_FLAGS and NEED_RESIZE. If you want to 13 | * allow advanced flags, do this here: 14 | * 15 | * #define NEED_OPEN_FLAGS 16 | * 17 | * To allow resize() functions define this: 18 | * 19 | * #define NEED_RESIZE 20 | * 21 | * It is important to define these macros here, in the header, 22 | * because it is used by tests. 23 | */ 24 | 25 | /** 26 | * Flags for ufs_open call. 27 | */ 28 | enum open_flags { 29 | /** 30 | * If the flag specified and a file does not exist - 31 | * create it. 32 | */ 33 | UFS_CREATE = 1, 34 | 35 | #ifdef NEED_OPEN_FLAGS 36 | 37 | /** 38 | * With this flag it is allowed to only read the file. 39 | */ 40 | UFS_READ_ONLY = 2, 41 | /** 42 | * With this flag it is allowed to only write into the 43 | * file. 44 | */ 45 | UFS_WRITE_ONLY = 4, 46 | /** 47 | * With this flag it is allowed to both read and write 48 | * into the file. 49 | */ 50 | UFS_READ_WRITE = 8, 51 | 52 | #endif 53 | }; 54 | 55 | /** Possible errors from all functions. */ 56 | enum ufs_error_code { 57 | UFS_ERR_NO_ERR = 0, 58 | UFS_ERR_NO_FILE, 59 | UFS_ERR_NO_MEM, 60 | UFS_ERR_NOT_IMPLEMENTED, 61 | 62 | #ifdef NEED_OPEN_FLAGS 63 | 64 | UFS_ERR_NO_PERMISSION, 65 | #endif 66 | }; 67 | 68 | /** Get code of the last error. */ 69 | enum ufs_error_code 70 | ufs_errno(); 71 | 72 | /** 73 | * Open a file by filename. 74 | * @param filename Name of a file to open. 75 | * @param flags Bitwise combination of open_flags. 76 | * 77 | * @retval > 0 File descriptor. 78 | * @retval -1 Error occurred. Check ufs_errno() for a code. 79 | * - UFS_ERR_NO_FILE - no such file, and UFS_CREATE flag is 80 | * not specified. 81 | */ 82 | int 83 | ufs_open(const char *filename, int flags); 84 | 85 | /** 86 | * Write data to the file. 87 | * @param fd File descriptor from ufs_open(). 88 | * @param buf Buffer to write. 89 | * @param size Size of @a buf. 90 | * 91 | * @retval > 0 How many bytes were written. 92 | * @retval -1 Error occurred. Check ufs_errno() for a code. 93 | * - UFS_ERR_NO_FILE - invalid file descriptor. 94 | * - UFS_ERR_NO_MEM - not enough memory. 95 | */ 96 | ssize_t 97 | ufs_write(int fd, const char *buf, size_t size); 98 | 99 | /** 100 | * Read data from the file. 101 | * @param fd File descriptor from ufs_open(). 102 | * @param buf Buffer to read into. 103 | * @param size Maximum bytes to read. 104 | * 105 | * @retval > 0 How many bytes were read. 106 | * @retval 0 EOF. 107 | * @retval -1 Error occurred. Check ufs_errno() for a code. 108 | * - UFS_ERR_NO_FILE - invalid file descriptor. 109 | */ 110 | ssize_t 111 | ufs_read(int fd, char *buf, size_t size); 112 | 113 | /** 114 | * Close a file. 115 | * @param fd File descriptor from ufs_open(). 116 | * @retval 0 Success. 117 | * @retval -1 Error occurred. Check ufs_errno() for a code. 118 | * - UFS_ERR_NO_FILE - invalid file descriptor. 119 | */ 120 | int 121 | ufs_close(int fd); 122 | 123 | /** 124 | * Delete a file by its name. Note, that it is allowed to drop the 125 | * file even if there are opened descriptors. In such a case the 126 | * file content will live until the last descriptor is closed. If 127 | * the file is deleted, it is allowed to create a new one with the 128 | * same name immediately and it should not affect existing opened 129 | * descriptors of the deleted file. 130 | * 131 | * @param filename Name of a file to delete. 132 | * @retval -1 Error occurred. Check ufs_errno() for a code. 133 | * - UFS_ERR_NO_FILE - no such file. 134 | */ 135 | int 136 | ufs_delete(const char *filename); 137 | 138 | #ifdef NEED_RESIZE 139 | 140 | /** 141 | * Resize a file opened by the file descriptor @a fd. If current 142 | * file size is less than @a new_size, then new empty blocks are 143 | * created and positions of opened file descriptors are not 144 | * changed. If the current size is bigger than @a new_size, then 145 | * the blocks are truncated. Opened file descriptors behind the 146 | * new file size should proceed from the new file end. 147 | * 148 | * @param fd File descriptor from ufs_open(). 149 | * @param new_size New file size. 150 | * @retval 0 Success. 151 | * @retval -1 Error occurred. 152 | * - UFS_ERR_NO_FILE - invalid file descriptor. 153 | * - UFS_ERR_NO_MEM - not enough memory. Can appear only when 154 | * @a new_size is bigger than the current size. 155 | */ 156 | int 157 | ufs_resize(int fd, size_t new_size); 158 | 159 | #endif 160 | -------------------------------------------------------------------------------- /tasks/4/Makefile: -------------------------------------------------------------------------------- 1 | all: test.o thread_pool.o 2 | gcc test.o thread_pool.o 3 | 4 | test.o: test.c 5 | gcc -c test.c -o test.o -I ../utils 6 | 7 | thread_pool.o: thread_pool.c 8 | gcc -c thread_pool.c -o thread_pool.o 9 | -------------------------------------------------------------------------------- /tasks/4/task.txt: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------ 2 | Пул потоков. 3 | Язык: С. 4 | Время: 2 недели. 5 | ------------------------------------------------------------------ 6 | 7 | Нужно реализовать пул потоков. В разных программах, выполняющих 8 | много независимых и легко распараллеливаемых задач часто бывает 9 | удобно разносить их по разным потокам. Но создавать поток на 10 | каждую необходимость что-то вынести в него довольно дорого по 11 | времени и ресурсам. Если задача не слишком долгая, не читает диск, 12 | сеть, то создание/удаление потока может занять больше времени, чем 13 | сама задача. 14 | 15 | Тогда обычно либо задачи вообще не параллелят, либо при их большом 16 | количестве создают пул потоков. Это такой резерв рабочих потоков, 17 | который имеет очередь задач и некое число потоков, которые с 18 | очереди задачи забирают. Таким образом, можно всегда иметь под 19 | рукой уже созданный поток, который может быстро подхватить любую 20 | задачу, а в конце вместо завершения возьмет следующую. 21 | 22 | В библиотеках часто есть уже готовое решение: в Qt это класс 23 | QThreadPool, в .NET это класс ThreadPool, в boost это класс 24 | thread_pool. В задании нужно реализовать свое, подобное. 25 | 26 | В файлах thread_pool.h и thread_pool.c можно найти шаблоны функций 27 | и структур, которые надо реализовать. 28 | 29 | Пул потоков описывается структурой struct thread_pool, 30 | реализованной в thread_pool.c, а у пользователя может быть на нее 31 | только указатель. Каждая задача аналогично описывается структурой 32 | struct thread_task, которые пользователь может создавать и класть 33 | в пул в очередь. 34 | 35 | Пользователь может проверять состояние задачи (ждет постановки в 36 | поток; уже в потоке и выполняется), можно дождаться ее завершения 37 | и получить результат при помощи thread_task_join, наподобие 38 | pthread_join. 39 | 40 | В решении нужно обратить внимание, что thread_pool при создании 41 | через thread_pool_new не должен сразу стартовать все потоки. 42 | Потоки должны создаваться по мере необходимости, пока не достигнут 43 | лимита, заданного пользователем в thread_pool_new. 44 | 45 | Поскольку задача - реализация библиотеки, то программы main нет, а 46 | значит и принимать на вход некуда. Вы можете писать тесты на С, в 47 | отдельном файле, где будет main, и куда будет делаться include 48 | вашего решения. Например, создается файл main.c, который делает 49 | include "thread_pool.h" и в функции main делает какие-то тесты. 50 | Это все собирается так: 51 | 52 | gcc thread_pool.c main.c 53 | 54 | Варианты решения: 55 | 56 | - 15 баллов: реализовать все функции из thread_pool.h, как описано 57 | выше и в самом файле. 58 | 59 | - +5 баллов: реализовать функцию detach. В thread_pool.h уже 60 | определена функция detach под макросом NEED_DETACH. 61 | 62 | - +5 баллов: реализовать таймаут для thread_task_join(): 63 | 64 | int 65 | thread_task_join(struct thread_task *task, double timeout, 66 | void **result); 67 | 68 | Таймаут здесь в секундах. Эта функция должна начать возвращать 69 | новый код ошибки: TPOOL_ERR_TIMEOUT. 70 | 71 | Добавочные пункты на +5 баллов друг друга не включают. То есть 72 | можно не делать ни одного, можно сделать первый, или второй, или 73 | оба для +10. 74 | -------------------------------------------------------------------------------- /tasks/4/test.c: -------------------------------------------------------------------------------- 1 | #include "thread_pool.h" 2 | #include "unit.h" 3 | #include 4 | 5 | static void 6 | test_new(void) 7 | { 8 | unit_test_start(); 9 | 10 | struct thread_pool *p; 11 | unit_check(thread_pool_new(TPOOL_MAX_THREADS + 1, &p) == 12 | TPOOL_ERR_INVALID_ARGUMENT, "too big thread count is "\ 13 | "forbidden"); 14 | unit_check(thread_pool_new(0, &p) == TPOOL_ERR_INVALID_ARGUMENT, 15 | "0 thread count is forbidden"); 16 | unit_check(thread_pool_new(-1, &p) == TPOOL_ERR_INVALID_ARGUMENT, 17 | "negative thread count is forbidden"); 18 | 19 | unit_check(thread_pool_new(1, &p) == 0, "1 max thread is allowed"); 20 | unit_check(thread_pool_thread_count(p) == 0, 21 | "0 active threads after creation"); 22 | unit_check(thread_pool_delete(p) == 0, "delete without tasks"); 23 | 24 | unit_check(thread_pool_new(TPOOL_MAX_THREADS, &p) == 0, 25 | "max thread count is allowed"); 26 | unit_check(thread_pool_thread_count(p) == 0, 27 | "0 active threads after creation"); 28 | unit_check(thread_pool_delete(p) == 0, "delete"); 29 | 30 | unit_test_finish(); 31 | } 32 | 33 | static void * 34 | task_incr_f(void *arg) 35 | { 36 | ++*((int *) arg); 37 | return arg; 38 | } 39 | 40 | static void 41 | test_push(void) 42 | { 43 | unit_test_start(); 44 | 45 | struct thread_pool *p; 46 | struct thread_task *t; 47 | unit_fail_if(thread_pool_new(3, &p) != 0); 48 | int arg = 0; 49 | void *result; 50 | unit_check(thread_task_new(&t, task_incr_f, &arg) == 0, 51 | "created new task"); 52 | unit_check(thread_task_delete(t) == 0, 53 | "task can be deleted before push"); 54 | unit_fail_if(thread_task_new(&t, task_incr_f, &arg) != 0); 55 | 56 | unit_check(thread_task_join(t, &result) == TPOOL_ERR_TASK_NOT_PUSHED, 57 | "can't join a not pushed task"); 58 | unit_check(thread_pool_push_task(p, t) == 0, "pushed"); 59 | unit_check(thread_task_delete(t) == TPOOL_ERR_TASK_IN_POOL, 60 | "can't delete before join"); 61 | unit_check(thread_task_join(t, &result) == 0, "joined"); 62 | unit_check(result == &arg && arg == 1, "the task really did something"); 63 | 64 | unit_check(thread_pool_thread_count(p) == 1, "one active thread"); 65 | unit_check(thread_pool_push_task(p, t) == 0, "pushed again"); 66 | unit_check(thread_task_join(t, &result) == 0, "joined"); 67 | unit_check(thread_pool_thread_count(p) == 1, "still one active thread"); 68 | unit_check(thread_task_delete(t) == 0, "deleted after join"); 69 | 70 | unit_fail_if(thread_pool_delete(p) != 0); 71 | 72 | unit_test_finish(); 73 | } 74 | 75 | static void * 76 | task_lock_unlock_f(void *arg) 77 | { 78 | pthread_mutex_t *m = (pthread_mutex_t *) arg; 79 | pthread_mutex_lock(m); 80 | pthread_mutex_unlock(m); 81 | return arg; 82 | } 83 | 84 | static void 85 | test_thread_pool_delete(void) 86 | { 87 | unit_test_start(); 88 | 89 | void *result; 90 | struct thread_pool *p; 91 | struct thread_task *t; 92 | pthread_mutex_t m; 93 | pthread_mutex_init(&m, NULL); 94 | unit_fail_if(thread_pool_new(3, &p) != 0); 95 | unit_fail_if(thread_task_new(&t, task_lock_unlock_f, &m) != 0); 96 | 97 | pthread_mutex_lock(&m); 98 | unit_fail_if(thread_pool_push_task(p, t) != 0); 99 | unit_check(thread_pool_delete(p) == TPOOL_ERR_HAS_TASKS, "delete does "\ 100 | "not work until there are not finished tasks"); 101 | pthread_mutex_unlock(&m); 102 | unit_fail_if(thread_task_join(t, &result) != 0); 103 | unit_fail_if(thread_task_delete(t) != 0); 104 | 105 | unit_check(thread_pool_delete(p) == 0, "now delete works"); 106 | pthread_mutex_destroy(&m); 107 | 108 | unit_test_finish(); 109 | } 110 | 111 | int 112 | main(void) 113 | { 114 | unit_test_start(); 115 | 116 | test_new(); 117 | test_push(); 118 | test_thread_pool_delete(); 119 | 120 | unit_test_finish(); 121 | return 0; 122 | } 123 | -------------------------------------------------------------------------------- /tasks/4/thread_pool.c: -------------------------------------------------------------------------------- 1 | #include "thread_pool.h" 2 | #include 3 | 4 | struct thread_task { 5 | thread_task_f function; 6 | void *arg; 7 | 8 | /* PUT HERE OTHER MEMBERS */ 9 | }; 10 | 11 | struct thread_pool { 12 | pthread_t *threads; 13 | 14 | /* PUT HERE OTHER MEMBERS */ 15 | }; 16 | 17 | int 18 | thread_pool_new(int max_thread_count, struct thread_pool **pool) 19 | { 20 | /* IMPLEMENT THIS FUNCTION */ 21 | return TPOOL_ERR_NOT_IMPLEMENTED; 22 | } 23 | 24 | int 25 | thread_pool_thread_count(const struct thread_pool *pool) 26 | { 27 | /* IMPLEMENT THIS FUNCTION */ 28 | return TPOOL_ERR_NOT_IMPLEMENTED; 29 | } 30 | 31 | int 32 | thread_pool_delete(struct thread_pool *pool) 33 | { 34 | /* IMPLEMENT THIS FUNCTION */ 35 | return TPOOL_ERR_NOT_IMPLEMENTED; 36 | } 37 | 38 | int 39 | thread_pool_push_task(struct thread_pool *pool, struct thread_task *task) 40 | { 41 | /* IMPLEMENT THIS FUNCTION */ 42 | return TPOOL_ERR_NOT_IMPLEMENTED; 43 | } 44 | 45 | int 46 | thread_task_new(struct thread_task **task, thread_task_f function, void *arg) 47 | { 48 | /* IMPLEMENT THIS FUNCTION */ 49 | return TPOOL_ERR_NOT_IMPLEMENTED; 50 | } 51 | 52 | bool 53 | thread_task_is_finished(const struct thread_task *task) 54 | { 55 | /* IMPLEMENT THIS FUNCTION */ 56 | return false; 57 | } 58 | 59 | bool 60 | thread_task_is_running(const struct thread_task *task) 61 | { 62 | /* IMPLEMENT THIS FUNCTION */ 63 | return false; 64 | } 65 | 66 | int 67 | thread_task_join(struct thread_task *task, void **result) 68 | { 69 | /* IMPLEMENT THIS FUNCTION */ 70 | return TPOOL_ERR_NOT_IMPLEMENTED; 71 | } 72 | 73 | int 74 | thread_task_delete(struct thread_task *task) 75 | { 76 | /* IMPLEMENT THIS FUNCTION */ 77 | return TPOOL_ERR_NOT_IMPLEMENTED; 78 | } 79 | 80 | #ifdef NEED_DETACH 81 | 82 | int 83 | thread_task_detach(struct thread_task *task) 84 | { 85 | /* IMPLEMENT THIS FUNCTION */ 86 | return TPOOL_ERR_NOT_IMPLEMENTED; 87 | } 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /tasks/4/thread_pool.h: -------------------------------------------------------------------------------- 1 | #include 2 | #ifndef THREAD_POOL_DEFINED 3 | #define THREAD_POOL_DEFINED 4 | 5 | struct thread_pool; 6 | struct thread_task; 7 | 8 | typedef void *(*thread_task_f)(void *); 9 | 10 | enum { 11 | TPOOL_MAX_THREADS = 20, 12 | TPOOL_MAX_TASKS = 100000, 13 | }; 14 | 15 | enum thread_poool_errcode { 16 | TPOOL_ERR_INVALID_ARGUMENT = 1, 17 | TPOOL_ERR_TOO_MANY_TASKS, 18 | TPOOL_ERR_HAS_TASKS, 19 | TPOOL_ERR_TASK_NOT_PUSHED, 20 | TPOOL_ERR_TASK_IN_POOL, 21 | TPOOL_ERR_NOT_IMPLEMENTED, 22 | }; 23 | 24 | /** Thread pool API. */ 25 | 26 | /** 27 | * Create a new thread pool with maximum @a max_thread_count 28 | * threads. 29 | * @param max_thread_count Maximum pool size. 30 | * @param[out] Pointer to store result pool object. 31 | * 32 | * @retval 0 Success. 33 | * @retval != 0 Error code. 34 | * - TPOOL_ERR_INVALID_ARGUMENT - max_thread_count is too big, 35 | * or 0. 36 | */ 37 | int 38 | thread_pool_new(int max_thread_count, struct thread_pool **pool); 39 | 40 | /** 41 | * How many threads are created by this pool. Can be less than 42 | * max. 43 | * @param pool Thread pool to get thread count of. 44 | * @retval Thread count. 45 | */ 46 | int 47 | thread_pool_thread_count(const struct thread_pool *pool); 48 | 49 | /** 50 | * Delete @a pool, free its memory. 51 | * @param pool Pool to delete. 52 | * @retval 0 Success. 53 | * @retval != Error code. 54 | * - TPOOL_ERR_HAS_TASKS - pool still has tasks. 55 | */ 56 | int 57 | thread_pool_delete(struct thread_pool *pool); 58 | 59 | /** 60 | * Push @a task into thread pool queue. 61 | * @param pool Pool to push into. 62 | * @param task Task to push. 63 | * 64 | * @retval 0 Success. 65 | * @retval != Error code. 66 | * - TPOOL_ERR_TOO_MANY_TASKS - pool has too many tasks 67 | * already. 68 | */ 69 | int 70 | thread_pool_push_task(struct thread_pool *pool, struct thread_task *task); 71 | 72 | /** Thread pool task API. */ 73 | 74 | /** 75 | * Create a new task to push it into a pool. 76 | * @param[out] task Pointer to store result task object. 77 | * @param function Function to run by this task. 78 | * @param arg Argument for @a function. 79 | * 80 | * @retval Always 0. 81 | */ 82 | int 83 | thread_task_new(struct thread_task **task, thread_task_f function, void *arg); 84 | 85 | /** 86 | * Check if @a task is finished and its result can be obtained. 87 | * @param task Task to check. 88 | */ 89 | bool 90 | thread_task_is_finished(const struct thread_task *task); 91 | 92 | /** 93 | * Check if @a task is running right now. 94 | * @param task Task to check. 95 | */ 96 | bool 97 | thread_task_is_running(const struct thread_task *task); 98 | 99 | /** 100 | * Join the task. If it is not finished, then wait until it is. 101 | * Note, this function does not delete task object. It can be 102 | * reused for a next task or deleted via thread_task_delete. 103 | * @param task Task to join. 104 | * @param[out] result Pointer to stored result of @a task. 105 | * 106 | * @retval 0 Success. 107 | * @retval != 0 Error code. 108 | * - TPOOL_ERR_TASK_NOT_PUSHED - task is not pushed to a pool. 109 | */ 110 | int 111 | thread_task_join(struct thread_task *task, void **result); 112 | 113 | /** 114 | * Delete a task, free its memory. 115 | * @param task Task to delete. 116 | * 117 | * @retval 0 Success. 118 | * @retval != Error code. 119 | * - TPOOL_ERR_TASK_IN_POOL - can not drop the task. It still 120 | * is in a pool. Need to join it firstly. 121 | */ 122 | int 123 | thread_task_delete(struct thread_task *task); 124 | 125 | #ifdef NEED_DETACH 126 | 127 | /** 128 | * Detach a task so as to auto-delete it when it is finished. 129 | * After detach a task can not be accessed via any functions. 130 | * If it is already finished, then just delete it. 131 | * @param task Task to detach. 132 | * @retval 0 Success. 133 | * @retval != Error code. 134 | * - TPOOL_ERR_TASK_NOT_PUSHED - task is not pushed to a 135 | * pool. 136 | */ 137 | int 138 | thread_task_detach(struct thread_task *task); 139 | 140 | #endif 141 | 142 | #endif /* THREAD_POOL_DEFINED */ 143 | -------------------------------------------------------------------------------- /tasks/examples/coro_alloca.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | enum { 7 | /** Total number of coroutines. 3 for simplicity. */ 8 | CORO_COUNT = 3, 9 | /** 10 | * This stack size is really small, especially for Mac. 11 | * But enough for tiny functions. 12 | */ 13 | CORO_STACK = 8 * 1024, 14 | }; 15 | 16 | /** Coroutine. */ 17 | struct coro { 18 | /** Last stop context. */ 19 | jmp_buf ctx; 20 | /** True, if it has finished. */ 21 | bool is_finished; 22 | }; 23 | 24 | /** Currently working coroutine. */ 25 | static int coro_i; 26 | /** Coroutines array. Fixed size for simplicity. */ 27 | static struct coro coros[CORO_COUNT]; 28 | 29 | /** 30 | * That macro allocates some stack and plans to start a coroutine 31 | * here. An actual start happens on coro_continue_next from the 32 | * main function. 33 | */ 34 | #define coro_put_here(c, func, ...) ({ \ 35 | int rc = setjmp((c)->ctx); \ 36 | if (rc == 0) { \ 37 | (c)->is_finished = false; \ 38 | volatile char *p = alloca(CORO_STACK); \ 39 | } else { \ 40 | func(__VA_ARGS__); \ 41 | (c)->is_finished = true; \ 42 | } \ 43 | rc; \ 44 | }) 45 | 46 | /** This coroutine. */ 47 | #define coro_this() (&coros[coro_i]) 48 | 49 | /** 50 | * Give the control to a next not finished coroutine. If all the 51 | * others are finished - do nothing. 52 | */ 53 | #define coro_yield() ({ \ 54 | for (int i = 0; i < CORO_COUNT; ++i) { \ 55 | int next = (coro_i + 1) % CORO_COUNT; \ 56 | if (! coros[next].is_finished) { \ 57 | if (setjmp(coro_this()->ctx) == 0) { \ 58 | coro_i = next; \ 59 | longjmp(coro_this()->ctx, 1); \ 60 | } \ 61 | break; \ 62 | } \ 63 | } \ 64 | }) 65 | 66 | /** 67 | * Continue a next not finished coroutine. Note, that function 68 | * does not return, if a not finished coroutine has been found. 69 | */ 70 | static void 71 | coro_continue_next() 72 | { 73 | for (int i = 0; i < CORO_COUNT; ++i) { 74 | if (! coros[i].is_finished) { 75 | coro_i = i; 76 | longjmp(coro_this()->ctx, 1); 77 | } 78 | } 79 | } 80 | 81 | /** 82 | * Just a function to test the example. It calls itself 83 | * recursively and yields many times. 84 | */ 85 | static void 86 | coro_example_func(int deep) 87 | { 88 | printf("%d: start deep = %d\n", coro_i, deep); 89 | coro_yield(); 90 | if (deep < 2) 91 | coro_example_func(deep + 1); 92 | coro_yield(); 93 | printf("%d: end deep = %d\n", coro_i, deep); 94 | } 95 | 96 | int 97 | main(void) 98 | { 99 | printf("Start main\n"); 100 | for (int i = 0; i < CORO_COUNT; ++i) { 101 | if (coro_put_here(&coros[i], coro_example_func, 0) != 0) 102 | break; 103 | } 104 | /* Repeat until no next unfinished coros. */ 105 | coro_continue_next(); 106 | printf("Finish main\n"); 107 | return 0; 108 | } 109 | -------------------------------------------------------------------------------- /tasks/examples/reuse_port_addr/README.md: -------------------------------------------------------------------------------- 1 | ## SO_REUSEADDR vs SO_REUSEPORT 2 | 3 | `SO_REUSEADDR` is an option allowing to 'recycle' an already closed socket, but still 4 | alive in the kernel. The thing is that TCP socket is not deleted from the kernel right 5 | after it is closed, because it still can have some data to send. This is why it happens, 6 | that an attempt to `bind` to an address right after `close` has been called on it, can 7 | lead to an error 'Address in use'. This option allows to immediately delete the closed 8 | socket, if there is an attempt to bind to its address again. 9 | 10 | `SO_REUSEPORT` is an option, allowing to load-balance new clients over multiple 11 | processes bound to exactly the same IP and port. 12 | 13 | Look at the examples in this folder. There is a server, which tries to bind+listen to 14 | a local address, accept one client, and exit. The client just tries to connect and 15 | exits. The server can accept two command line options: `reuse_addr` and `reuse_port` 16 | to set the corresponding flags before `bind()`. 17 | 18 | ### First test: try to bind to the same address without any options: 19 | ``` 20 | # TERMINAL 1 # TERMINAL 2 # 21 | $> gcc server.c -o server 22 | $> gcc client.c -o client 23 | $> ./server $> ./server 24 | bind success bind error: Address already in use 25 | listen success $> 26 | ^C 27 | ``` 28 | Failed. 29 | 30 | ### Second test: try to bind to the same address with `SO_REUSEADDR`: 31 | ``` 32 | # TERMINAL 1 # TERMINAL 2 # 33 | $> ./server reuse_addr $> ./server reuse_addr 34 | reuse addr is set reuse addr is set 35 | bind success bind error: Address already in use 36 | listen success $> 37 | ^C 38 | ``` 39 | Failed. 40 | 41 | ### Third test: try to bind to the same address with `SO_REUSEPORT`: 42 | ``` 43 | # TERMINAL 1 # TERMINAL 2 # 44 | $> ./server reuse_port $> ./server reuse_port 45 | reuse port is set reuse port is set 46 | bind success bind success 47 | listen success listen success 48 | ... ... 49 | ``` 50 | It works. Now create a client: 51 | 52 | ``` 53 | # TERMINAL 1 # TERMINAL 2 # TERMINAL 3 # 54 | ... ... 55 | listen success listen success 56 | $> ./client 57 | accept success connect success 58 | $> $> 59 | 60 | $> ./client 61 | accept success connect success 62 | $> $> 63 | ``` 64 | Two clients are accepted by different servers. 65 | -------------------------------------------------------------------------------- /tasks/examples/reuse_port_addr/client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | int 12 | main(void) 13 | { 14 | struct sockaddr_in in; 15 | in.sin_family = AF_INET; 16 | inet_aton("127.0.0.1", &in.sin_addr); 17 | in.sin_port = htons(3333); 18 | int s = socket(AF_INET, SOCK_STREAM, 0); 19 | if (connect(s, (struct sockaddr *) &in, sizeof(in)) < 0) 20 | printf("connect error: %s\n", strerror(errno)); 21 | else 22 | printf("connect success\n"); 23 | close(s); 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /tasks/examples/reuse_port_addr/server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | int 13 | create_server(bool reuse_addr, bool reuse_port) 14 | { 15 | struct sockaddr_in in; 16 | in.sin_family = AF_INET; 17 | inet_aton("127.0.0.1", &in.sin_addr); 18 | in.sin_port = htons(3333); 19 | int s = socket(AF_INET, SOCK_STREAM, 0); 20 | assert(s >= 0); 21 | int rc, value = 1; 22 | if (reuse_addr) { 23 | printf("reuse addr is set\n"); 24 | rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &value, 25 | sizeof(int)); 26 | assert(rc == 0); 27 | } 28 | if (reuse_port) { 29 | printf("reuse port is set\n"); 30 | rc = setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &value, 31 | sizeof(int)); 32 | assert(rc == 0); 33 | } 34 | if (bind(s, (struct sockaddr *) &in, sizeof(in)) != 0) { 35 | printf("bind error: %s\n", strerror(errno)); 36 | close(s); 37 | return -1; 38 | } 39 | printf("bind success\n"); 40 | if (listen(s, 128) < 0) { 41 | printf("listen error: %s\n", strerror(errno)); 42 | close(s); 43 | return -1; 44 | } 45 | printf("listen success\n"); 46 | return s; 47 | } 48 | 49 | int 50 | main(int argc, char **argv) 51 | { 52 | bool reuse_addr = false, reuse_port = false; 53 | for (int i = 1; i < argc; ++i) { 54 | if (strcmp(argv[i], "reuse_addr") == 0) 55 | reuse_addr = true; 56 | else if (strcmp(argv[i], "reuse_port") == 0) 57 | reuse_port = true; 58 | } 59 | int s = create_server(reuse_addr, reuse_port); 60 | if (s < 0) 61 | return -1; 62 | int c = accept(s, NULL, NULL); 63 | if (c < 0) { 64 | printf("accept error: %s\n", strerror(errno)); 65 | } else { 66 | printf("accept success\n"); 67 | close(c); 68 | } 69 | close(s); 70 | return 0; 71 | } 72 | -------------------------------------------------------------------------------- /tasks/utils/unit.h: -------------------------------------------------------------------------------- 1 | #ifndef UNIT_TEST_H 2 | #define UNIT_TEST_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define unit_test_start() \ 9 | printf("\t-------- %s started --------\n", __func__) 10 | 11 | #define unit_test_finish() \ 12 | printf("\t-------- %s done --------\n", __func__) 13 | 14 | #define unit_fail_if(cond) ({ \ 15 | if (cond) { \ 16 | printf("Test failed, line %d\n", __LINE__); \ 17 | exit(-1); \ 18 | } \ 19 | }) 20 | 21 | #define unit_msg(...) ({ \ 22 | printf("# "); \ 23 | printf(__VA_ARGS__); \ 24 | printf("\n"); \ 25 | }) 26 | 27 | #define unit_check(cond, msg) ({ \ 28 | if (! (cond)) { \ 29 | printf("not ok - %s\n", (msg)); \ 30 | unit_fail_if(true); \ 31 | } else { \ 32 | printf("ok - %s\n", (msg)); \ 33 | } \ 34 | }) 35 | 36 | #endif 37 | --------------------------------------------------------------------------------