[C, Разработка под Linux] Давайте напишем Linux терминал
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
ПриветствиеВсем привет! Хочу поделиться своим опытом написания собственного терминала Linux используя Posix API, усаживайтесь поудобнее.
Итоговый результатЧто должен уметь наш терминал
- Запуск процессов в foreground и background режиме
- Завершение background процессов из терминала
- Поддержка перемещения по директориям
Как устроена работа терминала
- Считывание строки из стандартного потока ввода
- Разбиение строки на токены
- Создание дочернего процесса с помощью системного вызова fork
- Замена дочернего процесса на необходимый с помощью системного вызова exec
- Ожидание завершения дочернего процесса (в случае foreground процесса)
Немного про системный вызов fork()Простыми словами системный вызов fork создает полный клон текущего процесса, отличаются они лишь своим идентификатором, т. е. pid. Рассмотрим пример:
#include <stdio.h>
#include <unistd.h>
#include <wait.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("I'm child process!\n");
} else {
printf("I'm parent process!\n");
wait(NULL);
}
return 0;
}
Что выведет данная программа:I'm parent process!
I'm child process!Что же произошло? Системный вызов fork создал клон процесса, т. е. теперь мы имеем родительский и дочерний процесс. Чтобы отличить дочерний процесс от родительского в коде достаточно сделать проверку. Если результат функции fork равен 0 - мы имеем дело с дочерним процессом, если нет - с родительским. Это не означает, что в операционной системе id дочернего процесса равен 0.Причем, порядок выполнения дочернего и родительского процесса ничем не задекларирован. Все будет зависеть от планировщика операционной системы. Поэтому в конце блока родительского процесса добавлена строчка wait(NULL), которая дожидается окончания дочернего процесса.Подробнее про exec()В документацииесть различные вариации системного вызова exec, но одни отличаются только способом передачи токенов в параметры функции, смысл от этого не изменяется.Системный вызов exec заменяет текущий процесс сторонним. Естественно, сторонний процесс задается через параметры функции.Рассмотрим пример:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
execlp("ls", "ls", "-l", NULL);
exit(1);
} else {
waitpid(pid, NULL, 0);
}
return 0;
}
Что выведет данная программаtotal 16
-rwxr-xr-x 1 runner runner 8456 Jan 13 07:33 main
-rw-r--r-- 1 runner runner 267 Jan 13 07:33 main.cЧто произошло? Родительский процесс как обычно ожидает завершения дочернего процесса. В это время после системного вызова exec происходит замена дочернего процесса на консольную утилиту ls, она была взята для примера. Можно сказать мы реализовали простой терминал, вся логика заключается именно в этом.Перейдем к полноценной реализацииЧасть 1. Чтение строки с консоли.Изначально нам надо уметь считывать строку из командной строки. Думаю, с этим не возникнет сложностей.
char* readline() {
char* line = NULL;
size_t size = 0;
ssize_t str_len;
// Reading line from stdin
if ((str_len = getline(&line, &size, stdin)) == -1) {
// Logging all errors except Ctrl-D - terminal shutdown
if (errno != 0) {
printf("[ERROR] Couldn't read from stdin\n");
}
free(line);
printf("\n");
return NULL;
}
// Remove useless \n symbol if exists
if (line[str_len - 1] == '\n') {
line[str_len - 1] = '\0';
}
return line;
}
В данной функции происходит чтение строки с применением функции getline. После чего, если в конце строки имеется символ переноса строки, удаляем его.Обратите внимание, что при чтении могут возникнуть ошибки, одна из них - нажатие сочетания клавиш ctrl-D. Однако это штатный случай завершения работы терминала, следовательно в данном случае не должна выводиться ошибка. Часть 2. Разбиение строки на токены.В данной части будет представлена реализация разбиения строки на массив токенов.
#define DEFAULT_BUFF_SIZE 16
#define TOKENS_DELIMITERS " \t"
Определения начальной длины массива и разделителей строки.
char** split(char* line) {
size_t position = 0;
size_t buff_size = DEFAULT_BUFF_SIZE;
char* token;
// Allocate memory for tokens array
char** tokens = (char**)malloc(sizeof(char*) * buff_size);
if (tokens == NULL) {
printf("[ERROR] Couldn't allocate buffer for splitting!\n");
return NULL;
}
// Tokenize process
token = strtok(line, TOKENS_DELIMITERS);
while (token != NULL) {
// Emplace token to array
tokens[position++] = token;
// If array free space ended - increase array
if (position >= buff_size) {
buff_size *= 2;
tokens = (char**)realloc(tokens, buff_size * sizeof(char*));
if (tokens == NULL) {
printf("[ERROR] Couldn't reallocate buffer for tokens!\n");
return NULL;
}
}
// Getting next token
token = strtok(NULL, TOKENS_DELIMITERS);
}
// Place NULL to the end of tokens array
tokens[position] = NULL;
return tokens;
}
Код выглядит довольно громоздким, однако в нем нет ничего сложного.Очередной токен получается с использованием функции strtok. После чего данный токен копируется в массив токенов. Если в массиве токенов не достаточно места, массив увеличивается в 2 раза.Завершается всё добавлением завершающего токена равного NULL, т. к. функция exec() ожидает наличие данного завершающего токена.Часть 3. Выполнение процессов.Структура хранения списка запущенных процессов.Напишем определения структур для foreground и background процессов, fg_task и bg_task. А также определение структуры для хранения всех процессов tasks.
// Struct of background task
struct bg_task_t {
pid_t pid; // Process id
bool finished; // Process state
char* timestamp; // Process state
char* cmd; // Command cmd
};
typedef struct bg_task_t bg_task;
// Struct of foreground task
struct fg_task_t {
pid_t pid; // Process id
bool finished; // Process state
};
typedef struct fg_task_t fg_task;
// Struct of all tasks
struct tasks_t {
fg_task foreground; // Process id of foreground bg_task
bg_task* background; // Background task list
size_t cursor; // Cursor of background tasks
size_t capacity; // Background array capacity
};
typedef struct tasks_t tasks;
Создадим в коде глобальную переменную типа tasks, которая и будет хранить все наши запущенные процессы.
// Global variable for storing active tasks
tasks t = {
.foreground = {
.pid = -1,
.finished = true
},
.background = NULL,
.cursor = 0,
.capacity = 0
};
Вспомогательные функции добавления процессов.Установка foreground процесса выглядит банально и не нуждается в комментировании.
void set_foreground(pid_t pid) {
t.foreground.pid = pid;
t.foreground.finished = 0;
}
Добавление background процесса выглядит посложнее.
int add_background(pid_t pid, char* name) {
// Temp background task variable
bg_task* bt;
// If end of free space in background array - increase size
if (t.cursor >= t.capacity) {
t.capacity = t.capacity * 2 + 1;
t.background = (bg_task*)realloc(t.background, sizeof(bg_task) * t.capacity);
if (t.background == NULL) {
printf("[ERROR] Couldn't reallocate buffer for background tasks!\n");
return -1;
}
}
// Print info about process start
printf("[%zu] started.\n", t.cursor);
// Save task in temp variable
bt = &t.background[t.cursor];
// Save process info in array
bt->pid = pid;
bt->finished = false;
time_t timestamp = time(NULL);
bt->timestamp = ctime(×tamp);
bt->cmd = strdup(name);
// Move cursor right
t.cursor += 1;
return 0;
}
На деле же все проще. Смотрим есть ли место в массиве для хранения информации о процессе. Если его недостаточно - увеличиваем длину массива в 2 раза. После чего происходит добавление структуры bg_task в массив и последующее заполнение полей структуры информацией о процессе.Данная функция возвращает -1 в случае неудачи.Вспомогательные функции для завершения процессов.Добавим функцию экстренного завершения foreground процесса. Данная функция с помощью системного вызова kill с параметром SIGTERM завершает процесс по id процесса.
void kill_foreground() {
if (t.foreground.pid != -1) {
// Kill process
kill(t.foreground.pid, SIGTERM);
// Set finished flag
t.foreground.finished = true;
printf("\n");
}
}
Также добавим функцию для завершения background процесса.
int term(char** args) {
char* idx_str; // Cursor in index arg
int proc_idx = 0; // Converted to int index arg
if (args[1] == NULL) {
printf("[ERROR] No process index to stop!\n");
} else {
// Set cursor in index arg
idx_str = args[1];
// Convert string index arg to int
while (*idx_str >= '0' && *idx_str <= '9') {
proc_idx = (proc_idx * 10) + ((*idx_str) - '0');
// Move cursor to right
idx_str += 1;
}
// Kill process if process index not bad
// and target process not finished
if (*idx_str != '\0' || proc_idx >= t.cursor) {
printf("[ERROR] Incorrect background process index!\n");
} else if (!t.background[proc_idx].finished) {
kill(t.background[proc_idx].pid, SIGTERM);
}
}
return CONTINUE;
}
Данная функция принимает в себя массив токенов вида {"term", "<bg task index>", NULL}. После чего преобразует токен индекса background задачи в число. Убивает background задачу посредством системного вызова kill.Непосредственно запуск процессов.Для удобства введем функцию is_background, определяющую является ли задача фоновым процессом. Данная функция просто проверяет наличие & в конце.
int is_background(char** args) {
// Current position in array
int last_arg = 0;
// Finding last arg in array
while (args[last_arg + 1] != NULL) {
last_arg += 1;
}
// Checking if task is background`
if (strcmp(args[last_arg], "&") == 0) {
// Remove '&' token for future executing
args[last_arg] = NULL;
// Return true
return 1;
}
// Return false if: '&' wasn't founded
return 0;
}
Введем функцию launch которая будет запускать background процесс если в конце присутствует токен &, иначе будет запускаться foreground процесс.
int launch(char** args) {
pid_t pid; // Fork process id
int background; // Is background task
// Checking if task is background
background = is_background(args);
// Create child process
pid = fork();
// If created failure log error
if (pid < 0) {
printf("[ERROR] Couldn't create child process!\n");
}
// Child process
else if (pid == 0) {
// Try launch task
if (execvp(args[0], args) == -1) {
printf("[ERROR] Couldn't execute unknown command!\n");
}
exit(1);
}
// Parent process
else {
if (background) {
// Try add background task to array
if (add_background(pid, args[0]) == -1) {
// Kill all processes and free
// memory before exit
quit();
}
} else {
// Set foreground task to store
set_foreground(pid);
// Wait while process not ended
if (waitpid(pid, NULL, 0) == -1) {
// Logging error if process tracked with error
// Except when interrupted by a signal
if (errno != EINTR) {
printf("[ERROR] Couldn't track the completion of the process!\n");
}
}
}
}
return CONTINUE;
}
То, что происходит в этой функции уже должно быть все понятно.
- Создается дубликат процесса с помощью системного вызова fork
- Заменяем дочерний процесс на требуемый с помощью системного вызова exec
- Определяем является ли процесс фоновым
- Если процесс фоновый - просто добавляем его в список bacground задач
- Если процесс не фоновый - дожидаемся окончания выполнения процесса
В функции присутствует неизвестная функция quit. Ее мы разберем в следующем блоке.Вспомогательные функции для терминала.Введем функцию execute, которая в зависимости от первого токена выбирает нужное действие.
int execute(char** args) {
if (args[0] == NULL) {
return CONTINUE;
} else if (strcmp(args[0], "cd") == 0) {
return cd(args);
} else if (strcmp(args[0], "help") == 0) {
return help();
} else if (strcmp(args[0], "quit") == 0) {
return quit();
} else if (strcmp(args[0], "bg") == 0) {
return bg();
} else if (strcmp(args[0], "term") == 0) {
return term(args);
} else {
return launch(args);
}
}
Данная функция пропускает действие, если первый токен NULL. Переходит в директорию, если первый токен cd. Выводит справку о пользовании, если первый токен help. Завершает работу терминала, если первый токен quit. Выводит список background задач, если первый токен bg. Убивает процесс по индексу, если первый токен term. Во всех других случаях запускается процесс.Реализация вспомогательных функций.
#define CONTINUE 1
#define EXIT 0
Значение CONTINUEозначает дальнейшее исполнение главного цикла терминала. Значение EXITпрерывает выполнение главного цикла программы.
int cd(char** args) {
if (args[1] == NULL) {
printf("[ERROR] Expected argument for "cd" command!\n");
} else if (chdir(args[1]) != 0) {
printf("[ERROR] Couldn't change directory to "%s"!\n", args[1]);
}
return CONTINUE;
}
int help() {
printf(
"Simple shell by Denis Glazkov. \n\n"
"Just type program names and arguments, and hit enter. \n"
"Run tasks in background using '&' in the end of command. \n\n"
"Built in functions: \n"
" cd <path> - Changes current working directory \n"
" term <bg_task_idx> - Prints list of background tasks \n"
" help - Prints info about shell \n"
" bg - Prints list of background tasks \n"
" quit - Terminates shell and all active tasks\n\n"
"Use the man command for information on other programs. \n"
);
return CONTINUE;
}
int quit() {
// Temp background task variable
bg_task* bt;
// Disable logging on child killed
signal(SIGCHLD, SIG_IGN);
// Kill foreground process
if (!t.foreground.finished) {
kill_foreground();
}
// Kill all active background tasks
for (size_t i = 0; i < t.cursor; i++) {
// Place background task to temp variable
bt = &t.background[i];
// Kill process if active
if (!bt->finished) {
kill(bt->pid, SIGTERM);
}
// Free memory for command name
free(bt->cmd);
}
return EXIT;
}
Функция quit отключает все callback функции по событию SIGCHLD - т. е. функции, выполняющиеся когда дочерний элемент был завершен. После этого завершает все активные процессы.
#define PRIMARY_COLOR "\033[92m"
#define SECONDARY_COLOR "\033[90m"
#define RESET_COLOR "\033[0m"
Основные цвета терминала.
int bg() {
// Temp background task variable
bg_task* bt;
for (size_t i = 0; i < t.cursor; i++) {
// Store background task in temp variable
bt = &t.background[i];
// Print info about task
printf(
"[%zu]%s cmd: %s%s;%s pid: %s%d; %s"
"state: %s%s;%s timestamp: %s%s", i,
SECONDARY_COLOR, RESET_COLOR, bt->cmd,
SECONDARY_COLOR, RESET_COLOR, bt->pid,
SECONDARY_COLOR, RESET_COLOR, bt->finished ? "finished" : "active",
SECONDARY_COLOR, RESET_COLOR, bt->timestamp
);
}
return CONTINUE;
}
Часть 4. Главный цикл терминала.
#include <stdlib.h>
#include <signal.h>
#include "include/shell.h"
int main() {
char* line; // User input
char** args; // Tokens in user input
int status; // Status of execution
// Add signal for killing foreground child on ctrl-c
signal(SIGINT, kill_foreground);
// Add signal for handling end of child processes
signal(SIGCHLD, mark_ended_task);
// Shell is running while
// status == CONTINUE
do {
// Printing left shell info
display();
// Reading user input
line = readline();
if (line == NULL) {
exit(1);
}
// Parse line to tokens
args = split(line);
if (args == NULL) {
free(line);
exit(2);
}
// Try execute command
status = execute(args);
// Free allocated memory
free(line);
free(args);
} while (status);
return 0;
}
Здесь и происходит вся магия. Взгляните на следующие строки. С помощью функции signal задаются callback функции на заданные события.
// Add signal for killing foreground child on ctrl-c
signal(SIGINT, kill_foreground);
// Add signal for handling end of child processes
signal(SIGCHLD, mark_ended_task);
Событие SIGINT- срабатывает при нажатии комбинации ctrl-C, которое в дефолтном поведении завершает работу программы. В нашем же случае мы переназначаем его на завершение foreground процесса.Событие SIGCHLD - срабатывает при завершении дочернего процесса созданyого с помощью системного вызова fork. В нашем случае мы переопределяем его на пометку фоновой задачи как выполненной с помощью функции mark_ended_task.
void mark_ended_task() {
// Temp background task variable
bg_task* bt;
// Get process id of ended process
pid_t pid = waitpid(-1, NULL, 0);
// Handle foreground process
if (pid == t.foreground.pid) {
t.foreground.finished = true;
}
// Handle background process
else {
// Search and remove process form background tasks array
for (size_t i = 0; i < t.cursor; i++) {
// Place task to temp variable
bt = &t.background[i];
if (bt->pid == pid) {
// Print info about process end
printf("[%zu] finished.\n", i);
// Set new state for background process
bt->finished = 1;
break;
}
}
}
}
Все что описано в главном цикле терминала можно описать словами:
- Вывод информации о пользователе и текущей директории с помощью функции display
- Чтение строки из стандартного потока ввода
- Разбиение строки на токены
- Выполнение ранее описанной функции execute, которая в зависимости от массива токенов выполняет нужное нам действие.
Нам осталось реализовать одну оставшуюся функцию display. Которая получает информацию о текущей директории с помощью функции getcwd и имя пользователя с помощью функции getpwuid.
void display() {
// Try get and print username with color
uid_t uid = geteuid();
struct passwd *pw = getpwuid(uid);
if (pw != NULL) {
printf("%s%s%s:", PRIMARY_COLOR, pw->pw_name, RESET_COLOR);
}
// Try get and print current directory with color
char cwd[MAX_DIRECTORY_PATH];
if (getcwd(cwd, MAX_DIRECTORY_PATH) != NULL) {
printf("%s%s%s", SECONDARY_COLOR, cwd, RESET_COLOR);
}
// Print end of shell info
printf("# ");
}
Часть 5. Итоговый результат.
Итоговый результатНадеюсь, данный материал полезен. Если вам есть чем дополнить данный материал, буду рад услышать ваши мысли в комментариях.С исходным кодом проекта вы можете ознакомиться по данной ссылке.
===========
Источник:
habr.com
===========
Похожие новости:
- [Смартфоны, Процессоры] Samsung подтвердила использование AMD RDNA в следующем поколении процессоров Exynos
- [Управление разработкой, Управление персоналом, Интервью] Профессия СТО часть 2
- [IT-инфраструктура, Сетевые технологии, IPv6, Стандарты связи, Сетевое оборудование] В Китае зафиксирован рост популярности IPv6
- [JavaScript, Flutter] Пакет валидации mobx form validation kit 2.0 (TypeScript / Flutter)
- [Медгаджеты, Здоровье] Razer представила маску N95 со светодиодной подсветкой и проекцией голоса
- [Программирование, Разработка мобильных приложений, Учебный процесс в IT, Карьера в IT-индустрии] 1 марта SimbirSoft приглашает на Весенний интенсив
- [Python, Распределённые системы] Реализация распределённых вычислений на языке python с использованием технологии docker
- [Информационная безопасность, IT-стандарты] Как и зачем проходить сертификацию AICPA SOC 2 и 3. Опыт Яндекс.Паспорта
- [Научно-популярное, Мозг, Здоровье, Химия] Лед холодный, огонь горячий, а кактус лучше не обнимать: как мозг формирует сенсорную память
- [Разработка веб-сайтов, JavaScript, Программирование] Будущее JavaScript: декораторы
Теги для поиска: #_c, #_razrabotka_pod_linux (Разработка под Linux), #_terminal (терминал), #_razrabotka (разработка), #_posix, #_konsol (консоль), #_linux, #_c, #_razrabotka_pod_linux (
Разработка под Linux
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 14:40
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
ПриветствиеВсем привет! Хочу поделиться своим опытом написания собственного терминала Linux используя Posix API, усаживайтесь поудобнее. Итоговый результатЧто должен уметь наш терминал
#include <stdio.h>
#include <unistd.h> #include <wait.h> int main() { pid_t pid = fork(); if (pid == 0) { printf("I'm child process!\n"); } else { printf("I'm parent process!\n"); wait(NULL); } return 0; } I'm child process!Что же произошло? Системный вызов fork создал клон процесса, т. е. теперь мы имеем родительский и дочерний процесс. Чтобы отличить дочерний процесс от родительского в коде достаточно сделать проверку. Если результат функции fork равен 0 - мы имеем дело с дочерним процессом, если нет - с родительским. Это не означает, что в операционной системе id дочернего процесса равен 0.Причем, порядок выполнения дочернего и родительского процесса ничем не задекларирован. Все будет зависеть от планировщика операционной системы. Поэтому в конце блока родительского процесса добавлена строчка wait(NULL), которая дожидается окончания дочернего процесса.Подробнее про exec()В документацииесть различные вариации системного вызова exec, но одни отличаются только способом передачи токенов в параметры функции, смысл от этого не изменяется.Системный вызов exec заменяет текущий процесс сторонним. Естественно, сторонний процесс задается через параметры функции.Рассмотрим пример: #include <stdio.h>
#include <stdlib.h> #include <unistd.h> #include <wait.h> int main() { pid_t pid = fork(); if (pid == 0) { execlp("ls", "ls", "-l", NULL); exit(1); } else { waitpid(pid, NULL, 0); } return 0; } -rwxr-xr-x 1 runner runner 8456 Jan 13 07:33 main -rw-r--r-- 1 runner runner 267 Jan 13 07:33 main.cЧто произошло? Родительский процесс как обычно ожидает завершения дочернего процесса. В это время после системного вызова exec происходит замена дочернего процесса на консольную утилиту ls, она была взята для примера. Можно сказать мы реализовали простой терминал, вся логика заключается именно в этом.Перейдем к полноценной реализацииЧасть 1. Чтение строки с консоли.Изначально нам надо уметь считывать строку из командной строки. Думаю, с этим не возникнет сложностей. char* readline() {
char* line = NULL; size_t size = 0; ssize_t str_len; // Reading line from stdin if ((str_len = getline(&line, &size, stdin)) == -1) { // Logging all errors except Ctrl-D - terminal shutdown if (errno != 0) { printf("[ERROR] Couldn't read from stdin\n"); } free(line); printf("\n"); return NULL; } // Remove useless \n symbol if exists if (line[str_len - 1] == '\n') { line[str_len - 1] = '\0'; } return line; } #define DEFAULT_BUFF_SIZE 16
#define TOKENS_DELIMITERS " \t" char** split(char* line) {
size_t position = 0; size_t buff_size = DEFAULT_BUFF_SIZE; char* token; // Allocate memory for tokens array char** tokens = (char**)malloc(sizeof(char*) * buff_size); if (tokens == NULL) { printf("[ERROR] Couldn't allocate buffer for splitting!\n"); return NULL; } // Tokenize process token = strtok(line, TOKENS_DELIMITERS); while (token != NULL) { // Emplace token to array tokens[position++] = token; // If array free space ended - increase array if (position >= buff_size) { buff_size *= 2; tokens = (char**)realloc(tokens, buff_size * sizeof(char*)); if (tokens == NULL) { printf("[ERROR] Couldn't reallocate buffer for tokens!\n"); return NULL; } } // Getting next token token = strtok(NULL, TOKENS_DELIMITERS); } // Place NULL to the end of tokens array tokens[position] = NULL; return tokens; } // Struct of background task
struct bg_task_t { pid_t pid; // Process id bool finished; // Process state char* timestamp; // Process state char* cmd; // Command cmd }; typedef struct bg_task_t bg_task; // Struct of foreground task struct fg_task_t { pid_t pid; // Process id bool finished; // Process state }; typedef struct fg_task_t fg_task; // Struct of all tasks struct tasks_t { fg_task foreground; // Process id of foreground bg_task bg_task* background; // Background task list size_t cursor; // Cursor of background tasks size_t capacity; // Background array capacity }; typedef struct tasks_t tasks; // Global variable for storing active tasks
tasks t = { .foreground = { .pid = -1, .finished = true }, .background = NULL, .cursor = 0, .capacity = 0 }; void set_foreground(pid_t pid) {
t.foreground.pid = pid; t.foreground.finished = 0; } int add_background(pid_t pid, char* name) {
// Temp background task variable bg_task* bt; // If end of free space in background array - increase size if (t.cursor >= t.capacity) { t.capacity = t.capacity * 2 + 1; t.background = (bg_task*)realloc(t.background, sizeof(bg_task) * t.capacity); if (t.background == NULL) { printf("[ERROR] Couldn't reallocate buffer for background tasks!\n"); return -1; } } // Print info about process start printf("[%zu] started.\n", t.cursor); // Save task in temp variable bt = &t.background[t.cursor]; // Save process info in array bt->pid = pid; bt->finished = false; time_t timestamp = time(NULL); bt->timestamp = ctime(×tamp); bt->cmd = strdup(name); // Move cursor right t.cursor += 1; return 0; } void kill_foreground() {
if (t.foreground.pid != -1) { // Kill process kill(t.foreground.pid, SIGTERM); // Set finished flag t.foreground.finished = true; printf("\n"); } } int term(char** args) {
char* idx_str; // Cursor in index arg int proc_idx = 0; // Converted to int index arg if (args[1] == NULL) { printf("[ERROR] No process index to stop!\n"); } else { // Set cursor in index arg idx_str = args[1]; // Convert string index arg to int while (*idx_str >= '0' && *idx_str <= '9') { proc_idx = (proc_idx * 10) + ((*idx_str) - '0'); // Move cursor to right idx_str += 1; } // Kill process if process index not bad // and target process not finished if (*idx_str != '\0' || proc_idx >= t.cursor) { printf("[ERROR] Incorrect background process index!\n"); } else if (!t.background[proc_idx].finished) { kill(t.background[proc_idx].pid, SIGTERM); } } return CONTINUE; } int is_background(char** args) {
// Current position in array int last_arg = 0; // Finding last arg in array while (args[last_arg + 1] != NULL) { last_arg += 1; } // Checking if task is background` if (strcmp(args[last_arg], "&") == 0) { // Remove '&' token for future executing args[last_arg] = NULL; // Return true return 1; } // Return false if: '&' wasn't founded return 0; } int launch(char** args) {
pid_t pid; // Fork process id int background; // Is background task // Checking if task is background background = is_background(args); // Create child process pid = fork(); // If created failure log error if (pid < 0) { printf("[ERROR] Couldn't create child process!\n"); } // Child process else if (pid == 0) { // Try launch task if (execvp(args[0], args) == -1) { printf("[ERROR] Couldn't execute unknown command!\n"); } exit(1); } // Parent process else { if (background) { // Try add background task to array if (add_background(pid, args[0]) == -1) { // Kill all processes and free // memory before exit quit(); } } else { // Set foreground task to store set_foreground(pid); // Wait while process not ended if (waitpid(pid, NULL, 0) == -1) { // Logging error if process tracked with error // Except when interrupted by a signal if (errno != EINTR) { printf("[ERROR] Couldn't track the completion of the process!\n"); } } } } return CONTINUE; }
int execute(char** args) {
if (args[0] == NULL) { return CONTINUE; } else if (strcmp(args[0], "cd") == 0) { return cd(args); } else if (strcmp(args[0], "help") == 0) { return help(); } else if (strcmp(args[0], "quit") == 0) { return quit(); } else if (strcmp(args[0], "bg") == 0) { return bg(); } else if (strcmp(args[0], "term") == 0) { return term(args); } else { return launch(args); } } #define CONTINUE 1
#define EXIT 0 int cd(char** args) {
if (args[1] == NULL) { printf("[ERROR] Expected argument for "cd" command!\n"); } else if (chdir(args[1]) != 0) { printf("[ERROR] Couldn't change directory to "%s"!\n", args[1]); } return CONTINUE; } int help() {
printf( "Simple shell by Denis Glazkov. \n\n" "Just type program names and arguments, and hit enter. \n" "Run tasks in background using '&' in the end of command. \n\n" "Built in functions: \n" " cd <path> - Changes current working directory \n" " term <bg_task_idx> - Prints list of background tasks \n" " help - Prints info about shell \n" " bg - Prints list of background tasks \n" " quit - Terminates shell and all active tasks\n\n" "Use the man command for information on other programs. \n" ); return CONTINUE; } int quit() {
// Temp background task variable bg_task* bt; // Disable logging on child killed signal(SIGCHLD, SIG_IGN); // Kill foreground process if (!t.foreground.finished) { kill_foreground(); } // Kill all active background tasks for (size_t i = 0; i < t.cursor; i++) { // Place background task to temp variable bt = &t.background[i]; // Kill process if active if (!bt->finished) { kill(bt->pid, SIGTERM); } // Free memory for command name free(bt->cmd); } return EXIT; } #define PRIMARY_COLOR "\033[92m"
#define SECONDARY_COLOR "\033[90m" #define RESET_COLOR "\033[0m" int bg() {
// Temp background task variable bg_task* bt; for (size_t i = 0; i < t.cursor; i++) { // Store background task in temp variable bt = &t.background[i]; // Print info about task printf( "[%zu]%s cmd: %s%s;%s pid: %s%d; %s" "state: %s%s;%s timestamp: %s%s", i, SECONDARY_COLOR, RESET_COLOR, bt->cmd, SECONDARY_COLOR, RESET_COLOR, bt->pid, SECONDARY_COLOR, RESET_COLOR, bt->finished ? "finished" : "active", SECONDARY_COLOR, RESET_COLOR, bt->timestamp ); } return CONTINUE; } #include <stdlib.h>
#include <signal.h> #include "include/shell.h" int main() { char* line; // User input char** args; // Tokens in user input int status; // Status of execution // Add signal for killing foreground child on ctrl-c signal(SIGINT, kill_foreground); // Add signal for handling end of child processes signal(SIGCHLD, mark_ended_task); // Shell is running while // status == CONTINUE do { // Printing left shell info display(); // Reading user input line = readline(); if (line == NULL) { exit(1); } // Parse line to tokens args = split(line); if (args == NULL) { free(line); exit(2); } // Try execute command status = execute(args); // Free allocated memory free(line); free(args); } while (status); return 0; } // Add signal for killing foreground child on ctrl-c
signal(SIGINT, kill_foreground); // Add signal for handling end of child processes signal(SIGCHLD, mark_ended_task); void mark_ended_task() {
// Temp background task variable bg_task* bt; // Get process id of ended process pid_t pid = waitpid(-1, NULL, 0); // Handle foreground process if (pid == t.foreground.pid) { t.foreground.finished = true; } // Handle background process else { // Search and remove process form background tasks array for (size_t i = 0; i < t.cursor; i++) { // Place task to temp variable bt = &t.background[i]; if (bt->pid == pid) { // Print info about process end printf("[%zu] finished.\n", i); // Set new state for background process bt->finished = 1; break; } } } }
void display() {
// Try get and print username with color uid_t uid = geteuid(); struct passwd *pw = getpwuid(uid); if (pw != NULL) { printf("%s%s%s:", PRIMARY_COLOR, pw->pw_name, RESET_COLOR); } // Try get and print current directory with color char cwd[MAX_DIRECTORY_PATH]; if (getcwd(cwd, MAX_DIRECTORY_PATH) != NULL) { printf("%s%s%s", SECONDARY_COLOR, cwd, RESET_COLOR); } // Print end of shell info printf("# "); } Итоговый результатНадеюсь, данный материал полезен. Если вам есть чем дополнить данный материал, буду рад услышать ваши мысли в комментариях.С исходным кодом проекта вы можете ознакомиться по данной ссылке. =========== Источник: habr.com =========== Похожие новости:
Разработка под Linux ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 14:40
Часовой пояс: UTC + 5