L'expérience d'apprentissage est meilleure avec les vidéos !
Section a : https://www.bilibili.com/video/BV1gk4y1w7u1/?vd_source=701807c4f8684b13e922d0a8b116af31
Section b : https://www.bilibili.com/video/BV1Mm4y1N7fS/?vd_source=701807c4f8684b13e922d0a 8b1 16af31
Section c : https:// www .bilibili.com/video/BV1Th4y1A7xw/?vd_source=701807c4f8684b13e922d0a8b116af31
Section d : https://www.bilibili.com/video/BV1cm4y1N7XG/?vd_source=701807c4f8684b13e922d0a8b116af3 1 Section
e : https://www.bilibili.com/video /BV1qh4y1Y7gE /?vd_source=701807c4f8684b13e922d0a8b116af31
Section f : https://www.bilibili.com/video/BV1Su4y1675s/?vd_source=701807c4f8684b13e922d0a8b116af31 Section g : https://www.bilibili .com/video/BV1RP4 1187cD/?vd_source=
701807c4f8684b13e922d0a8b116af31
Section h : https://www.bilibili.com/video/BV1mH4y1D7Ze/?vd_source=701807c4f8684b13e922d0a8b116af31
Section i : https://www.bilibili.com/video/BV1rP411t7bd/?vd_source=701807c4f8684b13e922d0a 8b1 16af31 Section
j : https:// www .bilibili.com/video/BV1Jk4y1F7ob/?vd_source=701807c4f8684b13e922d0a8b116af31
Section k : https://www.bilibili.com/video/BV1pm4y1N7CH/?vd_source=701807c4f8684b13e922d0a8b116af31
Dépôt de code : https://github.com/xukanshan/the_truth_of_operationg_system
Section a :
fourchette
Dans cette section, nous allons implémenter fork
Fork est utilisé pour copier un processus, c'est-à-dire pour copier un processus enfant basé sur le processus parent. Mais comme il s’agit essentiellement de deux processus, ils présentent encore de nombreuses différences, telles que des ressources indépendantes et des pids indépendants.
Il existe un tel morceau de code
#include <unistd.h>
#include <stdio.h>
int main() {
int pid = fork();
if (pid == -1)
return 1;
printf("who am I ? my pid is %d\n", getpid());
sleep (5) ;
return 0;
}
Après l'exécution de fork(), le code après fork sera exécuté deux fois car il appartient à deux processus (le processus principal qui a appelé fork et le sous-processus copié) (naturellement, le processus principal et le sous-processus sont exécutés une fois chacun ).
Puisque fork copie le processus et que l'étape de copie est terminée avant la fin du code du fork (en supposant que le code du fork comporte 1000 lignes, la copie est terminée à la ligne 800), donc le retour dans la dernière ligne du code du fork sera exécuté deux fois. Pour le processus parent, fork renvoie le pid du processus enfant. Pour les processus enfants, fork renverra 0. Nous pouvons distinguer les processus parent et enfant en fonction des différentes valeurs renvoyées par fork, de sorte que les processus parent et enfant exécutent des codes différents. Par exemple:
if (pid) {
printf("I am father, my pid is d\n",getpid());
sleep(5);
return 0;
}
else {
printf("I am child, my pid is d\n",getpid());
sleep(5);
return 0;
}
Commencez maintenant à implémenter le fork, implémentez d'abord certaines fonctions d'infrastructure
fork_pid
Il est encapsulé allocate_pid
, car allocate_pid
le mot-clé static était inclus dans l'implémentation précédente, donc afin de ne pas le modifier, l'auteur a adopté une encapsulation supplémentaire.
Modifier ( myos/thread/thread.c )
/* fork进程时为其分配pid,因为allocate_pid已经是静态的,别的文件无法调用.
不想改变函数定义了,故定义fork_pid函数来封装一下。*/
pid_t fork_pid(void)
{
return allocate_pid();
}
Déclaration de fonction, modification ( myos/thread/thread.h )
pid_t fork_pid(void);
get_a_page_without_opvaddrbitmap
Utilisé pour créer un mappage de page physique pour l'adresse virtuelle spécifiée, comparé au get_a_page
bitmap du pool de mémoire virtuelle dans la carte PCB du processus d'exploitation.
Modifier ( myos/kernel/memory.c )
/* 安装1页大小的vaddr,专门针对fork时虚拟地址位图无须操作的情况 */
void *get_a_page_without_opvaddrbitmap(enum pool_flags pf, uint32_t vaddr)
{
struct pool *mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;
lock_acquire(&mem_pool->lock);
void *page_phyaddr = palloc(mem_pool);
if (page_phyaddr == NULL)
{
lock_release(&mem_pool->lock);
return NULL;
}
page_table_add((void *)vaddr, page_phyaddr);
lock_release(&mem_pool->lock);
return (void *)vaddr;
}
Déclaration de fonction, modification ( myos/kernel/memory.h )
void *get_a_page_without_opvaddrbitmap(enum pool_flags pf, uint32_t vaddr);
copy_pcb_vaddrbitmap_stack0
Utilisé pour copier d'abord l'intégralité du contenu de la carte PCB du processus parent sur la carte PCB du processus enfant en fonction du pointeur de la carte PCB du processus parent et enfant transmis, puis définir le contenu de la carte PCB du processus enfant, notamment : pid, elapsed_ticks, status, ticks, parent_pid, general_tag, all_list_tag, u_block_desc , userprog_vaddr (laissez le processus enfant avoir son propre pool de mémoire d'espace d'adressage virtuel utilisateur, mais son bitmap est copié à partir du processus parent). Au cours de ce processus, le contenu de la pile du noyau est entièrement copié.
(myos/userprog/fork.c)
#include "fork.h"
#include "stdint.h"
#include "global.h"
#include "thread.h"
#include "string.h"
#include "debug.h"
#include "process.h"
/* 将父进程的pcb、虚拟地址位图拷贝给子进程 */
static int32_t copy_pcb_vaddrbitmap_stack0(struct task_struct *child_thread, struct task_struct *parent_thread)
{
/* a 复制pcb所在的整个页,里面包含进程pcb信息及特级0极的栈,里面包含了返回地址, 然后再单独修改个别部分 */
memcpy(child_thread, parent_thread, PG_SIZE);
child_thread->pid = fork_pid();
child_thread->elapsed_ticks = 0;
child_thread->status = TASK_READY;
child_thread->ticks = child_thread->priority; // 为新进程把时间片充满
child_thread->parent_pid = parent_thread->pid;
child_thread->general_tag.prev = child_thread->general_tag.next = NULL;
child_thread->all_list_tag.prev = child_thread->all_list_tag.next = NULL;
block_desc_init(child_thread->u_block_desc);
/* b 复制父进程的虚拟地址池的位图 */
uint32_t bitmap_pg_cnt = DIV_ROUND_UP((0xc0000000 - USER_VADDR_START) / PG_SIZE / 8, PG_SIZE);
void *vaddr_btmp = get_kernel_pages(bitmap_pg_cnt);
if (vaddr_btmp == NULL)
return -1;
/* 此时child_thread->userprog_vaddr.vaddr_bitmap.bits还是指向父进程虚拟地址的位图地址
* 下面将child_thread->userprog_vaddr.vaddr_bitmap.bits指向自己的位图vaddr_btmp */
memcpy(vaddr_btmp, child_thread->userprog_vaddr.vaddr_bitmap.bits, bitmap_pg_cnt * PG_SIZE);
child_thread->userprog_vaddr.vaddr_bitmap.bits = vaddr_btmp;
/* 调试用 */
ASSERT(strlen(child_thread->name) < 11); // pcb.name的长度是16,为避免下面strcat越界
strcat(child_thread->name, "_fork");
return 0;
}
copy_body_stack3
Utilisé pour copier les données dans le tas et la pile de l'espace utilisateur du processus en fonction du pointeur PCB des processus parent et enfant transmis. Principe de base : parcourez le bitmap de l'espace d'adressage virtuel dans le userprog_vaddr du processus parent pour déterminer s'il existe des données dans l'espace d'adressage virtuel utilisateur du processus parent. Si tel est le cas, copiez-le dans la zone de transit de l'espace noyau, puis appelez-le page_dir_activate
, passez à la table des pages du sous-processus, appelez pour get_a_page_without_opvaddrbitmap
demander une page physique pour l'adresse virtuelle spécifique au sous-processus (qui n'implique pas le modification du bitmap dans le sous-processus userprog_vaddr), puis copiez les données de la zone de transit du noyau vers la même adresse virtuelle du processus enfant.
Modifier ( myos/userprog/fork.c )
extern void intr_exit(void);
/* 复制子进程的进程体(代码和数据)及用户栈 */
static void copy_body_stack3(struct task_struct *child_thread, struct task_struct *parent_thread, void *buf_page)
{
uint8_t *vaddr_btmp = parent_thread->userprog_vaddr.vaddr_bitmap.bits;
uint32_t btmp_bytes_len = parent_thread->userprog_vaddr.vaddr_bitmap.btmp_bytes_len;
uint32_t vaddr_start = parent_thread->userprog_vaddr.vaddr_start;
uint32_t idx_byte = 0;
uint32_t idx_bit = 0;
uint32_t prog_vaddr = 0;
/* 在父进程的用户空间中查找已有数据的页 */
while (idx_byte < btmp_bytes_len)
{
if (vaddr_btmp[idx_byte])
{
idx_bit = 0;
while (idx_bit < 8)
{
if ((BITMAP_MASK << idx_bit) & vaddr_btmp[idx_byte])
{
prog_vaddr = (idx_byte * 8 + idx_bit) * PG_SIZE + vaddr_start;
/* 下面的操作是将父进程用户空间中的数据通过内核空间做中转,最终复制到子进程的用户空间 */
/* a 将父进程在用户空间中的数据复制到内核缓冲区buf_page,
目的是下面切换到子进程的页表后,还能访问到父进程的数据*/
memcpy(buf_page, (void *)prog_vaddr, PG_SIZE);
/* b 将页表切换到子进程,目的是避免下面申请内存的函数将pte及pde安装在父进程的页表中 */
page_dir_activate(child_thread);
/* c 申请虚拟地址prog_vaddr */
get_a_page_without_opvaddrbitmap(PF_USER, prog_vaddr);
/* d 从内核缓冲区中将父进程数据复制到子进程的用户空间 */
memcpy((void *)prog_vaddr, buf_page, PG_SIZE);
/* e 恢复父进程页表 */
page_dir_activate(parent_thread);
}
idx_bit++;
}
}
idx_byte++;
}
}
build_child_stack
Utilisé pour modifier la valeur de retour du processus enfant et définir sa pile de noyau. intr_stack
Le principe du processus enfant renvoyant 0 : lorsque nous avons construit le mécanisme d'appel système auparavant, la valeur de retour de l'appel système sera placée dans la position eax de la pile d'interruption ( ) dans la pile du noyau , de sorte que la sortie d'interruption ( intr_exit
) poussera eax et mettez la valeur de retour au milieu de chaque axe. Nous modifions donc la valeur de la pile d'interruption de la pile du noyau du processus enfant eax en 0.
Notre processus enfant s'exécute sur l'ordinateur en se préparant et en attendant une certaine interruption d'horloge pour appeler la fonction switch_to à exécuter sur l'ordinateur.
mov eax, [esp + 24]
mov esp, [eax]
pop ebp
pop ebx
pop edi
pop esi
ret
switch_to trouvera le haut de la pile du noyau à partir du PCB du processus enfant et le placera dans esp, puis exécutera les quatre instructions pop et ret de switch_to. La disposition de la pile du noyau du processus enfant après la copie est la suivante :
Par conséquent, il ne nous est certainement pas possible d'utiliser directement la disposition de la pile du noyau comme les processus enfants. Nous devons la modifier manuellement en
ajoutant la pile switch_to devant intr_stack (c'est-à-dire le thread_stack mentionné dans la p694 du livre) , de sorte que l'esp en haut du PCB pointe vers la pile switch_to.top, et l'adresse de retour dans la pile switch_to doit être remplie avec intr_exit
l'adresse de la fonction. Après avoir exécuté ret de cette manière, vous pouvez l'exécuter intr_exit
et utiliser intr_stack pour exécuter le retour d'interruption. Puisque intr_stack copie les informations de la pile utilisateur et les informations cs: ip lorsque le processus parent entre dans l'interruption, donc après la sortie de l'interruption, le processus enfant le fera continuez à exécuter le code du processus parent après cela.
Modifier ( myos/userprog/fork.c )
/* 为子进程构建thread_stack和修改返回值 */
static int32_t build_child_stack(struct task_struct *child_thread)
{
/* a 使子进程pid返回值为0 */
/* 获取子进程0级栈栈顶 */
struct intr_stack *intr_0_stack = (struct intr_stack *)((uint32_t)child_thread + PG_SIZE - sizeof(struct intr_stack));
/* 修改子进程的返回值为0 */
intr_0_stack->eax = 0;
/* b 为switch_to 构建 struct thread_stack,将其构建在紧临intr_stack之下的空间*/
uint32_t *ret_addr_in_thread_stack = (uint32_t *)intr_0_stack - 1;
/*** 这三行不是必要的,只是为了梳理thread_stack中的关系 ***/
uint32_t *esi_ptr_in_thread_stack = (uint32_t *)intr_0_stack - 2;
uint32_t *edi_ptr_in_thread_stack = (uint32_t *)intr_0_stack - 3;
uint32_t *ebx_ptr_in_thread_stack = (uint32_t *)intr_0_stack - 4;
/**********************************************************/
/* ebp在thread_stack中的地址便是当时的esp(0级栈的栈顶),
即esp为"(uint32_t*)intr_0_stack - 5" */
uint32_t *ebp_ptr_in_thread_stack = (uint32_t *)intr_0_stack - 5;
/* switch_to的返回地址更新为intr_exit,直接从中断返回 */
*ret_addr_in_thread_stack = (uint32_t)intr_exit;
/* 下面这两行赋值只是为了使构建的thread_stack更加清晰,其实也不需要,
* 因为在进入intr_exit后一系列的pop会把寄存器中的数据覆盖 */
*ebp_ptr_in_thread_stack = *ebx_ptr_in_thread_stack =
*edi_ptr_in_thread_stack = *esi_ptr_in_thread_stack = 0;
/*********************************************************/
/* 把构建的thread_stack的栈顶做为switch_to恢复数据时的栈顶 */
child_thread->self_kstack = ebp_ptr_in_thread_stack;
return 0;
}
update_inode_open_cnts
Étant donné que le processus enfant issu du fork est presque le même que le processus parent, les fichiers ouverts par le processus parent doivent également être ouverts par le processus enfant. Par conséquent, le nombre de fois où le fichier est ouvert dans la structure globale de fichiers ouverts du processus parent doit être + 1. Principe : Parcourez les descripteurs de fichiers dans la carte de processus (parent ou enfant) et recherchez l'index global de structure de fichiers ouverts correspondant.
Modifier ( myos/user/fork.c )
#include <file.h>
/* 更新inode打开数 */
static void update_inode_open_cnts(struct task_struct *thread)
{
int32_t local_fd = 3, global_fd = 0;
while (local_fd < MAX_FILES_OPEN_PER_PROC)
{
global_fd = thread->fd_table[local_fd];
ASSERT(global_fd < MAX_FILE_OPEN);
if (global_fd != -1)
{
file_table[global_fd].fd_inode->i_open_cnts++;
}
local_fd++;
}
}
copy_process
C'est la fonction utilisée pour copier les ressources du processus parent lors du fork, et c'est l'encapsulation de la fonction précédente. Principe : Appel pour copy_pcb_vaddrbitmap_stack0
copier le PCB, le bitmap d'adresse virtuelle et la pile du noyau du processus parent vers le processus enfant ; puis appel pour create_page_dir
créer une table de pages pour le processus enfant. Cette table de pages contient déjà le mappage de l'espace d'adressage du noyau ; puis appel pour copy_body_stack3
copier le tas de l'espace utilisateur et les données de la pile sont ensuite appelées pour build_child_stack
modifier la valeur de retour du processus enfant et définir sa pile de noyau ; le dernier appel consiste à update_inode_open_cnts
mettre à jour le nombre d'inodes ouverts.
Modifier ( myos/user/fork.c )
/* 拷贝父进程本身所占资源给子进程 */
static int32_t copy_process(struct task_struct *child_thread, struct task_struct *parent_thread)
{
/* 内核缓冲区,作为父进程用户空间的数据复制到子进程用户空间的中转 */
void *buf_page = get_kernel_pages(1);
if (buf_page == NULL)
{
return -1;
}
/* a 复制父进程的pcb、虚拟地址位图、内核栈到子进程 */
if (copy_pcb_vaddrbitmap_stack0(child_thread, parent_thread) == -1)
{
return -1;
}
/* b 为子进程创建页表,此页表仅包括内核空间 */
child_thread->pgdir = create_page_dir();
if (child_thread->pgdir == NULL)
{
return -1;
}
/* c 复制父进程进程体及用户栈给子进程 */
copy_body_stack3(child_thread, parent_thread, buf_page);
/* d 构建子进程thread_stack和修改返回值pid */
build_child_stack(child_thread);
/* e 更新文件inode的打开数 */
update_inode_open_cnts(child_thread);
mfree_page(PF_KERNEL, buf_page, 1);
return 0;
}
sys_fork
Utilisé pour copier un processus et l'ajouter à la file d'attente prête
(myos/userprog/fork.c)
#include "interrupt.h"
/* fork子进程,内核线程不可直接调用 */
pid_t sys_fork(void)
{
struct task_struct *parent_thread = running_thread();
struct task_struct *child_thread = get_kernel_pages(1); // 为子进程创建pcb(task_struct结构)
if (child_thread == NULL)
{
return -1;
}
ASSERT(INTR_OFF == intr_get_status() && parent_thread->pgdir != NULL);
if (copy_process(child_thread, parent_thread) == -1)
{
return -1;
}
/* 添加到就绪线程队列和所有线程队列,子进程由调试器安排运行 */
ASSERT(!elem_find(&thread_ready_list, &child_thread->general_tag));
list_append(&thread_ready_list, &child_thread->general_tag);
ASSERT(!elem_find(&thread_all_list, &child_thread->all_list_tag));
list_append(&thread_all_list, &child_thread->all_list_tag);
return child_thread->pid; // 父进程返回子进程的pid
}
Déclaration de fonction ( myos/userprog/fork.h )
#ifndef __USERPROG_FORK_H
#define __USERPROG_FORK_H
#include "stdint.h"
pid_t sys_fork(void);
#endif
Ensuite, nous ajoutons fork
l'appel système
Ajouter le numéro d'appel système et modifier ( myos/lib/user/syscall.h )
#include "thread.h"
enum SYSCALL_NR {
SYS_GETPID,
SYS_WRITE,
SYS_MALLOC,
SYS_FREE,
SYS_FORK
};
Entrée d'appel système en mode utilisateur, modifiée ( myos/lib/user/syscall.c )
#include "thread.h"
/* 派生子进程,返回子进程pid */
pid_t fork(void)
{
return _syscall0(SYS_FORK);
}
Déclaration de fonction, modification ( myos/lib/user/syscall.h )
pid_t fork(void);
Ajoutez les fonctions d'appel système réelles à la table des appels système et modifiez ( myos/userprog/syscall-init.c )
#include "fork.h"
/* 初始化系统调用 */
void syscall_init(void) {
put_str("syscall_init start\n");
syscall_table[SYS_GETPID] = sys_getpid;
syscall_table[SYS_WRITE] = sys_write;
syscall_table[SYS_MALLOC] = sys_malloc;
syscall_table[SYS_FREE] = sys_free;
syscall_table[SYS_FORK] = sys_fork;
put_str("syscall_init done\n");
}
init
Processus : nous apprenons de l'approche Linux et laissons-le init
être un processus utilisateur avec un pid de 1, il doit donc être créé avant la création du thread principal. Tous les processus ultérieurs sont ses enfants et il est également responsable du recyclage des ressources de tous les processus enfants.
Modifier ( myos/thread/thread.c/thread_init ) pour que init
le pid soit 1
extern void init(void);
/* 初始化线程环境 */
void thread_init(void)
{
put_str("thread_init start\n");
list_init(&thread_ready_list);
list_init(&thread_all_list);
lock_init(&pid_lock);
/* 先创建第一个用户进程:init */
process_execute(init, "init"); // 放在第一个初始化,这是第一个进程,init进程的pid为1
/* 将当前main函数创建为线程 */
make_main_thread();
/* 创建idle线程 */
idle_thread = thread_start("idle", 10, idle, NULL);
put_str("thread_init done\n");
}
Code de test et init
mise en œuvre des processus
(myos/kernel/main.c)
#include "print.h"
#include "init.h"
#include "fork.h"
#include "stdio.h"
#include "syscall.h"
void init(void);
int main(void)
{
put_str("I am kernel\n");
init_all();
while (1)
;
return 0;
}
/* init进程 */
void init(void)
{
uint32_t ret_pid = fork();
if (ret_pid)
{
printf("i am father, my pid is %d, child pid is %d\n", getpid(), ret_pid);
}
else
{
printf("i am child, my pid is %d, ret pid is %d\n", getpid(), ret_pid);
}
while (1)
;
}
Une erreur de page sera signalée lors de la compilation et de l'exécution. Après dépannage, elle a été modifiée ( myos/thread/thread.c/thread_create )
/* 先预留中断使用栈的空间,可见thread.h中定义的结构 */
// pthread->self_kstack -= sizeof(struct intr_stack); //-=结果是sizeof(struct intr_stack)的4倍
// self_kstack类型为uint32_t*,也就是一个明确指向uint32_t类型值的地址,那么加减操作,都是会是sizeof(uint32_t) = 4 的倍数
pthread->self_kstack = (uint32_t *)((int)(pthread->self_kstack) - sizeof(struct intr_stack));
/* 再留出线程栈空间,可见thread.h中定义 */
// pthread->self_kstack -= sizeof(struct thread_stack);
pthread->self_kstack = (uint32_t *)((int)(pthread->self_kstack) - sizeof(struct thread_stack));
pour
/* 先预留中断使用栈的空间,可见thread.h中定义的结构 */
pthread->self_kstack -= sizeof(struct intr_stack); //-=结果是sizeof(struct intr_stack)的4倍
// self_kstack类型为uint32_t*,也就是一个明确指向uint32_t类型值的地址,那么加减操作,都是会是sizeof(uint32_t) = 4 的倍数
// pthread->self_kstack = (uint32_t *)((int)(pthread->self_kstack) - sizeof(struct intr_stack));
/* 再留出线程栈空间,可见thread.h中定义 */
pthread->self_kstack -= sizeof(struct thread_stack);
// pthread->self_kstack = (uint32_t *)((int)(pthread->self_kstack) - sizeof(struct thread_stack));
Les programmes typiques s'appuient sur des erreurs pour s'exécuter, et on ne sait pas encore pourquoi l'erreur se produit.
Section B :
Obtenir une saisie au clavier
sys_read
Utilisé pour obtenir des données d'octets de compte à partir du descripteur de fichier spécifié. Si le descripteur de fichier est stdin_no, faites une boucle directe pour ioq_getchar
obtenir le contenu du clavier, sinon appelez pour file_read
lire le contenu du fichier.
sys_put_char
Utilisé pour afficher un caractère à l'écran
Modifier ( myos/fs/fs.c/sys_read )
#include "keyboard.h"
#include "ioqueue.h"
/* 从文件描述符fd指向的文件中读取count个字节到buf,若成功则返回读出的字节数,到文件尾则返回-1 */
int32_t sys_read(int32_t fd, void *buf, uint32_t count)
{
ASSERT(buf != NULL);
int32_t ret = -1;
if (fd < 0 || fd == stdout_no || fd == stderr_no)
{
printk("sys_read: fd error\n");
}
else if (fd == stdin_no)
{
char *buffer = buf;
uint32_t bytes_read = 0;
while (bytes_read < count)
{
*buffer = ioq_getchar(&kbd_buf);
bytes_read++;
buffer++;
}
ret = (bytes_read == 0 ? -1 : (int32_t)bytes_read);
}
else
{
uint32_t _fd = fd_local2global(fd);
ret = file_read(&file_table[_fd], buf, count);
}
return ret;
}
/* 向屏幕输出一个字符 */
void sys_putchar(char char_asci)
{
console_put_char(char_asci);
}
Déclaration de fonction, modification ( myos/fs/fs.h )
void sys_putchar(char char_asci);
cls_screen
Utilisé pour effacer l'écran, le principe de base : écrire un caractère espace dans la mémoire représentant 80 colonnes × 25 lignes, un total de 2000 positions de caractères, puis définir la position du curseur dans le coin supérieur gauche (c'est-à-dire la position 0)
Modifier ( myos/lib/kernel/print.S )
global cls_screen
cls_screen:
pushad
; 由于用户程序的cpl为3,显存段的dpl为0,故用于显存段的选择子gs在低于自己特权的环境中为0,
; 导致用户程序再次进入中断后,gs为0,故直接在put_str中每次都为gs赋值.
mov ax, SELECTOR_VIDEO ; 不能直接把立即数送入gs,须由ax中转
mov gs, ax
mov ebx, 0
mov ecx, 80*25
.cls:
mov word [gs:ebx], 0x0720 ;0x0720是黑底白字的空格键
add ebx, 2
loop .cls
mov ebx, 0
.set_cursor: ;直接把set_cursor搬过来用,省事
;;;;;; 1 先设置高8位 ;;;;;;;;
mov dx, 0x03d4 ;索引寄存器
mov al, 0x0e ;用于提供光标位置的高8位
out dx, al
mov dx, 0x03d5 ;通过读写数据端口0x3d5来获得或设置光标位置
mov al, bh
out dx, al
;;;;;;; 2 再设置低8位 ;;;;;;;;;
mov dx, 0x03d4
mov al, 0x0f
out dx, al
mov dx, 0x03d5
mov al, bl
out dx, al
popad
ret
Déclaration de fonction, modification ( myos/lib/kernel/print.h )
void cls_screen(void);
Convertir sys_read
, sys_putchar
, cls_screen
en appels système
Ajouter le numéro d'appel système et modifier ( myos/lib/user/syscall.h )
enum SYSCALL_NR
{
SYS_GETPID,
SYS_WRITE,
SYS_MALLOC,
SYS_FREE,
SYS_FORK,
SYS_READ,
SYS_PUTCHAR,
SYS_CLEAR
};
Préparer read
et put_char
modifier clear
l'entrée du mode utilisateur ( myos/lib/user/syscall.c )
/* 从文件描述符fd中读取count个字节到buf */
int32_t read(int32_t fd, void *buf, uint32_t count)
{
return _syscall3(SYS_READ, fd, buf, count);
}
/* 输出一个字符 */
void putchar(char char_asci)
{
_syscall1(SYS_PUTCHAR, char_asci);
}
/* 清空屏幕 */
void clear(void)
{
_syscall0(SYS_CLEAR);
}
Déclarez ensuite la fonction et modifiez ( myos/lib/user/syscall.h )
int32_t read(int32_t fd, void* buf, uint32_t count);
void putchar(char char_asci);
void clear(void);
Ajoutez le programme d'exécution réel de l'appel système à la table des appels système et modifiez-le ( myos/lib/user/syscall.c )
/* 初始化系统调用 */
void syscall_init(void)
{
put_str("syscall_init start\n");
syscall_table[SYS_GETPID] = sys_getpid;
syscall_table[SYS_WRITE] = sys_write;
syscall_table[SYS_MALLOC] = sys_malloc;
syscall_table[SYS_FREE] = sys_free;
syscall_table[SYS_FORK] = sys_fork;
syscall_table[SYS_READ] = sys_read;
syscall_table[SYS_PUTCHAR] = sys_putchar;
syscall_table[SYS_CLEAR] = cls_screen;
put_str("syscall_init done\n");
}
Le shell est l'interface d'interaction entre l'utilisateur et le système d'exploitation. Le terminal Linux que nous utilisons quotidiennement est un shell. Sa fonction est d'obtenir la saisie au clavier de l'utilisateur, puis d'analyser la commande à partir de celle-ci, puis d'effectuer l'action correspondante en fonction de la commande.
print_prompt
Utilisé pour afficher l'invite de commande, c'est-à-dire que lorsque nous entrons la commande dans le terminal, la chaîne de caractères précédente
(myos/shell/shell.c)
#include "shell.h"
#include "stdio.h"
char cwd_cache[64] = {
0};
/* 输出提示符 */
void print_prompt(void)
{
printf("[rabbit@localhost %s]$ ", cwd_cache);
}
readline
L'appel en boucle read
lit les caractères du tampon d'entrée du clavier, un à la fois, et lit au maximum le nombre d'octets dans buf. En fonction de la valeur lue à chaque fois, la méthode de traitement est également différente : /n, /r signifie appuyer sur la touche Entrée et l'utilisateur saisissant la commande se termine, et entrer un 0 dans le tampon signifie la fin de la chaîne de commande. /b signifie qu'appuyer sur la touche retour arrière supprimera un caractère. Les caractères ordinaires sont lus directement dans buf. Chaque caractère est appelé putchar
pour l'impression car notre gestionnaire d'interruption du clavier a supprimé la fonction d'impression.
Modifier ( myos/shell/shell.c )
#include "file.h"
#include "debug.h"
#include "syscall.h"
/* 从键盘缓冲区中最多读入count个字节到buf。*/
static void readline(char *buf, int32_t count)
{
ASSERT(buf != NULL && count > 0);
char *pos = buf;
while (read(stdin_no, pos, 1) != -1 && (pos - buf) < count)
{
// 在不出错情况下,直到找到回车符才返回
switch (*pos)
{
/* 找到回车或换行符后认为键入的命令结束,直接返回 */
case '\n':
case '\r':
*pos = 0; // 添加cmd_line的终止字符0
putchar('\n');
return;
case '\b':
if (buf[0] != '\b')
{
// 阻止删除非本次输入的信息
--pos; // 退回到缓冲区cmd_line中上一个字符
putchar('\b');
}
break;
/* 非控制键则输出字符 */
default:
putchar(*pos);
pos++;
}
}
printf("readline: can`t find enter_key in the cmd_line, max num of char is 128\n");
}
my_shell
C'est le processus shell qui continue de boucler : appeler print_prompt
l'invite de commande de sortie, puis appeler pour readline
obtenir l'entrée de l'utilisateur.
Modifier ( myos/shell/shell.c )
#include "string.h"
#define cmd_len 128 // 最大支持键入128个字符的命令行输入
static char cmd_line[cmd_len] = {
0};
/* 简单的shell */
void my_shell(void)
{
cwd_cache[0] = '/';
while (1)
{
print_prompt();
memset(cmd_line, 0, cmd_len);
readline(cmd_line, cmd_len);
if (cmd_line[0] == 0)
{
// 若只键入了一个回车
continue;
}
}
PANIC("my_shell: should not be here");
}
Déclaration de fonction, ( myos/shell/shell.h )
#ifndef __KERNEL_SHELL_H
#define __KERNEL_SHELL_H
void print_prompt(void);
void my_shell(void);
#endif
Ouvrons et modifions ( myos/kernel/init
main.c )shell
#include "print.h"
#include "init.h"
#include "fork.h"
#include "stdio.h"
#include "syscall.h"
#include "debug.h"
#include "shell.h"
#include "console.h"
void init(void);
int main(void) {
put_str("I am kernel\n");
init_all();
cls_screen();
console_put_str("[rabbit@localhost /]$ ");
while(1);
return 0;
}
/* init进程 */
void init(void)
{
uint32_t ret_pid = fork();
if (ret_pid)
{
// 父进程
while (1)
;
}
else
{
// 子进程
my_shell();
}
PANIC("init: should not be here");
}
Supprimez l'instruction d'impression dans ( myos/device/keyboard.c/intr_keyboard_handler )
put_char(cur_char); // 临时的
N'oubliez pas d'ajouter une bibliothèque statique au makefile
LIB= -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/ -I thread/ -I userprog/ -I fs/ -I shell/
Question ici :
1. Il existe une instruction pour imprimer l'invite de commande dans la fonction principale, et l' appel init_all
init crée le processus init. Lorsque init s'exécute, il générera un processus qui appelle uniquement le shell. Ce processus appellera l'invite de commande print, ce qui revient à imprimer dans main. Pour obtenir l'effet décrit dans le livre, l'appel au processus qui exécute le shell doit être forké avant l'appel . Cela dépend de l’ordre spécifique d’exécution des tâches, mais ne se passe généralement pas mal.thread_init
process_execute
print_prompt
print_prompt
main
cls_screen
Section c :
Ajouter des touches de raccourci
readline
Traitement nouvellement ajouté pour les combinaisons de touches, ctrl + l efface les autres lignes à l'exception de la ligne actuelle. ctrl + u efface l'entrée de cette ligne. L'effet est similaire à l'appui successif de plusieurs retours arrière. Nous avons pré-écrit le traitement consistant à appuyer sur ctrl + l et ctrl + u dans le gestionnaire d'interruption du clavier
if ((ctrl_status && cur_char == 'l') || (ctrl_status && cur_char == 'u')) {
cur_char -= 'a';
}
if (!ioq_full(&kbd_buf)) {
ioq_putchar(&kbd_buf, cur_char);
}
C'est-à-dire que lorsque l'on appuie sur ctrl + l et ctrl + u, les caractères mis dans le tampon de saisie du clavier sont les codes ascii 'l' - 'a' et 'u' - 'a'. Ces deux codes ascii sont invisibles. caractères de contrôle. Il suffit donc d’ajouter readline
la logique de traitement pour lire ces deux situations.
Modifier ( myos/shell/shell.c )
/* 从键盘缓冲区中最多读入count个字节到buf。*/
static void readline(char *buf, int32_t count)
{
ASSERT(buf != NULL && count > 0);
char *pos = buf;
while (read(stdin_no, pos, 1) != -1 && (pos - buf) < count)
{
// 在不出错情况下,直到找到回车符才返回
switch (*pos)
{
/* 找到回车或换行符后认为键入的命令结束,直接返回 */
case '\n':
case '\r':
*pos = 0; // 添加cmd_line的终止字符0
putchar('\n');
return;
case '\b':
if (cmd_line[0] != '\b')
{
// 阻止删除非本次输入的信息
--pos; // 退回到缓冲区cmd_line中上一个字符
putchar('\b');
}
break;
/* ctrl+l 清屏 */
case 'l' - 'a':
/* 1 先将当前的字符'l'-'a'置为0 */
*pos = 0;
/* 2 再将屏幕清空 */
clear();
/* 3 打印提示符 */
print_prompt();
/* 4 将之前键入的内容再次打印 */
printf("%s", buf);
break;
/* ctrl+u 清掉输入 */
case 'u' - 'a':
while (buf != pos)
{
putchar('\b');
*(pos--) = 0;
}
break;
/* 非控制键则输出字符 */
default:
putchar(*pos);
pos++;
}
}
printf("readline: can`t find enter_key in the cmd_line, max num of char is 128\n");
}
Section d :
Analyser les caractères saisis
cmd_parse
Analysez les mots avec token comme délimiteur dans la chaîne cmd_str et stockez les pointeurs de chaque mot dans le tableau argv. Cette fonction est une fonction de traitement de chaîne qui divise les mots des commandes telles que 'ls dir' en 'ls' et 'dir'.
Modifier ( myos/shell/shell.c )
#define MAX_ARG_NR 16 // 加上命令名外,最多支持15个参数
/* 分析字符串cmd_str中以token为分隔符的单词,将各单词的指针存入argv数组 */
static int32_t cmd_parse(char *cmd_str, char **argv, char token)
{
ASSERT(cmd_str != NULL);
int32_t arg_idx = 0;
while (arg_idx < MAX_ARG_NR)
{
argv[arg_idx] = NULL;
arg_idx++;
}
char *next = cmd_str;
int32_t argc = 0;
/* 外层循环处理整个命令行 */
while (*next)
{
/* 去除命令字或参数之间的空格 */
while (*next == token)
{
next++;
}
/* 处理最后一个参数后接空格的情况,如"ls dir2 " */
if (*next == 0)
{
break;
}
argv[argc] = next;
/* 内层循环处理命令行中的每个命令字及参数 */
while (*next && *next != token)
{ // 在字符串结束前找单词分隔符
next++;
}
/* 如果未结束(是token字符),使tocken变成0 */
if (*next)
{
*next++ = 0; // 将token字符替换为字符串结束符0,做为一个单词的结束,并将字符指针next指向下一个字符
}
/* 避免argv数组访问越界,参数过多则返回0 */
if (argc > MAX_ARG_NR)
{
return -1;
}
argc++;
}
return argc;
}
my_shell
Ajouter un code de test pour afficher chaque mot séparé
Modifier ( myos/shell/shell.c )
char *argv[MAX_ARG_NR]; // argv必须为全局变量,为了以后exec的程序可访问参数
char final_path[MAX_PATH_LEN] = {
0}; // 用于洗路径时的缓冲
int32_t argc = -1;
void my_shell(void)
{
cwd_cache[0] = '/';
while (1)
{
print_prompt();
memset(final_path, 0, MAX_PATH_LEN);
memset(cmd_line, 0, MAX_PATH_LEN);
readline(cmd_line, MAX_PATH_LEN);
if (cmd_line[0] == 0)
{
// 若只键入了一个回车
continue;
}
argc = -1;
argc = cmd_parse(cmd_line, argv, ' ');
if (argc == -1)
{
printf("num of arguments exceed %d\n", MAX_ARG_NR);
continue;
}
int32_t arg_idx = 0;
while (arg_idx < argc)
{
printf("%s ", argv[arg_idx]);
arg_idx++;
}
printf("\n");
}
PANIC("my_shell: should not be here");
}
Section e :
Implémentez la commande d'entrée puis appelez la fonction correspondante
Implémentez d’abord un appel système ps
pad_print
Il est utilisé pour une sortie alignée, c'est-à-dire qu'il y a une zone buf d'une longueur de 10 octets, puis peu importe ce que nous voulons afficher, nous écrivons dans ce buf, puis remplissons toutes les parties vides avec des espaces, et enfin nous produisons le buf entier. Par exemple, si « bonjour » est affiché, il devient « bonjour » après le traitement.
elem2thread_info
Appelé pad_print
pour aligner et afficher le pid, le ppid, l'état, les elapsed_ticks, le nom de chaque PCB
sys_ps
L'appel list_traversal
parcourt toutes les files d'attente de tâches et rappelle elem2thread_info
pour afficher les informations dans le processus ou le thread PCB.
Modifier ( myos/thread/thread.c )
#include "stdio.h"
#include "fs.h"
#include "file.h"
/* 以填充空格的方式输出buf */
static void pad_print(char *buf, int32_t buf_len, void *ptr, char format)
{
memset(buf, 0, buf_len);
uint8_t out_pad_0idx = 0;
switch (format)
{
case 's':
out_pad_0idx = sprintf(buf, "%s", ptr);
break;
case 'd':
out_pad_0idx = sprintf(buf, "%d", *((int16_t *)ptr));
case 'x':
out_pad_0idx = sprintf(buf, "%x", *((uint32_t *)ptr));
}
while (out_pad_0idx < buf_len)
{
// 以空格填充
buf[out_pad_0idx] = ' ';
out_pad_0idx++;
}
sys_write(stdout_no, buf, buf_len - 1);
}
/* 用于在list_traversal函数中的回调函数,用于针对线程队列的处理 */
static bool elem2thread_info(struct list_elem *pelem, int arg UNUSED)
{
struct task_struct *pthread = elem2entry(struct task_struct, all_list_tag, pelem);
char out_pad[16] = {
0};
pad_print(out_pad, 16, &pthread->pid, 'd');
if (pthread->parent_pid == -1)
{
pad_print(out_pad, 16, "NULL", 's');
}
else
{
pad_print(out_pad, 16, &pthread->parent_pid, 'd');
}
switch (pthread->status)
{
case 0:
pad_print(out_pad, 16, "RUNNING", 's');
break;
case 1:
pad_print(out_pad, 16, "READY", 's');
break;
case 2:
pad_print(out_pad, 16, "BLOCKED", 's');
break;
case 3:
pad_print(out_pad, 16, "WAITING", 's');
break;
case 4:
pad_print(out_pad, 16, "HANGING", 's');
break;
case 5:
pad_print(out_pad, 16, "DIED", 's');
}
pad_print(out_pad, 16, &pthread->elapsed_ticks, 'x');
memset(out_pad, 0, 16);
ASSERT(strlen(pthread->name) < 17);
memcpy(out_pad, pthread->name, strlen(pthread->name));
strcat(out_pad, "\n");
sys_write(stdout_no, out_pad, strlen(out_pad));
return false; // 此处返回false是为了迎合主调函数list_traversal,只有回调函数返回false时才会继续调用此函数
}
/* 打印任务列表 */
void sys_ps(void)
{
char *ps_title = "PID PPID STAT TICKS COMMAND\n";
sys_write(stdout_no, ps_title, strlen(ps_title));
list_traversal(&thread_all_list, elem2thread_info, 0);
}
Ajouter une déclaration de fonction et modifier ( myos/thread/thread.h )
void sys_ps(void);
Encapsulez ensuite toutes les fonctions commençant par sys implémentées dans le chapitre précédent et ce chapitre dans les appels système.
Ajoutez d'abord le numéro d'appel système et modifiez ( myos/lib/user/syscall.h )
#include "fs.h"
enum SYSCALL_NR
{
SYS_GETPID,
SYS_WRITE,
SYS_MALLOC,
SYS_FREE,
SYS_FORK,
SYS_READ,
SYS_PUTCHAR,
SYS_CLEAR,
SYS_GETCWD,
SYS_OPEN,
SYS_CLOSE,
SYS_LSEEK,
SYS_UNLINK,
SYS_MKDIR,
SYS_OPENDIR,
SYS_CLOSEDIR,
SYS_CHDIR,
SYS_RMDIR,
SYS_READDIR,
SYS_REWINDDIR,
SYS_STAT,
SYS_PS
};
Ensuite, implémentez leurs entrées en mode utilisateur et modifiez ( myos/lib/user/syscall.c )
/* 获取当前工作目录 */
char *getcwd(char *buf, uint32_t size)
{
return (char *)_syscall2(SYS_GETCWD, buf, size);
}
/* 以flag方式打开文件pathname */
int32_t open(char *pathname, uint8_t flag)
{
return _syscall2(SYS_OPEN, pathname, flag);
}
/* 关闭文件fd */
int32_t close(int32_t fd)
{
return _syscall1(SYS_CLOSE, fd);
}
/* 设置文件偏移量 */
int32_t lseek(int32_t fd, int32_t offset, uint8_t whence)
{
return _syscall3(SYS_LSEEK, fd, offset, whence);
}
/* 删除文件pathname */
int32_t unlink(const char *pathname)
{
return _syscall1(SYS_UNLINK, pathname);
}
/* 创建目录pathname */
int32_t mkdir(const char *pathname)
{
return _syscall1(SYS_MKDIR, pathname);
}
/* 打开目录name */
struct dir *opendir(const char *name)
{
return (struct dir *)_syscall1(SYS_OPENDIR, name);
}
/* 关闭目录dir */
int32_t closedir(struct dir *dir)
{
return _syscall1(SYS_CLOSEDIR, dir);
}
/* 删除目录pathname */
int32_t rmdir(const char *pathname)
{
return _syscall1(SYS_RMDIR, pathname);
}
/* 读取目录dir */
struct dir_entry *readdir(struct dir *dir)
{
return (struct dir_entry *)_syscall1(SYS_READDIR, dir);
}
/* 回归目录指针 */
void rewinddir(struct dir *dir)
{
_syscall1(SYS_REWINDDIR, dir);
}
/* 获取path属性到buf中 */
int32_t stat(const char *path, struct stat *buf)
{
return _syscall2(SYS_STAT, path, buf);
}
/* 改变工作目录为path */
int32_t chdir(const char *path)
{
return _syscall1(SYS_CHDIR, path);
}
/* 显示任务列表 */
void ps(void)
{
_syscall0(SYS_PS);
}
Ajoutez la déclaration de la fonction d'entrée du mode utilisateur de l'appel système et modifiez-la ( myos/lib/user/syscall.h )
char *getcwd(char *buf, uint32_t size);
int32_t open(char *pathname, uint8_t flag);
int32_t close(int32_t fd);
int32_t lseek(int32_t fd, int32_t offset, uint8_t whence);
int32_t unlink(const char *pathname);
int32_t mkdir(const char *pathname);
struct dir *opendir(const char *name);
int32_t closedir(struct dir *dir);
int32_t rmdir(const char *pathname);
struct dir_entry *readdir(struct dir *dir);
void rewinddir(struct dir *dir);
int32_t stat(const char *path, struct stat *buf);
int32_t chdir(const char *path);
void ps(void);
Enfin, ajoutez la véritable fonction d'exécution des appels système à la table des appels système et modifiez ( myos/userprog/syscall-init.c )
/* 初始化系统调用 */
void syscall_init(void)
{
put_str("syscall_init start\n");
syscall_table[SYS_GETPID] = sys_getpid;
syscall_table[SYS_WRITE] = sys_write;
syscall_table[SYS_MALLOC] = sys_malloc;
syscall_table[SYS_FREE] = sys_free;
syscall_table[SYS_FORK] = sys_fork;
syscall_table[SYS_READ] = sys_read;
syscall_table[SYS_PUTCHAR] = sys_putchar;
syscall_table[SYS_CLEAR] = cls_screen;
syscall_table[SYS_GETCWD] = sys_getcwd;
syscall_table[SYS_OPEN] = sys_open;
syscall_table[SYS_CLOSE] = sys_close;
syscall_table[SYS_LSEEK] = sys_lseek;
syscall_table[SYS_UNLINK] = sys_unlink;
syscall_table[SYS_MKDIR] = sys_mkdir;
syscall_table[SYS_OPENDIR] = sys_opendir;
syscall_table[SYS_CLOSEDIR] = sys_closedir;
syscall_table[SYS_CHDIR] = sys_chdir;
syscall_table[SYS_RMDIR] = sys_rmdir;
syscall_table[SYS_READDIR] = sys_readdir;
syscall_table[SYS_REWINDDIR] = sys_rewinddir;
syscall_table[SYS_STAT] = sys_stat;
syscall_table[SYS_PS] = sys_ps;
put_str("syscall_init done\n");
}
Pour la commodité des utilisateurs, les systèmes d’exploitation fournissent généralement des fonctions de chemin relatif. Par exemple, notre chemin de travail actuel est /home/kanshan/Desktop. Si nous voulons exécuter un programme compilé et saisir ./test, il est en fait analysé par le système d'exploitation dans /home/kanshan/Desktop/test, qui est le chemin de travail actuel + chemin relatif = chemin absolu.
wash_path
Convertissez ... et .dans le chemin old_abs_path (qui est le chemin absolu fourni par l'appelant) en chemins réels et stockez-les dans new_abs_path. Par exemple, le chemin donné /a/b/..
doit être converti en /a
. Le chemin donné /a/b/.
doit être converti en /a/b
. Principe de base : appeler path_parse
le chemin analysé, et si c'est le cas ..
, revenir au chemin précédent. Si c'est le cas .
, ne faites rien. Apportez un exemple, par exemple, /a/../home/.
vous pouvez comprendre comment fonctionne la sous-fonction
make_clear_abs_path
Traitez le chemin (y compris le chemin relatif et le chemin absolu) en un chemin absolu sans... et ., et stockez-le dans final_path. Principe de base : Déterminer si le chemin d'entrée est un chemin relatif ou un chemin absolu. S'il s'agit d'un chemin relatif, appelez getcwd pour obtenir le chemin absolu du répertoire de travail actuel, ajoutez le chemin saisi par l'utilisateur au chemin du répertoire de travail pour formez un chemin de répertoire absolu et transmettez-le en tant que paramètre wash_path
Effectuez la conversion du chemin.
#include "buildin_cmd.h"
#include "debug.h"
#include "dir.h"
#include "string.h"
#include "fs.h"
#include "syscall.h"
/* 将路径old_abs_path中的..和.转换为实际路径后存入new_abs_path */
static void wash_path(char *old_abs_path, char *new_abs_path)
{
ASSERT(old_abs_path[0] == '/');
char name[MAX_FILE_NAME_LEN] = {
0};
char *sub_path = old_abs_path;
sub_path = path_parse(sub_path, name);
if (name[0] == 0)
{
// 若只键入了"/",直接将"/"存入new_abs_path后返回
new_abs_path[0] = '/';
new_abs_path[1] = 0;
return;
}
new_abs_path[0] = 0; // 避免传给new_abs_path的缓冲区不干净
strcat(new_abs_path, "/");
while (name[0])
{
/* 如果是上一级目录“..” */
if (!strcmp("..", name))
{
char *slash_ptr = strrchr(new_abs_path, '/');
/*如果未到new_abs_path中的顶层目录,就将最右边的'/'替换为0,
这样便去除了new_abs_path中最后一层路径,相当于到了上一级目录 */
if (slash_ptr != new_abs_path)
{
// 如new_abs_path为“/a/b”,".."之后则变为“/a”
*slash_ptr = 0;
}
else
{
// 如new_abs_path为"/a",".."之后则变为"/"
/* 若new_abs_path中只有1个'/',即表示已经到了顶层目录,
就将下一个字符置为结束符0. */
*(slash_ptr + 1) = 0;
}
}
else if (strcmp(".", name))
{
// 如果路径不是‘.’,就将name拼接到new_abs_path
if (strcmp(new_abs_path, "/"))
{
// 如果new_abs_path不是"/",就拼接一个"/",此处的判断是为了避免路径开头变成这样"//"
strcat(new_abs_path, "/");
}
strcat(new_abs_path, name);
} // 若name为当前目录".",无须处理new_abs_path
/* 继续遍历下一层路径 */
memset(name, 0, MAX_FILE_NAME_LEN);
if (sub_path)
{
sub_path = path_parse(sub_path, name);
}
}
}
/* 将path处理成不含..和.的绝对路径,存储在final_path */
void make_clear_abs_path(char *path, char *final_path)
{
char abs_path[MAX_PATH_LEN] = {
0};
/* 先判断是否输入的是绝对路径 */
if (path[0] != '/')
{
// 若输入的不是绝对路径,就拼接成绝对路径
memset(abs_path, 0, MAX_PATH_LEN);
if (getcwd(abs_path, MAX_PATH_LEN) != NULL)
{
if (!((abs_path[0] == '/') && (abs_path[1] == 0)))
{
// 若abs_path表示的当前目录不是根目录/
strcat(abs_path, "/");
}
}
}
strcat(abs_path, path);
wash_path(abs_path, final_path);
}
Question 1 : Dans le code : new_abs_path[0] = 0 ; quelle est la signification ?
Cela garantit que new_abs_path
toute concaténation de chaînes ultérieure (par exemple via strcat
une fonction) commencera depuis le début.
Déclaration de fonction, ( myos/shell/buildin_cmd.h )
#ifndef __SHELL_BUILDIN_CMD_H
#define __SHELL_BUILDIN_CMD_H
void make_clear_abs_path(char *path, char *final_path);
#endif
Code de support, modification ( myos/fs/fs.c )
static char *path_parse(char *pathname, char *name_store)
pour
char *path_parse(char *pathname, char *name_store)
my_shell
Ajouter du code de test et modifier ( myos/shell/shell.c )
#include "buildin_cmd.h"
void my_shell(void)
{
cwd_cache[0] = '/';
cwd_cache[1] = 0;
while (1)
{
print_prompt();
memset(final_path, 0, MAX_PATH_LEN);
memset(cmd_line, 0, MAX_PATH_LEN);
readline(cmd_line, MAX_PATH_LEN);
if (cmd_line[0] == 0)
{
// 若只键入了一个回车
continue;
}
argc = -1;
argc = cmd_parse(cmd_line, argv, ' ');
if (argc == -1)
{
printf("num of arguments exceed %d\n", MAX_ARG_NR);
continue;
}
char buf[MAX_PATH_LEN] = {
0};
int32_t arg_idx = 0;
while (arg_idx < argc)
{
make_clear_abs_path(argv[arg_idx], buf);
printf("%s -> %s\n", argv[arg_idx], buf);
arg_idx++;
}
}
PANIC("my_shell: should not be here");
}
Section f :
Implémenter une série de commandes intégrées
Les commandes Shell sont divisées en commandes externes et commandes internes. L'exécution de commandes externes exécute en fait un processus. La commande interne consiste à exécuter la fonction fournie avec le système d'exploitation. Nous implémentons maintenant une série de fonctions intégrées requises par les commandes internes.
Chaque fonction intégrée est transmise dans deux paramètres :
uint32_t argc
: Ce paramètre indique le nombre de paramètres passés à la fonction. Dans commandls -l
,ls
est la commande et-l
estls
le paramètre. Dans cet exemple,argc
c'est 2 car il y a deux paramètres :ls
et-l
.char** argv
: Il s'agit d'un pointeur vers un tableau de chaînes représentant les valeurs des paramètres transmises.argv
Chaque élément de est une chaîne représentant un paramètre sur la ligne de commande.
buildin_pwd
Appelle-le simplementgetcwd
Modifier ( myos/shell/buildin_cmd.c )
#include "shell.h"
#include "stdio.h"
/* pwd命令的内建函数 */
void buildin_pwd(uint32_t argc, char **argv UNUSED)
{
if (argc != 1)
{
printf("pwd: no argument support!\n");
return;
}
else
{
if (NULL != getcwd(final_path, MAX_PATH_LEN))
{
printf("%s\n", final_path);
}
else
{
printf("pwd: get current work directory failed.\n");
}
}
}
Code de support, modification ( myos/shell/shell.h )
#include "fs.h"
extern char final_path[MAX_PATH_LEN];
buildin_cd
Il est appelé pour make_clear_abs_path
l'analyser argv[1]
en un chemin absolu, puis appelé chdir
pour changer de répertoire.
Modifier ( myos/shell/buildin_cmd.c )
/* cd命令的内建函数 */
char *buildin_cd(uint32_t argc, char **argv)
{
if (argc > 2)
{
printf("cd: only support 1 argument!\n");
return NULL;
}
/* 若是只键入cd而无参数,直接返回到根目录. */
if (argc == 1)
{
final_path[0] = '/';
final_path[1] = 0;
}
else
{
make_clear_abs_path(argv[1], final_path);
}
if (chdir(final_path) == -1)
{
printf("cd: no such directory %s\n", final_path);
return NULL;
}
return final_path;
}
buildin_ls
: utilisé pour lister des fichiers ou des répertoires
Principe de fonctionnement de base :
- Analyse des paramètres de ligne de commande :
utilisezwhile
une boucle pour parcourir tous les paramètres de ligne de commandeargv
et effectuez le traitement suivant :- Si un argument
-
commence par , il est alors traité comme une option. Actuellement, deux options sont prises en charge :-l
et-h
. L'-l
option entraîne la sortie des informations au format long, tandis que-h
l'option imprime les informations d'aide. - Si l’argument n’est pas une option, il est traité comme un argument de chemin. La fonction ne prend en charge qu'un seul paramètre de chemin.
- Si un argument
- Définir le chemin par défaut :
Si l'utilisateur ne fournit pas de paramètre de chemin, la fonction utilisera le répertoire de travail actuel comme chemin par défaut. - Obtenir l'état du fichier ou du répertoire :
utilisezstat
la fonction pour vérifier l'état du fichier ou du répertoire au chemin spécifié. Si le chemin n'existe pas, la fonction imprimera un message d'erreur et reviendra. - Traitement du répertoire :
Si le chemin spécifié est un répertoire :- Ouvrez ce répertoire.
- Si l'option est utilisée
-l
, chaque entrée de répertoire dans le répertoire est affichée au format long. Cela inclut le type de fichier (répertoire ou fichier normal), le numéro d'inode, la taille du fichier et le nom du fichier. - Si aucune
-l
option n'est utilisée, seul le nom du fichier est affiché. - Enfin, fermez le répertoire.
- Gestion des fichiers :
Si le chemin spécifié est un fichier :- Si l'option est utilisée
-l
, les informations du fichier sont sorties au format long. - Si aucune
-l
option n'est utilisée, seul le nom du fichier est affiché.
- Si l'option est utilisée
Modifier ( myos/shell/buildin_cmd.c )
/* ls命令的内建函数 */
void buildin_ls(uint32_t argc, char **argv)
{
char *pathname = NULL;
struct stat file_stat;
memset(&file_stat, 0, sizeof(struct stat));
bool long_info = false;
uint32_t arg_path_nr = 0;
uint32_t arg_idx = 1; // 跨过argv[0],argv[0]是字符串“ls”
while (arg_idx < argc)
{
if (argv[arg_idx][0] == '-')
{
// 如果是选项,单词的首字符是-
if (!strcmp("-l", argv[arg_idx]))
{
// 如果是参数-l
long_info = true;
}
else if (!strcmp("-h", argv[arg_idx]))
{
// 参数-h
printf("usage: -l list all infomation about the file.\n-h for help\nlist all files in the current dirctory if no option\n");
return;
}
else
{
// 只支持-h -l两个选项
printf("ls: invalid option %s\nTry `ls -h' for more information.\n", argv[arg_idx]);
return;
}
}
else
{
// ls的路径参数
if (arg_path_nr == 0)
{
pathname = argv[arg_idx];
arg_path_nr = 1;
}
else
{
printf("ls: only support one path\n");
return;
}
}
arg_idx++;
}
if (pathname == NULL)
{
// 若只输入了ls 或 ls -l,没有输入操作路径,默认以当前路径的绝对路径为参数.
if (NULL != getcwd(final_path, MAX_PATH_LEN))
{
pathname = final_path;
}
else
{
printf("ls: getcwd for default path failed\n");
return;
}
}
else
{
make_clear_abs_path(pathname, final_path);
pathname = final_path;
}
if (stat(pathname, &file_stat) == -1)
{
printf("ls: cannot access %s: No such file or directory\n", pathname);
return;
}
if (file_stat.st_filetype == FT_DIRECTORY)
{
struct dir *dir = opendir(pathname);
struct dir_entry *dir_e = NULL;
char sub_pathname[MAX_PATH_LEN] = {
0};
uint32_t pathname_len = strlen(pathname);
uint32_t last_char_idx = pathname_len - 1;
memcpy(sub_pathname, pathname, pathname_len);
if (sub_pathname[last_char_idx] != '/')
{
sub_pathname[pathname_len] = '/';
pathname_len++;
}
rewinddir(dir);
if (long_info)
{
char ftype;
printf("total: %d\n", file_stat.st_size);
while ((dir_e = readdir(dir)))
{
ftype = 'd';
if (dir_e->f_type == FT_REGULAR)
{
ftype = '-';
}
sub_pathname[pathname_len] = 0;
strcat(sub_pathname, dir_e->filename);
memset(&file_stat, 0, sizeof(struct stat));
if (stat(sub_pathname, &file_stat) == -1)
{
printf("ls: cannot access %s: No such file or directory\n", dir_e->filename);
return;
}
printf("%c %d %d %s\n", ftype, dir_e->i_no, file_stat.st_size, dir_e->filename);
}
}
else
{
while ((dir_e = readdir(dir)))
{
printf("%s ", dir_e->filename);
}
printf("\n");
}
closedir(dir);
}
else
{
if (long_info)
{
printf("- %d %d %s\n", file_stat.st_ino, file_stat.st_size, pathname);
}
else
{
printf("%s\n", pathname);
}
}
}
buildin_ps
Il suffit d'appelerps
Modifier ( myos/shell/buildin_cmd.c )
/* ps命令内建函数 */
void buildin_ps(uint32_t argc, char **argv UNUSED)
{
if (argc != 1)
{
printf("ps: no argument support!\n");
return;
}
ps();
}
buildin_clear
Il suffit d'appelerclear
Modifier ( myos/shell/buildin_cmd.c )
/* clear命令内建函数 */
void buildin_clear(uint32_t argc, char **argv UNUSED)
{
if (argc != 1)
{
printf("clear: no argument support!\n");
return;
}
clear();
}
buildin_mkdir
Autrement dit, make_clear_abs_path
l'appel est analysé argv[1]
en un chemin absolu, puis appelémkdir
Modifier ( myos/shell/buildin_cmd.c )
/* mkdir命令内建函数 */
int32_t buildin_mkdir(uint32_t argc, char **argv)
{
int32_t ret = -1;
if (argc != 2)
{
printf("mkdir: only support 1 argument!\n");
}
else
{
make_clear_abs_path(argv[1], final_path);
/* 若创建的不是根目录 */
if (strcmp("/", final_path))
{
if (mkdir(final_path) == 0)
{
ret = 0;
}
else
{
printf("mkdir: create directory %s failed.\n", argv[1]);
}
}
}
return ret;
}
buildin_rmdir
Autrement dit, make_clear_abs_path
l'appel est analysé argv[1]
en un chemin absolu, puis appelérmdir
Modifier ( myos/shell/buildin_cmd.c )
/* rmdir命令内建函数 */
int32_t buildin_rmdir(uint32_t argc, char **argv)
{
int32_t ret = -1;
if (argc != 2)
{
printf("rmdir: only support 1 argument!\n");
}
else
{
make_clear_abs_path(argv[1], final_path);
/* 若删除的不是根目录 */
if (strcmp("/", final_path))
{
if (rmdir(final_path) == 0)
{
ret = 0;
}
else
{
printf("rmdir: remove %s failed.\n", argv[1]);
}
}
}
return ret;
}
buildin_rm
Autrement dit, make_clear_abs_path
l'appel est analysé argv[1]
en un chemin absolu, puis appeléunlink
Modifier ( myos/shell/buildin_cmd.c )
/* rm命令内建函数 */
int32_t buildin_rm(uint32_t argc, char **argv)
{
int32_t ret = -1;
if (argc != 2)
{
printf("rm: only support 1 argument!\n");
}
else
{
make_clear_abs_path(argv[1], final_path);
/* 若删除的不是根目录 */
if (strcmp("/", final_path))
{
if (unlink(final_path) == 0)
{
ret = 0;
}
else
{
printf("rm: delete %s failed.\n", argv[1]);
}
}
}
return ret;
}
Déclaration de fonction, modification ( myos/shell/buildin_cmd.h )
#include "global.h"
void buildin_pwd(uint32_t argc, char **argv UNUSED);
char *buildin_cd(uint32_t argc, char **argv);
void buildin_ls(uint32_t argc, char **argv);
void buildin_ps(uint32_t argc, char **argv UNUSED);
void buildin_clear(uint32_t argc, char **argv UNUSED);
int32_t buildin_mkdir(uint32_t argc, char **argv);
int32_t buildin_rmdir(uint32_t argc, char **argv);
int32_t buildin_rm(uint32_t argc, char **argv);
my_shell
Il est ajouté en jugeant ce qu'est arg[0] (c'est le nom de la commande à appeler), puis en appelant la fonction intégrée en conséquence.
Modifier ( myos/shell/shell.c )
void my_shell(void)
{
cwd_cache[0] = '/';
while (1)
{
print_prompt();
memset(final_path, 0, MAX_PATH_LEN);
memset(cmd_line, 0, MAX_PATH_LEN);
readline(cmd_line, MAX_PATH_LEN);
if (cmd_line[0] == 0)
{
// 若只键入了一个回车
continue;
}
argc = -1;
argc = cmd_parse(cmd_line, argv, ' ');
if (argc == -1)
{
printf("num of arguments exceed %d\n", MAX_ARG_NR);
continue;
}
if (!strcmp("ls", argv[0]))
{
buildin_ls(argc, argv);
}
else if (!strcmp("cd", argv[0]))
{
if (buildin_cd(argc, argv) != NULL)
{
memset(cwd_cache, 0, MAX_PATH_LEN);
strcpy(cwd_cache, final_path);
}
}
else if (!strcmp("pwd", argv[0]))
{
buildin_pwd(argc, argv);
}
else if (!strcmp("ps", argv[0]))
{
buildin_ps(argc, argv);
}
else if (!strcmp("clear", argv[0]))
{
buildin_clear(argc, argv);
}
else if (!strcmp("mkdir", argv[0]))
{
buildin_mkdir(argc, argv);
}
else if (!strcmp("rmdir", argv[0]))
{
buildin_rmdir(argc, argv);
}
else if (!strcmp("rm", argv[0]))
{
buildin_rm(argc, argv);
}
else
{
printf("external command\n");
}
}
PANIC("my_shell: should not be here");
}
Section g :
Charger le processus utilisateur
segment_load
Chargez le segment avec les fichiers de décalage et de taillez dans le fichier pointé par le descripteur de fichier fd dans la mémoire avec l'adresse virtuelle vaddr. Principe de base : Après avoir compilé le programme, le compilateur a déjà spécifié l'adresse virtuelle du segment chargeable. Nous pouvons directement charger le segment à l'adresse virtuelle correspondante en mémoire en fonction de cette adresse virtuelle. Puisque cette fonction est fork
utilisée ultérieurement lors du chargement d'un segment chargeable à partir d'un programme compilé à partir du disque, nous utilisons la table des pages du processus qui appelle fork, nous devons donc déterminer si l'adresse virtuelle de la mémoire de destination est valide dans la table des pages. n'est pas valide, puis demandez de la mémoire physique pour l'adresse virtuelle spécifiée. Une fois l'application mémoire terminée, nous sys_read
pouvons appeler le segment chargeable du disque à l'adresse virtuelle mémoire spécifiée.
(myos/userprog/exec.c)
#include "exec.h"
#include "stdint.h"
#include "global.h"
#include "memory.h"
#include "fs.h"
typedef uint32_t Elf32_Word, Elf32_Addr, Elf32_Off;
typedef uint16_t Elf32_Half;
/* 32位elf头 */
struct Elf32_Ehdr
{
unsigned char e_ident[16];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
};
/* 程序头表Program header.就是段描述头 */
struct Elf32_Phdr
{
Elf32_Word p_type; // 见下面的enum segment_type
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
};
/* 段类型 */
enum segment_type
{
PT_NULL, // 忽略
PT_LOAD, // 可加载程序段
PT_DYNAMIC, // 动态加载信息
PT_INTERP, // 动态加载器名称
PT_NOTE, // 一些辅助信息
PT_SHLIB, // 保留
PT_PHDR // 程序头表
};
/* 将文件描述符fd指向的文件中,偏移为offset,大小为filesz的段加载到虚拟地址为vaddr的内存 */
static bool segment_load(int32_t fd, uint32_t offset, uint32_t filesz, uint32_t vaddr)
{
uint32_t vaddr_first_page = vaddr & 0xfffff000; // vaddr地址所在的页框
uint32_t size_in_first_page = PG_SIZE - (vaddr & 0x00000fff); // 加载到内存后,文件在第一个页框中占用的字节大小
uint32_t occupy_pages = 0;
/* 若一个页框容不下该段 */
if (filesz > size_in_first_page)
{
uint32_t left_size = filesz - size_in_first_page;
occupy_pages = DIV_ROUND_UP(left_size, PG_SIZE) + 1; // 1是指vaddr_first_page
}
else
{
occupy_pages = 1;
}
/* 为进程分配内存 */
uint32_t page_idx = 0;
uint32_t vaddr_page = vaddr_first_page;
while (page_idx < occupy_pages)
{
uint32_t *pde = pde_ptr(vaddr_page);
uint32_t *pte = pte_ptr(vaddr_page);
/* 如果pde不存在,或者pte不存在就分配内存.
* pde的判断要在pte之前,否则pde若不存在会导致
* 判断pte时缺页异常 */
if (!(*pde & 0x00000001) || !(*pte & 0x00000001))
{
if (get_a_page(PF_USER, vaddr_page) == NULL)
{
return false;
}
} // 如果原进程的页表已经分配了,利用现有的物理页,直接覆盖进程体
vaddr_page += PG_SIZE;
page_idx++;
}
sys_lseek(fd, offset, SEEK_SET);
sys_read(fd, (void *)vaddr, filesz);
return true;
}
load
Selon le chemin entrant, chargez le segment chargeable du programme sur le disque et renvoyez enfin l'adresse d'entrée du programme. Principe : Le programme compilé est sur le disque, en commençant par l'en-tête ELF. Nous lisons ceci et obtenons le décalage, la quantité et la taille de l'en-tête du programme. Ensuite, nous lisons l'en-tête du programme dans une boucle basée sur ces informations et appelons le segment_load
segment chargeable en mémoire en fonction de chaque information d'en-tête de programme.
Modifier ( myos/userprog/exec.c )
#include "string.h"
/* 从文件系统上加载用户程序pathname,成功则返回程序的起始地址,否则返回-1 */
static int32_t load(const char *pathname)
{
int32_t ret = -1;
struct Elf32_Ehdr elf_header;
struct Elf32_Phdr prog_header;
memset(&elf_header, 0, sizeof(struct Elf32_Ehdr));
int32_t fd = sys_open(pathname, O_RDONLY);
if (fd == -1)
{
return -1;
}
if (sys_read(fd, &elf_header, sizeof(struct Elf32_Ehdr)) != sizeof(struct Elf32_Ehdr))
{
ret = -1;
goto done;
}
/* 校验elf头 */
if (memcmp(elf_header.e_ident, "\177ELF\1\1\1", 7) || elf_header.e_type != 2 || elf_header.e_machine != 3 || elf_header.e_version != 1 || elf_header.e_phnum > 1024 || elf_header.e_phentsize != sizeof(struct Elf32_Phdr))
{
ret = -1;
goto done;
}
Elf32_Off prog_header_offset = elf_header.e_phoff;
Elf32_Half prog_header_size = elf_header.e_phentsize;
/* 遍历所有程序头 */
uint32_t prog_idx = 0;
while (prog_idx < elf_header.e_phnum)
{
memset(&prog_header, 0, prog_header_size);
/* 将文件的指针定位到程序头 */
sys_lseek(fd, prog_header_offset, SEEK_SET);
/* 只获取程序头 */
if (sys_read(fd, &prog_header, prog_header_size) != prog_header_size)
{
ret = -1;
goto done;
}
/* 如果是可加载段就调用segment_load加载到内存 */
if (PT_LOAD == prog_header.p_type)
{
if (!segment_load(fd, prog_header.p_offset, prog_header.p_filesz, prog_header.p_vaddr))
{
ret = -1;
goto done;
}
}
/* 更新下一个程序头的偏移 */
prog_header_offset += elf_header.e_phentsize;
prog_idx++;
}
ret = elf_header.e_entry;
done:
sys_close(fd);
return ret;
}
En C et C++, \xHH
une attention particulière est requise lors de l'utilisation de la séquence d'échappement hexadécimale du format, car cette séquence continuera à analyser tous les chiffres hexadécimaux valides jusqu'à ce qu'un chiffre ou une séquence non hexadécimal soit rencontré. La longueur atteint sa valeur maximale. La chaîne "\x7fELF"
sera analysée comme un caractère \x7fE
, puis LF
, au lieu du \x7f
et attendu ELF
.
sys_execv
Remplacez le processus actuel par le programme pointé par path. Notez que cette fonction est fork
appelée plus tard. Principe : Appelez d'abord load
le segment exécutable du chargeur dans la mémoire et obtenez l'adresse d'entrée du programme. Il suffit ensuite de modifier les données dans le pcb, notamment : le nom du programme, le registre utilisé pour passer les paramètres de la pile d'interruption dans la pile noyau (cette fonction s'exécute en mode noyau et exécute un nouveau processus intr_exit
en revenant en mode utilisateur, donc la pile d'interruptions Les données seront intr_exit
envoyées au registre par l'opération push pour atteindre l'objectif de transmission des paramètres), eip dans la pile d'interruptions est utilisé pour accéder à l'entrée du programme, et esp dans la pile d'interruptions est utilisé pour définir le sommet position de la pile du nouveau processus (la fork
table de la page du processus parent est copiée), l'espace d'adressage physique est réappliqué pour copier les données de la pile utilisateur, il n'y a donc pas lieu de s'inquiéter que l'adresse virtuelle utilisée par le nouvel utilisateur du processus la pile ne correspond pas à l'adresse physique). Enfin, définissez esp sur l'emplacement de la pile d'interruptions via l'assemblage en ligne, puis passez à l'exécution intr_exit
afin que le nouveau processus puisse être exécuté.
Modifier ( myos/userprog/exec.c )
#include "thread.h"
/* 用path指向的程序替换当前进程 */
int32_t sys_execv(const char *path, const char *argv[])
{
uint32_t argc = 0;
while (argv[argc])
{
argc++;
}
int32_t entry_point = load(path);
if (entry_point == -1)
{
// 若加载失败则返回-1
return -1;
}
struct task_struct *cur = running_thread();
/* 修改进程名 */
memcpy(cur->name, path, TASK_NAME_LEN);
cur->name[TASK_NAME_LEN - 1] = 0;
struct intr_stack *intr_0_stack = (struct intr_stack *)((uint32_t)cur + PG_SIZE - sizeof(struct intr_stack));
/* 参数传递给用户进程 */
intr_0_stack->ebx = (int32_t)argv;
intr_0_stack->ecx = argc;
intr_0_stack->eip = (void *)entry_point;
/* 使新用户进程的栈地址为最高用户空间地址 */
intr_0_stack->esp = (void *)0xc0000000;
/* exec不同于fork,为使新进程更快被执行,直接从中断返回 */
asm volatile("movl %0, %%esp; jmp intr_exit" : : "g"(intr_0_stack) : "memory");
return 0;
}
Code de support, modification ( myos/thread/thread.h )
#define TASK_NAME_LEN 16
sera sys_execv
transformé en un appel système
Ajouter le numéro d'appel système et modifier ( myos/lib/user/syscall.h )
enum SYSCALL_NR
{
SYS_GETPID,
SYS_WRITE,
SYS_MALLOC,
SYS_FREE,
SYS_FORK,
SYS_READ,
SYS_PUTCHAR,
SYS_CLEAR,
SYS_GETCWD,
SYS_OPEN,
SYS_CLOSE,
SYS_LSEEK,
SYS_UNLINK,
SYS_MKDIR,
SYS_OPENDIR,
SYS_CLOSEDIR,
SYS_CHDIR,
SYS_RMDIR,
SYS_READDIR,
SYS_REWINDDIR,
SYS_STAT,
SYS_PS,
SYS_EXECV
};
Entrée d'appel système en mode utilisateur, modifiée ( myos/lib/user/syscall.c )
int execv(const char *pathname, char **argv)
{
return _syscall2(SYS_EXECV, pathname, argv);
}
Déclarez l'entrée d'appel système en mode utilisateur et modifiez ( myos/lib/user/syscall.h )
int execv(const char* pathname, char** argv);
Modification de la table des appels système, modification ( myos/userprog/syscall-init.c )
#include "exec.h"
/* 初始化系统调用 */
void syscall_init(void)
{
put_str("syscall_init start\n");
syscall_table[SYS_GETPID] = sys_getpid;
syscall_table[SYS_WRITE] = sys_write;
syscall_table[SYS_MALLOC] = sys_malloc;
syscall_table[SYS_FREE] = sys_free;
syscall_table[SYS_FORK] = sys_fork;
syscall_table[SYS_READ] = sys_read;
syscall_table[SYS_PUTCHAR] = sys_putchar;
syscall_table[SYS_CLEAR] = cls_screen;
syscall_table[SYS_GETCWD] = sys_getcwd;
syscall_table[SYS_OPEN] = sys_open;
syscall_table[SYS_CLOSE] = sys_close;
syscall_table[SYS_LSEEK] = sys_lseek;
syscall_table[SYS_UNLINK] = sys_unlink;
syscall_table[SYS_MKDIR] = sys_mkdir;
syscall_table[SYS_OPENDIR] = sys_opendir;
syscall_table[SYS_CLOSEDIR] = sys_closedir;
syscall_table[SYS_CHDIR] = sys_chdir;
syscall_table[SYS_RMDIR] = sys_rmdir;
syscall_table[SYS_READDIR] = sys_readdir;
syscall_table[SYS_REWINDDIR] = sys_rewinddir;
syscall_table[SYS_STAT] = sys_stat;
syscall_table[SYS_PS] = sys_ps;
syscall_table[SYS_EXECV] = sys_execv;
put_str("syscall_init done\n");
}
Modifiez my_shell
et ajoutez le code des commandes externes pour charger les programmes binaires compilés à partir du disque et les exécuter. L'essentiel est de fork
créer d'abord un processus enfant, puis d'appeler le processus enfant pour make_clear_abs_path
analyser le chemin entrant, puis de l'appeler execv
pour l'exécuter.
Modifier ( myos/shell/shell.c/my_shell )
#include "syscall.h"
void my_shell(void)
{
cwd_cache[0] = '/';
while (1)
{
print_prompt();
memset(final_path, 0, MAX_PATH_LEN);
memset(cmd_line, 0, MAX_PATH_LEN);
readline(cmd_line, MAX_PATH_LEN);
if (cmd_line[0] == 0)
{
// 若只键入了一个回车
continue;
}
argc = -1;
argc = cmd_parse(cmd_line, argv, ' ');
if (argc == -1)
{
printf("num of arguments exceed %d\n", MAX_ARG_NR);
continue;
}
if (!strcmp("ls", argv[0]))
{
buildin_ls(argc, argv);
}
else if (!strcmp("cd", argv[0]))
{
if (buildin_cd(argc, argv) != NULL)
{
memset(cwd_cache, 0, MAX_PATH_LEN);
strcpy(cwd_cache, final_path);
}
}
else if (!strcmp("pwd", argv[0]))
{
buildin_pwd(argc, argv);
}
else if (!strcmp("ps", argv[0]))
{
buildin_ps(argc, argv);
}
else if (!strcmp("clear", argv[0]))
{
buildin_clear(argc, argv);
}
else if (!strcmp("mkdir", argv[0]))
{
buildin_mkdir(argc, argv);
}
else if (!strcmp("rmdir", argv[0]))
{
buildin_rmdir(argc, argv);
}
else if (!strcmp("rm", argv[0]))
{
buildin_rm(argc, argv);
}
else
{
// 如果是外部命令,需要从磁盘上加载
int32_t pid = fork();
if (pid)
{
// 父进程
/* 下面这个while必须要加上,否则父进程一般情况下会比子进程先执行,
因此会进行下一轮循环将findl_path清空,这样子进程将无法从final_path中获得参数*/
while (1)
;
}
else
{
// 子进程
make_clear_abs_path(argv[0], final_path);
argv[0] = final_path;
/* 先判断下文件是否存在 */
struct stat file_stat;
memset(&file_stat, 0, sizeof(struct stat));
if (stat(argv[0], &file_stat) == -1)
{
printf("my_shell: cannot access %s: No such file or directory\n", argv[0]);
}
else
{
execv(argv[0], argv);
}
while (1)
;
}
}
int32_t arg_idx = 0;
while (arg_idx < MAX_ARG_NR)
{
argv[arg_idx] = NULL;
arg_idx++;
}
}
PANIC("my_shell: should not be here");
}
Après avoir compilé un programme utilisateur prog_no_arg
, nous devons écrire nous-mêmes hd60M.img, puis utiliser le système d'exploitation pour obtenir le programme utilisateur compilé de hd60M.img dans la mémoire, puis l'écrire dans hd80M.img avec le système de fichiers et enfin l'exécuter.prog_no_arg
(myos/command/prog_no_arg.c)
#include "stdio.h"
int main(void)
{
printf("prog_no_arg from disk\n");
while (1)
;
return 0;
}
Parce que ce programme réutilise printf, et printf appelle vsprintf, et vsprintf appelle strcpy, et strcpy appelle la macro ASSERT, et ASSERT utilise PANIC, et PANIC utilise panic_spin, et panic_spin utilise intr_disable(). C'est-à-dire que lorsque nous appelons le programme printf en mode utilisateur, si l'ASSERT de strcpy au milieu échoue, intr_disable sera appelé directement en mode utilisateur. Ceci n'est absolument pas autorisé et une erreur de protection au niveau des privilèges sera signalée. en courant ! L'approche correcte consiste à passer d'abord en mode noyau via un appel système, puis à appeler intr_disable.
Par conséquent, nous implémentons d'abord l'assertion utilisée en mode utilisateur
(myos/lib/user/assert.c)
#include "assert.h"
#include "stdio.h"
void user_spin(char *filename, int line, const char *func, const char *condition)
{
printf("\n\n\n\nfilename %s\nline %d\nfunction %s\ncondition %s\n", filename, line, func, condition);
while (1)
;
}
(myos/lib/user/assert.h)
#ifndef __LIB_USER_ASSERT_H
#define __LIB_USER_ASSERT_H
#include "global.h"
void user_spin(char *filename, int line, const char *func, const char *condition);
#define panic(...) user_spin(__FILE__, __LINE__, __func__, __VA_ARGS__)
#ifdef NDEBUG
#define assert(CONDITION) ((void)0)
#else
#define assert(CONDITION) \
if (!(CONDITION)) \
{
\
panic(#CONDITION); \
}
#endif /*NDEBUG*/
#endif /*__LIB_USER_ASSERT_H*/
De cette façon, si notre assert
jugement est erroné, nous entrerons normalement dans l'état du noyau via l'appel système printf
danswrite
Modifions à la fois ASSERT et PANIC utilisés par les programmes en mode utilisateur dans le noyau.
Modifiez tout ce qui se trouve dans ( myos/lib/string.cASSERT
) en , puis modifiez assert
le fichier d'en-tête en#include "debug.h"
#incldue "assert.h"
Modifiez tout dans ( myos/shell/buildin_cmd.cASSERT
) en , puis modifiez assert
le fichier d'en-tête en#include "debug.h"
#incldue "assert.h"
Modifiez tout dans ( myos/shell/shell.cASSERT
) en assert
, modifiez tous les PANIC
to , puis modifiez panic
le fichier d'en-tête en#include "debug.h"
#incldue "assert.h"
Modifiez tout ce qui se trouve dans ( myos/kernel/main.cPANIC
) en , puis modifiez panic
le fichier d'en-tête en#include "debug.h"
#incldue "assert.h"
Un script pour faire fonctionner prog_no_arg.c est donné. La fonction principale de ce script est de compiler prog_no_arg.c puis de le lier avec le fichier .o utilisé (ici on réutilise le fichier .o pour le système d'exploitation, logiquement parlant, il nous faut pour implémenter le fichier .o du programme utilisateur séparément, mais soyons paresseux), et enfin écrire sur le disque hd60M.img avec un décalage de 300 secteurs
Remarque : par rapport au script de l'auteur, le compilateur utilisé a été modifié en gcc-4.4, CFLAGS, et les paramètres derrière ld doivent être modifiés DD_OUT
par le chemin hd60M.img dans votre propre environnement ! ! ! **Le script doit être exécuté une fois que le système d'exploitation a tout créé (afin que le fichier .o utilisé apparaisse dans le répertoire de construction). Le script doit être exécuté dans le répertoire de commandes. Avant que le script ne soit exécuté, les autorisations exécutables doivent à ajouter. Commande : , exécutez le chmod +x compile.sh
script Commande :./compile.sh
(myos/command/compile.sh)
#### 此脚本应该在command目录下执行
if [[ ! -d "../lib" || ! -d "../build" ]];then
echo "dependent dir don\`t exist!"
cwd=$(pwd)
cwd=${cwd##*/}
cwd=${cwd%/}
if [[ $cwd != "command" ]];then
echo -e "you\`d better in command dir\n"
fi
exit
fi
CC="gcc-4.4"
BIN="prog_no_arg"
CFLAGS="-Wall -c -fno-builtin -W -Wstrict-prototypes \
-Wmissing-prototypes -Wsystem-headers -m32 -fno-stack-protector"
LIB="../lib/"
OBJS="../build/string.o ../build/syscall.o \
../build/stdio.o ../build/assert.o"
DD_IN=$BIN
DD_OUT="/home/rlk/Desktop/bochs/hd60M.img"
$CC $CFLAGS -I $LIB -o $BIN".o" $BIN".c"
ld -e main $BIN".o" $OBJS -o $BIN -m elf_i386
SEC_CNT=$(ls -l $BIN|awk '{
printf("%d", ($5+511)/512)}')
if [[ -f $BIN ]];then
dd if=./$DD_IN of=$DD_OUT bs=512 \
count=$SEC_CNT seek=300 conv=notrunc
fi
########## 以上核心就是下面这三条命令 ##########
#gcc -Wall -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes \
# -Wsystem-headers -I ../lib -o prog_no_arg.o prog_no_arg.c
#ld -e main prog_no_arg.o ../build/string.o ../build/syscall.o\
# ../build/stdio.o ../build/assert.o -o prog_no_arg
#dd if=prog_no_arg of=/home/work/my_workspace/bochs/hd60M.img \
# bs=512 count=10 seek=300 conv=notrunc
Code de test, ( myos/kernel/main.c ), remarque : file_size
veuillez modifier cette variable à votre propre taille prog_no_arg. Vous pouvez vérifier la taille de prog_no_arg par ls -l dans le répertoire de commandes. Nous allons tout faire deux fois. La première fois est de faire en sorte que prog_no_arg ait le fichier .o disponible, et la deuxième fois est de modifier main.c pour charger prog_no_arg de hd60M.img vers hd80M.img.
#include "print.h"
#include "init.h"
#include "fork.h"
#include "stdio.h"
#include "syscall.h"
#include "assert.h"
#include "shell.h"
#include "console.h"
#include "ide.h"
#include "stdio-kernel.h"
void init(void);
int main(void)
{
put_str("I am kernel\n");
init_all();
uint32_t file_size = 20684; //这个变量请自行修改成自己的prog_no_arg大小
uint32_t sec_cnt = DIV_ROUND_UP(file_size, 512);
struct disk *sda = &channels[0].devices[0];
void *prog_buf = sys_malloc(file_size);
ide_read(sda, 300, prog_buf, sec_cnt);
int32_t fd = sys_open("/prog_no_arg", O_CREAT | O_RDWR);
if (fd != -1)
{
if (sys_write(fd, prog_buf, file_size) == -1)
{
printk("file write error!\n");
while (1)
;
}
}
cls_screen();
console_put_str("[rabbit@localhost /]$ ");
while (1)
;
return 0;
}
/* init进程 */
void init(void)
{
uint32_t ret_pid = fork();
if (ret_pid)
{
// 父进程
while (1)
;
}
else
{
// 子进程
my_shell();
}
panic("init: should not be here");
}
Une erreur s'est produite pendant le fonctionnement. Après le dépannage, la modification ( myos/fs/fs.c/sys_getcwd ) a été
if (child_inode_nr == 0)
{
buf[0] = '/';
buf[1] = 0;
return buf;
}
pour
if (child_inode_nr == 0)
{
buf[0] = '/';
buf[1] = 0;
sys_free(io_buf);
return buf;
}
Modifier ( myos/kernel/memory.c/get_a_page )
...
if (cur->pgdir != NULL && pf == PF_USER)
{
bit_idx = (vaddr - cur->userprog_vaddr.vaddr_start) / PG_SIZE;
ASSERT(bit_idx > 0);
bitmap_set(&cur->userprog_vaddr.vaddr_bitmap, bit_idx, 1);
}
...
if (page_phyaddr == NULL)
return NULL;
...
pour
...
if (cur->pgdir != NULL && pf == PF_USER)
{
bit_idx = (vaddr - cur->userprog_vaddr.vaddr_start) / PG_SIZE;
ASSERT(bit_idx >= 0);
bitmap_set(&cur->userprog_vaddr.vaddr_bitmap, bit_idx, 1);
}
...
if (page_phyaddr == NULL)
{
lock_release(&mem_pool->lock);
return NULL;
}
...
Section h :
Permettre aux processus utilisateur de prendre en charge la transmission de paramètres
_start
Cette fonction sera execv
exécutée, et elle exécutera le programme utilisateur réel en son nom, car cette fonction deviendra la véritable entrée du programme avant de lier le programme utilisateur, afin qu'elle puisse transmettre des paramètres au programme utilisateur. Parce que nous sys_exec
avons défini le code de la pile d'interruption ebx et ecx dans la pile du noyau du programme à ouvrir, intr_exit
nous mettrons l'adresse du tableau de pointeurs de chaîne de paramètres dans ebx, le nombre de paramètres dans ecx, puis mettrons _start
ces deux registres sont poussé sur la pile, et main
les déclarations des fonctions standard sont toutes int mian(int argc, char** argv)
, main
vous pouvez donc naturellement trouver les paramètres souhaités sur la pile. Et comme _start
le passage des paramètres sur la pile est conforme à main
la déclaration de fonction, c'est-à-dire qu'ils sont poussés séquentiellement sur la pile de gauche à droite, cette main
opération ne nécessite aucun paramètre externe et peut s'exécuter normalement, comme n'importe quelle fonction ordinaire en langage C avec paramètres.
(myos/command/start.S)
[bits 32]
extern main
section .text
global _start
_start:
;下面这两个要和execv中load之后指定的寄存器一致
push ebx ;压入argv
push ecx ;压入argc
call main
prog_arg.c
in main
ouvre un sous-processus pour exécuter argv[1]
le programme spécifié dans.
(myos/commadn/prog_arg.c)
#include "stdio.h"
#include "syscall.h"
#include "string.h"
int main(int argc, char **argv)
{
int arg_idx = 0;
while (arg_idx < argc)
{
printf("argv[%d] is %s\n", arg_idx, argv[arg_idx]);
arg_idx++;
}
int pid = fork();
if (pid)
{
int delay = 900000;
while (delay--)
;
printf("\n I`m father prog, my pid:%d, I will show process list\n", getpid());
ps();
}
else
{
char abs_path[512] = {
0};
printf("\n I`m child prog, my pid:%d, I will exec %s right now\n", getpid(), argv[1]);
if (argv[1][0] != '/')
{
getcwd(abs_path, 512);
strcat(abs_path, "/");
strcat(abs_path, argv[1]);
execv(abs_path, argv);
}
else
{
execv(argv[1], argv);
}
}
while (1)
;
return 0;
}
Traiter le script prog_arg.c ( myos/command/compile.sh )
Par rapport au script de la section précédente, le BIN du programme à compiler a été modifié, y compris le fichier d'en-tête LIB, reliant le fichier .o OBJS, la commande pour compiler start.S, la commande pour créer une bibliothèque statique, et la commande link.
ar rcs simple_crt.a $OBJS start.o
explication de:
-
ar
: Il s'agit d'un programme utilisé pour créer, modifier et extraire des bibliothèques statiques. Les bibliothèques statiques sont généralement utilisées pour regrouper plusieurs fichiers objets dans un seul fichier, de sorte que plusieurs fichiers objets puissent être liés simultanément lors de la liaison. -
rcs
:r
: remplacez ou ajoutez le fichier objet spécifié à la bibliothèque. Si un fichier objet du même nom existe déjà dans la bibliothèque, ce fichier sera remplacé par le nouveau fichier.c
: Si le fichier bibliothèque n'existe pas, créez un nouveau fichier bibliothèque.s
: Créez un index du fichier cible. Cela accélère la liaison.
-
simple_crt.a
: C'est le nom de la bibliothèque statique que vous souhaitez créer ou modifier. -
$OBJS start.o
: Il s'agit d'une liste de fichiers objets qui seront ajoutés ou remplacés dans la bibliothèque statique.
Ainsi, la commande entière signifie : ajouter ou remplacer tous les fichiers objets répertoriés dans $OBJS
et dans la bibliothèque statique, et créer un index pour ces fichiers objets. S'il n'existe pas encore, un nouveau fichier de bibliothèque statique sera créé.start.o
simple_crt.a
simple_crt.a
#### 此脚本应该在command目录下执行
if [[ ! -d "../lib" || ! -d "../build" ]];then
echo "dependent dir don\`t exist!"
cwd=$(pwd)
cwd=${cwd##*/}
cwd=${cwd%/}
if [[ $cwd != "command" ]];then
echo -e "you\`d better in command dir\n"
fi
exit
fi
CC="gcc-4.4"
BIN="prog_arg"
CFLAGS="-Wall -c -fno-builtin -W -Wstrict-prototypes \
-Wmissing-prototypes -Wsystem-headers -m32 -fno-stack-protector"
LIBS="-I ../lib -I ../lib/user -I ../fs -I ../thread -I ../lib/kernel -I ../kernel"
OBJS="../build/string.o ../build/syscall.o \
../build/stdio.o ../build/assert.o start.o"
DD_IN=$BIN
DD_OUT="/home/rlk/Desktop/bochs/hd60M.img"
nasm -f elf ./start.S -o ./start.o
ar rcs simple_crt.a $OBJS start.o
$CC $CFLAGS $LIBS -o $BIN".o" $BIN".c"
ld $BIN".o" simple_crt.a -o $BIN -m elf_i386
SEC_CNT=$(ls -l $BIN|awk '{
printf("%d", ($5+511)/512)}')
if [[ -f $BIN ]];then
dd if=./$DD_IN of=$DD_OUT bs=512 \
count=$SEC_CNT seek=300 conv=notrunc
fi
########## 以上核心就是下面这三条命令 ##########
#gcc -Wall -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes \
# -Wsystem-headers -I ../lib -o prog_no_arg.o prog_no_arg.c
#ld -e main prog_no_arg.o ../build/string.o ../build/syscall.o\
# ../build/stdio.o ../build/assert.o -o prog_no_arg
#dd if=prog_no_arg of=/home/work/my_workspace/bochs/hd60M.img \
# bs=512 count=10 seek=300 conv=notrunc
Code de test ( myos/kernel/main.c )
#include "print.h"
#include "init.h"
#include "fork.h"
#include "stdio.h"
#include "syscall.h"
#include "assert.h"
#include "shell.h"
#include "console.h"
#include "ide.h"
#include "stdio-kernel.h"
void init(void);
int main(void)
{
put_str("I am kernel\n");
init_all();
uint32_t file_size = 20840;
uint32_t sec_cnt = DIV_ROUND_UP(file_size, 512);
struct disk *sda = &channels[0].devices[0];
void *prog_buf = sys_malloc(file_size);
ide_read(sda, 300, prog_buf, sec_cnt);
int32_t fd = sys_open("/prog_arg", O_CREAT | O_RDWR);
if (fd != -1)
{
if (sys_write(fd, prog_buf, file_size) == -1)
{
printk("file write error!\n");
while (1)
;
}
}
cls_screen();
console_put_str("[rabbit@localhost /]$ ");
while (1)
;
return 0;
}
/* init进程 */
void init(void)
{
uint32_t ret_pid = fork();
if (ret_pid)
{
// 父进程
while (1)
;
}
else
{
// 子进程
my_shell();
}
panic("init: should not be here");
}
prog_arg.c
Il sera start.S
compilé et lié avec le code pour former un nouveau programme, puis comme dans la section précédente, écrivez d'abord hd60M.img sur le disque nu, puis main.c
chargez la nouvelle application du disque nu prog_arg
dans la mémoire, puis écrivez dans le système de fichiers hd80M.img. Lorsque nous démarrons le système d'exploitation et entrons dans le shell ./prog_arg /prog_no_arg
, argv[0] = prog_arg, argv[1] = prog_no_arg
. exec
Commencez en premier prog_arg
(car argv[0]
c'est le programme à exécuter, et ce qui suit argv[1+n]
sont les paramètres que nous transmettons à ce programme). Nous sys_exec(exec的真正实现)
avons transmis l'adresse du tableau de pointeurs de chaîne de paramètres à utiliser par le programme dans ebx
, et _start
c'est prog_arg
la véritable entrée. _start
Il y a une phrase dans push_ebx
le code, ce qui signifie que l'adresse du tableau de pointeurs de chaîne de paramètres a été transmise au prog_arg
programme. Naturellement prog_arg
, le programme peut être argv[1]
démarré parprog_no_arg
Section I :
Arrêt du processus et recyclage des ressources
Présentez d’abord quelques concepts importants :
exit
Appel système : Cet appel est utilisé pour terminer le processus. Lorsqu'un processus appelle exit
, il libère toutes les ressources à l'exception du bloc de contrôle de processus (pcb). Le PCB doit être traité spécialement car il contient des informations importantes sur le processus, telles que l'état de sortie. Remarque spéciale : exit
les appels système appartiennent à la bibliothèque d'exécution du programme et seront exécutés indépendamment du fait que le processus les appelle activement ou non. Tout comme notre _start
fonction.
wait
Appel système : Il s'agit d'un appel lié à la synchronisation des processus et au recyclage des ressources. Concrètement, il a les fonctions suivantes :
- Bloque le processus parent jusqu'à ce qu'un processus enfant se termine et reçoive la valeur de retour du processus enfant.
- Recyclez les ressources PCB utilisées par le processus enfant pour garantir qu'aucune ressource n'est gaspillée.
Lorsqu'un processus parent crée un processus enfant pour effectuer une certaine tâche, le processus parent peut avoir besoin de connaître l'état de sortie du processus enfant. Une fois que le processus enfant a terminé sa tâche, il enregistre son état de sortie dans PCB et appelle exit
exit. A ce moment, le PCB du processus enfant ne sera pas recyclé immédiatement car il contient l'état de sortie du processus enfant. wait
La carte PCB du processus enfant ne sera recyclée que lorsque le processus parent interrogera l'état du processus enfant via un appel système.
Processus orphelin : Si un processus parent se termine avant la fin de ses processus enfants, alors ces processus enfants seront appelés processus orphelins, ce qui signifie qu'il n'y a pas de processus parent pour récupérer leurs ressources PCB. Afin d'éviter le gaspillage des ressources, ces processus orphelins seront init
« adoptés » par le processus, c'est-à-dire qu'ils deviendront init
des processus enfants du processus, recyclant init
ainsi leurs PCB.
Processus zombie : lorsqu'un processus enfant se termine mais que son processus parent n'appelle pas wait
pour récupérer ses ressources, le processus enfant ne peut pas être transmis à init à ce moment, donc le processus enfant devient un processus zombie. Ils occupent toujours le PCB mais ne font rien. L’existence de processus zombies peut entraîner un gaspillage de ressources.
PCB ajoute des membres indiquant le statut de sortie, tel qu'une sortie normale ou autre chose. Modifier ( myos/thread/thread.h )
/* 进程或线程的pcb,程序控制块, 此结构体用于存储线程的管理信息*/
struct task_struct
{
uint32_t *self_kstack; // 用于存储线程的栈顶位置,栈顶放着线程要用到的运行信息
pid_t pid;
enum task_status status;
uint8_t priority; // 线程优先级
char name[16]; // 用于存储自己的线程的名字
uint8_t ticks; // 线程允许上处理器运行还剩下的滴答值,因为priority不能改变,所以要在其之外另行定义一个值来倒计时
uint32_t elapsed_ticks; // 此任务自上cpu运行后至今占用了多少cpu嘀嗒数, 也就是此任务执行了多久*/
struct list_elem general_tag; // general_tag的作用是用于线程在一般的队列(如就绪队列或者等待队列)中的结点
struct list_elem all_list_tag; // all_list_tag的作用是用于线程队列thread_all_list(这个队列用于管理所有线程)中的结点
uint32_t *pgdir; // 进程自己页表的虚拟地址
struct virtual_addr userprog_vaddr; // 用户进程的虚拟地址
int32_t fd_table[MAX_FILES_OPEN_PER_PROC]; // 已打开文件数组
uint32_t cwd_inode_nr; // 进程所在的工作目录的inode编号
int16_t parent_pid; // 父进程pid
struct mem_block_desc u_block_desc[DESC_CNT]; // 用户进程内存块描述符
int8_t exit_status; // 进程结束时自己调用exit传入的参数
uint32_t stack_magic; // 如果线程的栈无限生长,总会覆盖地pcb的信息,那么需要定义个边界数来检测是否栈已经到了PCB的边界
};
free_a_phy_page
Utilisé pour récupérer des adresses physiques, l'essence est de récupérer les bits correspondant au bitmap du pool d'adresses physiques. De cette façon, cette adresse physique sera à nouveau attribuée la prochaine fois.
Modifier ( myos/kernel/memory.c )
/* 根据物理页框地址pg_phy_addr在相应的内存池的位图清0,不改动页表*/
void free_a_phy_page(uint32_t pg_phy_addr)
{
struct pool *mem_pool;
uint32_t bit_idx = 0;
if (pg_phy_addr >= user_pool.phy_addr_start)
{
mem_pool = &user_pool;
bit_idx = (pg_phy_addr - user_pool.phy_addr_start) / PG_SIZE;
}
else
{
mem_pool = &kernel_pool;
bit_idx = (pg_phy_addr - kernel_pool.phy_addr_start) / PG_SIZE;
}
bitmap_set(&mem_pool->pool_bitmap, bit_idx, 0);
}
Ajouter une déclaration de fonction et modifier ( myos/kernel/memory.h )
void free_a_phy_page(uint32_t pg_phy_addr);
Étant donné que notre processus doit libérer son propre pid après sa sortie, la gestion du pid d'origine alloue uniquement et ne recycle pas. Nous devons donc utiliser le bitmap pid pour gérer l'allocation et le recyclage du pid, modifier ( myos/thread/thread.c ) (les nouvelles allocate_pid
fonctions suivantes doivent remplacer les allocate_pid
fonctions d'origine)
allocate_pid
Utilisé pour allouer le pid en fonction du décalage des bits libres dans le bitmap pid + le pid de départ
pid_pool_init
Utilisé pour initialiser le bitmap pid et appelé thread_init
dans
release_pid
Pour libérer le pid, l'essentiel est de libérer la position 0 dans le bitmap pid correspondant au pid.
thread_init
Ajouter le code d'initialisation du pool pid
/* pid的位图,最大支持1024个pid */
uint8_t pid_bitmap_bits[128] = {
0};
/* pid池 */
struct pid_pool
{
struct bitmap pid_bitmap; // pid位图
uint32_t pid_start; // 起始pid
struct lock pid_lock; // 分配pid锁
} pid_pool;
/* 分配pid */
static pid_t allocate_pid(void)
{
lock_acquire(&pid_pool.pid_lock);
int32_t bit_idx = bitmap_scan(&pid_pool.pid_bitmap, 1);
bitmap_set(&pid_pool.pid_bitmap, bit_idx, 1);
lock_release(&pid_pool.pid_lock);
return (bit_idx + pid_pool.pid_start);
}
/* 初始化pid池 */
static void pid_pool_init(void)
{
pid_pool.pid_start = 1;
pid_pool.pid_bitmap.bits = pid_bitmap_bits;
pid_pool.pid_bitmap.btmp_bytes_len = 128;
bitmap_init(&pid_pool.pid_bitmap);
lock_init(&pid_pool.pid_lock);
}
/* 释放pid */
void release_pid(pid_t pid)
{
lock_acquire(&pid_pool.pid_lock);
int32_t bit_idx = pid - pid_pool.pid_start;
bitmap_set(&pid_pool.pid_bitmap, bit_idx, 0);
lock_release(&pid_pool.pid_lock);
}
void thread_init(void)
{
put_str("thread_init start\n");
list_init(&thread_ready_list);
list_init(&thread_all_list);
pid_pool_init();
/* 先创建第一个用户进程:init */
process_execute(init, "init"); // 放在第一个初始化,这是第一个进程,init进程的pid为1
/* 将当前main函数创建为线程 */
make_main_thread();
/* 创建idle线程 */
idle_thread = thread_start("idle", 10, idle, NULL);
put_str("thread_init done\n");
}
thread_exit
Utilisé pour recycler le PCB et la table des pages de la tâche spécifiée et le supprimer de la file d'attente prête
pid_check
sera list_traversal
appelé pour comparer si le pid de la tâche correspondant au all_list_tag
pointeur entrant est le pid entrant que vous recherchez.
pid2thread
Trouvez le PCB en fonction du pid entrant. Le principe est d'utiliser list_traversal
l'appel pid_check
. Une fois pid_check
trouvé, il retournera true, donc list_traversal
le pointeur du PCB sera renvoyé.
Modifier ( thread/thread.c )
/* 回收thread_over的pcb和页表,并将其从调度队列中去除 */
void thread_exit(struct task_struct *thread_over, bool need_schedule)
{
/* 要保证schedule在关中断情况下调用 */
intr_disable();
thread_over->status = TASK_DIED;
/* 如果thread_over不是当前线程,就有可能还在就绪队列中,将其从中删除 */
if (elem_find(&thread_ready_list, &thread_over->general_tag))
{
list_remove(&thread_over->general_tag);
}
if (thread_over->pgdir)
{
// 如是进程,回收进程的页表
mfree_page(PF_KERNEL, thread_over->pgdir, 1);
}
/* 从all_thread_list中去掉此任务 */
list_remove(&thread_over->all_list_tag);
/* 回收pcb所在的页,主线程的pcb不在堆中,跨过 */
if (thread_over != main_thread)
{
mfree_page(PF_KERNEL, thread_over, 1);
}
/* 归还pid */
release_pid(thread_over->pid);
/* 如果需要下一轮调度则主动调用schedule */
if (need_schedule)
{
schedule();
PANIC("thread_exit: should not be here\n");
}
}
/* 比对任务的pid */
static bool pid_check(struct list_elem *pelem, int32_t pid)
{
struct task_struct *pthread = elem2entry(struct task_struct, all_list_tag, pelem);
if (pthread->pid == pid)
{
return true;
}
return false;
}
/* 根据pid找pcb,若找到则返回该pcb,否则返回NULL */
struct task_struct *pid2thread(int32_t pid)
{
struct list_elem *pelem = list_traversal(&thread_all_list, pid_check, pid);
if (pelem == NULL)
{
return NULL;
}
struct task_struct *thread = elem2entry(struct task_struct, all_list_tag, pelem);
return thread;
}
Déclaration de fonction, modification ( myos/thread/thread.h )
void thread_exit(struct task_struct* thread_over, bool need_schedule);
struct task_struct* pid2thread(int32_t pid);
void release_pid(pid_t pid);
release_prog_resource
Utilisé pour libérer des ressources de tâche en fonction du pointeur PCB entrant, y compris 1. la page physique correspondante dans la table des pages (la méthode utilisée ici consiste à parcourir la table des pages) ; 2. le cadre de page physique occupé par le pool de mémoire virtuelle ; 3. . fermer et ouvrir le document
(myos/userprog/wait_exit.c)
#include "wait_exit.h"
#include "stdint.h"
#include "global.h"
#include "thread.h"
#include "fs.h"
/* 释放用户进程资源:
* 1 页表中对应的物理页
* 2 虚拟内存池占物理页框
* 3 关闭打开的文件 */
static void release_prog_resource(struct task_struct *release_thread)
{
uint32_t *pgdir_vaddr = release_thread->pgdir;
uint16_t user_pde_nr = 768, pde_idx = 0;
uint32_t pde = 0;
uint32_t *v_pde_ptr = NULL; // v表示var,和函数pde_ptr区分
uint16_t user_pte_nr = 1024, pte_idx = 0;
uint32_t pte = 0;
uint32_t *v_pte_ptr = NULL; // 加个v表示var,和函数pte_ptr区分
uint32_t *first_pte_vaddr_in_pde = NULL; // 用来记录pde中第0个pte的地址
uint32_t pg_phy_addr = 0;
/* 回收页表中用户空间的页框 */
while (pde_idx < user_pde_nr)
{
v_pde_ptr = pgdir_vaddr + pde_idx;
pde = *v_pde_ptr;
if (pde & 0x00000001)
{
// 如果页目录项p位为1,表示该页目录项下可能有页表项
first_pte_vaddr_in_pde = pte_ptr(pde_idx * 0x400000); // 一个页表表示的内存容量是4M,即0x400000
pte_idx = 0;
while (pte_idx < user_pte_nr)
{
v_pte_ptr = first_pte_vaddr_in_pde + pte_idx;
pte = *v_pte_ptr;
if (pte & 0x00000001)
{
/* 将pte中记录的物理页框直接在相应内存池的位图中清0 */
pg_phy_addr = pte & 0xfffff000;
free_a_phy_page(pg_phy_addr);
}
pte_idx++;
}
/* 将pde中记录的物理页框直接在相应内存池的位图中清0 */
pg_phy_addr = pde & 0xfffff000;
free_a_phy_page(pg_phy_addr);
}
pde_idx++;
}
/* 回收用户虚拟地址池所占的物理内存*/
uint32_t bitmap_pg_cnt = (release_thread->userprog_vaddr.vaddr_bitmap.btmp_bytes_len) / PG_SIZE;
uint8_t *user_vaddr_pool_bitmap = release_thread->userprog_vaddr.vaddr_bitmap.bits;
mfree_page(PF_KERNEL, user_vaddr_pool_bitmap, bitmap_pg_cnt);
/* 关闭进程打开的文件 */
uint8_t fd_idx = 3;
while (fd_idx < MAX_FILES_OPEN_PER_PROC)
{
if (release_thread->fd_table[fd_idx] != -1)
{
sys_close(fd_idx);
}
fd_idx++;
}
}
fild_child
sera list_traversal
appelé pour comparer si le parient_id de la tâche correspondant au all_list_tag
pointeur entrant est le ppid entrant que vous recherchez.
Modifier ( myos/userprog/wait_exit.c )
/* list_traversal的回调函数,
* 查找pelem的parent_pid是否是ppid,成功返回true,失败则返回false */
static bool find_child(struct list_elem *pelem, int32_t ppid)
{
/* elem2entry中间的参数all_list_tag取决于pelem对应的变量名 */
struct task_struct *pthread = elem2entry(struct task_struct, all_list_tag, pelem);
if (pthread->parent_pid == ppid)
{
// 若该任务的parent_pid为ppid,返回
return true; // list_traversal只有在回调函数返回true时才会停止继续遍历,所以在此返回true
}
return false; // 让list_traversal继续传递下一个元素
}
find_hanging_child
sera list_traversal
appelé pour comparer si all_list_tag
le ppid de la tâche correspondant au pointeur entrant est le pid entrant, et si le statut n'est pas TASK_HANGING (c'est le statut lorsque le processus n'est pas complètement terminé). Cette fonction est utilisée par le processus parent pour rechercher son processus enfant quitté afin de récupérer ses ressources restantes.
Modifier ( myos/userprog/wait_exit.c )
/* list_traversal的回调函数,
* 查找状态为TASK_HANGING的任务 */
static bool find_hanging_child(struct list_elem* pelem, int32_t ppid) {
struct task_struct* pthread = elem2entry(struct task_struct, all_list_tag, pelem);
if (pthread->parent_pid == ppid && pthread->status == TASK_HANGING) {
return true;
}
return false;
}
init_adopt_a_child
all_list_tag
Remplacez la tâche correspondant au pointeur entrant parent_pid
par 1, c'est-à-dire adoptez un processus enfant pour initialiser
Modifier ( myos/userprog/wait_exit.c )
/* list_traversal的回调函数,
* 将一个子进程过继给init */
static bool init_adopt_a_child(struct list_elem *pelem, int32_t pid)
{
struct task_struct *pthread = elem2entry(struct task_struct, all_list_tag, pelem);
if (pthread->parent_pid == pid)
{
// 若该进程的parent_pid为pid,返回
pthread->parent_pid = 1;
}
return false; // 让list_traversal继续传递下一个元素
}
sys_wait
Attendez que le processus enfant appelle exit, enregistrez l'état de sortie du processus enfant dans la variable pointée par status, recyclez la carte PCB et la table de pages du processus enfant, et enfin renvoyez le pid du processus enfant. Si tous les processus enfants sont en cours d'exécution, bloquez-vous. Il existe deux manières d'utiliser cette fonction. L'une consiste init
à appeler while(1) en continu pour recycler continuellement les ressources du processus enfant ; l'autre consiste à appeler le processus parent après le fork, puis à attendre que le processus enfant se termine et continue à s'exécuter. , puis recyclez les ressources restantes du processus enfant.
Modifier ( myos/userprog/wait_exit.c )
/* 等待子进程调用exit,将子进程的退出状态保存到status指向的变量.
* 成功则返回子进程的pid,失败则返回-1 */
pid_t sys_wait(int32_t *status)
{
struct task_struct *parent_thread = running_thread();
while (1)
{
/* 优先处理已经是挂起状态的任务 */
struct list_elem *child_elem = list_traversal(&thread_all_list, find_hanging_child, parent_thread->pid);
/* 若有挂起的子进程 */
if (child_elem != NULL)
{
struct task_struct *child_thread = elem2entry(struct task_struct, all_list_tag, child_elem);
*status = child_thread->exit_status;
/* thread_exit之后,pcb会被回收,因此提前获取pid */
uint16_t child_pid = child_thread->pid;
/* 2 从就绪队列和全部队列中删除进程表项*/
thread_exit(child_thread, false); // 传入false,使thread_exit调用后回到此处
/* 进程表项是进程或线程的最后保留的资源, 至此该进程彻底消失了 */
return child_pid;
}
/* 判断是否有子进程 */
child_elem = list_traversal(&thread_all_list, find_child, parent_thread->pid);
if (child_elem == NULL)
{
// 若没有子进程则出错返回
return -1;
}
else
{
/* 若子进程还未运行完,即还未调用exit,则将自己挂起,直到子进程在执行exit时将自己唤醒 */
thread_block(TASK_WAITING);
}
}
}
sys_exit
Le processus enfant est utilisé pour se terminer. Choses à faire lors de la sortie : 1. Laisser l'état de sortie dans son propre PCB ; 2. Adopter tous ses processus enfants pour s'initialiser ; 3. Recycler ses propres ressources à l'exception du PCB et de la table des pages ; 4 . , il peut y avoir un processus parent en attente d'être appelé exit
, il est donc nécessaire de réveiller le processus parent en attente ; 5. Bloquez-vous, c'est-à-dire remplacez le CPU. Cette fonction sera appelée par la bibliothèque d'exécution et le processus l'exécutera même s'il ne l'appelle pas activement.
#include "debug.h"
/* 子进程用来结束自己时调用 */
void sys_exit(int32_t status)
{
struct task_struct *child_thread = running_thread();
child_thread->exit_status = status;
if (child_thread->parent_pid == -1)
{
PANIC("sys_exit: child_thread->parent_pid is -1\n");
}
/* 将进程child_thread的所有子进程都过继给init */
list_traversal(&thread_all_list, init_adopt_a_child, child_thread->pid);
/* 回收进程child_thread的资源 */
release_prog_resource(child_thread);
/* 如果父进程正在等待子进程退出,将父进程唤醒 */
struct task_struct *parent_thread = pid2thread(child_thread->parent_pid);
if (parent_thread->status == TASK_WAITING)
{
thread_unblock(parent_thread);
}
/* 将自己挂起,等待父进程获取其status,并回收其pcb */
thread_block(TASK_HANGING);
}
Encapsuler sys_wait
et sys_exit
dans les appels système
Ajouter le numéro d'appel système et modifier ( myos/lib/user/syscall.h )
enum SYSCALL_NR {
SYS_GETPID,
SYS_WRITE,
SYS_MALLOC,
SYS_FREE,
SYS_FORK,
SYS_READ,
SYS_PUTCHAR,
SYS_CLEAR,
SYS_GETCWD,
SYS_OPEN,
SYS_CLOSE,
SYS_LSEEK,
SYS_UNLINK,
SYS_MKDIR,
SYS_OPENDIR,
SYS_CLOSEDIR,
SYS_CHDIR,
SYS_RMDIR,
SYS_READDIR,
SYS_REWINDDIR,
SYS_STAT,
SYS_PS,
SYS_EXECV,
SYS_EXIT,
SYS_WAIT
};
Créer une entrée d'appel système en mode utilisateur et modifier ( myos/lib/user/syscall.c )
/* 以状态status退出 */
void exit(int32_t status)
{
_syscall1(SYS_EXIT, status);
}
/* 等待子进程,子进程状态存储到status */
pid_t wait(int32_t *status)
{
return _syscall1(SYS_WAIT, status);
}
Déclaration, entrée de fonction d'appel système en mode utilisateur, modification ( myos/lib/user/syscall.h )
void exit(int32_t status);
pid_t wait(int32_t* status);
Dans la table des appels système, ajoutez la fonction de traitement des appels système réelle et modifiez ( myos/userprog/syscall-init.c )
#include "wait_exit.h"
/* 初始化系统调用 */
void syscall_init(void)
{
put_str("syscall_init start\n");
syscall_table[SYS_GETPID] = sys_getpid;
syscall_table[SYS_WRITE] = sys_write;
syscall_table[SYS_MALLOC] = sys_malloc;
syscall_table[SYS_FREE] = sys_free;
syscall_table[SYS_FORK] = sys_fork;
syscall_table[SYS_READ] = sys_read;
syscall_table[SYS_PUTCHAR] = sys_putchar;
syscall_table[SYS_CLEAR] = cls_screen;
syscall_table[SYS_GETCWD] = sys_getcwd;
syscall_table[SYS_OPEN] = sys_open;
syscall_table[SYS_CLOSE] = sys_close;
syscall_table[SYS_LSEEK] = sys_lseek;
syscall_table[SYS_UNLINK] = sys_unlink;
syscall_table[SYS_MKDIR] = sys_mkdir;
syscall_table[SYS_OPENDIR] = sys_opendir;
syscall_table[SYS_CLOSEDIR] = sys_closedir;
syscall_table[SYS_CHDIR] = sys_chdir;
syscall_table[SYS_RMDIR] = sys_rmdir;
syscall_table[SYS_READDIR] = sys_readdir;
syscall_table[SYS_REWINDDIR] = sys_rewinddir;
syscall_table[SYS_STAT] = sys_stat;
syscall_table[SYS_PS] = sys_ps;
syscall_table[SYS_EXECV] = sys_execv;
syscall_table[SYS_EXIT] = sys_exit;
syscall_table[SYS_WAIT] = sys_wait;
put_str("syscall_init done\n");
}
Intégrez exit
des fonctions dans la bibliothèque d'exécution. De cette façon, même s'il n'est pas explicitement appelé dans le programme exit
, il sera automatiquement appelé à la fin du programme, _start
de la même manière. Il est important de noter que le mécanisme de sortie du processus enfant est différent du mécanisme de retour de fonction ordinaire. Lorsqu'un processus enfant se termine, il ne « retourne » pas à son processus parent ; il termine simplement sa propre exécution. Le processus parent et le processus enfant sont complètement indépendants dans l'espace d'adressage mémoire et le contexte d'exécution.Le processus enfant ne peut pas "retourner" une valeur au processus parent selon la méthode conventionnelle d'appel de fonction (cela nécessite la prise en charge d'un autre mécanisme de communication inter-processus) . Au lieu de cela, le processus enfant fournit un statut de sortie qui décrit comment et pourquoi il s'est terminé. Ce push eax
n'est donc pas le genre de valeur de retour que nous voyons dans les appels de fonction normaux - comme un pointeur ou le résultat d'une sorte de calcul. En fait, il représente l'état final du processus enfant, comme nous l' main
avons écrit dans chaque fonction return 0
.
Modifier ( myos/command/start.S )
[bits 32]
extern main
extern exit
section .text
global _start
_start:
;下面这两个要和execv中load之后指定的寄存器一致
push ebx ;压入argv
push ecx ;压入argc
call main
;将main的返回值通过栈传给exit,gcc用eax存储返回值,这是ABI规定的
push eax
call exit
;exit不会返回
cat
Utilisé pour lire le contenu d'un fichier, non pas en tant qu'appel système, mais en tant que processus utilisateur. Le principe de base est d'appeler read
l'appel système, puis d'appeler write
l'appel système pour imprimer
(myos/command/cat.c)
#include "syscall.h"
#include "stdio.h"
#include "string.h"
int main(int argc, char **argv)
{
if (argc > 2 || argc == 1)
{
printf("cat: only support 1 argument.\neg: cat filename\n");
exit(-2);
}
int buf_size = 1024;
char abs_path[512] = {
0};
void *buf = malloc(buf_size);
if (buf == NULL)
{
printf("cat: malloc memory failed\n");
return -1;
}
if (argv[1][0] != '/')
{
getcwd(abs_path, 512);
strcat(abs_path, "/");
strcat(abs_path, argv[1]);
}
else
{
strcpy(abs_path, argv[1]);
}
int fd = open(abs_path, O_RDONLY);
if (fd == -1)
{
printf("cat: open: open %s failed\n", argv[1]);
return -1;
}
int read_bytes = 0;
while (1)
{
read_bytes = read(fd, buf, buf_size);
if (read_bytes == -1)
{
break;
}
write(1, buf, read_bytes);
}
free(buf);
close(fd);
return 66;
}
Script qui gère cat.c
(myos/command/compile.sh)
#### 此脚本应该在command目录下执行
if [[ ! -d "../lib" || ! -d "../build" ]];then
echo "dependent dir don\`t exist!"
cwd=$(pwd)
cwd=${cwd##*/}
cwd=${cwd%/}
if [[ $cwd != "command" ]];then
echo -e "you\`d better in command dir\n"
fi
exit
fi
CC="gcc-4.4"
BIN="cat"
CFLAGS="-Wall -c -fno-builtin -W -Wstrict-prototypes \
-Wmissing-prototypes -Wsystem-headers -m32 -fno-stack-protector"
LIBS="-I ../lib/ -I ../lib/kernel/ -I ../lib/user/ -I \
../kernel/ -I ../device/ -I ../thread/ -I \
../userprog/ -I ../fs/ -I ../shell/"
OBJS="../build/string.o ../build/syscall.o \
../build/stdio.o ../build/assert.o start.o"
DD_IN=$BIN
DD_OUT="/home/rlk/Desktop/bochs/hd60M.img"
nasm -f elf ./start.S -o ./start.o
ar rcs simple_crt.a $OBJS start.o
$CC $CFLAGS $LIBS -o $BIN".o" $BIN".c"
ld $BIN".o" simple_crt.a -o $BIN -m elf_i386
SEC_CNT=$(ls -l $BIN|awk '{
printf("%d", ($5+511)/512)}')
if [[ -f $BIN ]];then
dd if=./$DD_IN of=$DD_OUT bs=512 \
count=$SEC_CNT seek=300 conv=notrunc
fi
Maintenant que nous avons un mécanisme pour quitter le programme, nous n’avons finalement plus besoin d’utiliser while(1) pour bloquer le programme sans sauter partout !
Modifier ( myos/shell/shell.c/my_shell )
else
{
// 如果是外部命令,需要从磁盘上加载
int32_t pid = fork();
if (pid)
{
// 父进程
/* 下面这个while必须要加上,否则父进程一般情况下会比子进程先执行,
因此会进行下一轮循环将findl_path清空,这样子进程将无法从final_path中获得参数*/
while (1)
;
}
else
{
// 子进程
make_clear_abs_path(argv[0], final_path);
argv[0] = final_path;
/* 先判断下文件是否存在 */
struct stat file_stat;
memset(&file_stat, 0, sizeof(struct stat));
if (stat(argv[0], &file_stat) == -1)
{
printf("my_shell: cannot access %s: No such file or directory\n", argv[0]);
}
else
{
execv(argv[0], argv);
}
while (1)
;
}
}
pour
else
{
// 如果是外部命令,需要从磁盘上加载
int32_t pid = fork();
if (pid)
{
// 父进程
int32_t status;
int32_t child_pid = wait(&status); // 此时子进程若没有执行exit,my_shell会被阻塞,不再响应键入的命令
if (child_pid == -1)
{
// 按理说程序正确的话不会执行到这句,fork出的进程便是shell子进程
panic("my_shell: no child\n");
}
printf("child_pid %d, it's status: %d\n", child_pid, status);
}
else
{
// 子进程
make_clear_abs_path(argv[0], final_path);
argv[0] = final_path;
/* 先判断下文件是否存在 */
struct stat file_stat;
memset(&file_stat, 0, sizeof(struct stat));
if (stat(argv[0], &file_stat) == -1)
{
printf("my_shell: cannot access %s: No such file or directory\n", argv[0]);
}
else
{
execv(argv[0], argv);
}
}
}
Modifier ( myos/kernel/main.c ), la fonction principale doit terminer le chargement du programme cat de hd60M.img vers hd80M.img et quitter ; le processus init est appelé en permanence wait
pour recycler les ressources du processus zombie adopté
#include "print.h"
#include "init.h"
#include "fork.h"
#include "stdio.h"
#include "syscall.h"
#include "assert.h"
#include "shell.h"
#include "console.h"
#include "ide.h"
#include "stdio-kernel.h"
void init(void);
int main(void)
{
put_str("I am kernel\n");
init_all();
uint32_t file_size = 21196;
uint32_t sec_cnt = DIV_ROUND_UP(file_size, 512);
struct disk *sda = &channels[0].devices[0];
void *prog_buf = sys_malloc(file_size);
ide_read(sda, 300, prog_buf, sec_cnt);
int32_t fd = sys_open("cat.c", O_CREAT | O_RDWR);
if (fd != -1)
{
if (sys_write(fd, prog_buf, file_size) == -1)
{
printk("file write error!\n");
while (1)
;
}
}
cls_screen();
console_put_str("[rabbit@localhost /]$ ");
thread_exit(running_thread(), true);
return 0;
}
/* init进程 */
void init(void)
{
uint32_t ret_pid = fork();
if (ret_pid)
{
// 父进程
int status;
int child_pid;
/* init在此处不停的回收僵尸进程 */
while (1)
{
child_pid = wait(&status);
printf("I`m init, My pid is 1, I recieve a child, It`s pid is %d, status is %d\n", child_pid, status);
}
}
else
{
// 子进程
my_shell();
}
panic("init: should not be here");
}
Code de support, supprimer ( myos/userprog/fork.c/copy_pcb_vaddrbitmap_stack0 )
ASSERT(strlen(child_thread->name) < 11); // pcb.name的长度是16,为避免下面strcat越界
strcat(child_thread->name, "_fork");
Section j :
Cette section prendra en charge les tubes, qui sont des mécanismes utilisés pour la communication des processus parent-enfant.
Un tube est essentiellement un tampon en anneau situé dans l'espace du noyau. Suivant la philosophie de conception de Linux : tout est un fichier, nous traitons le pipeline comme un fichier. De cette façon, nous pouvons lire et écrire dans le tube via le descripteur de fichier. Lors de la communication entre les processus parent et enfant, le processus parent crée d'abord un canal, obtenant ainsi deux descripteurs de fichier, l'un pour la lecture et l'autre pour l'écriture. Par la suite, le processus parent utilise pour fork
créer le processus enfant. Le processus enfant hérite des fichiers ouverts par le processus parent, il peut donc également communiquer avec des canaux via ces descripteurs de fichiers pour obtenir une interaction avec le processus parent.
- Pipe anonyme : visible uniquement par le processus qui l'a créé et ses processus enfants, non accessible aux autres processus.
- Canal nommé : accessible par tous les processus du système.
Notre mécanisme de pipeline réutilisera le système de fichiers existant
ioq_length
Renvoie la longueur des données dans le tampon en anneau
Modifier ( myos/device/ioqueue.c )
/* 返回环形缓冲区中的数据长度 */
uint32_t ioq_length(struct ioqueue *ioq)
{
uint32_t len = 0;
if (ioq->head >= ioq->tail)
{
len = ioq->head - ioq->tail;
}
else
{
len = bufsize - (ioq->tail - ioq->head);
}
return len;
}
Déclaration de fonction, modification ( myos/device/ioqueue.h )
uint32_t ioq_length(struct ioqueue *ioq);
is_pipe
Déterminer si le fichier correspondant au descripteur de fichier est un tube
(myos/shell/pipe.c)
#include "pipe.h"
#include "stdint.h"
#include "global.h"
#include "file.h"
#include "fs.h"
/* 判断文件描述符local_fd是否是管道 */
bool is_pipe(uint32_t local_fd)
{
uint32_t global_fd = fd_local2global(local_fd);
return file_table[global_fd].fd_flag == PIPE_FLAG;
}
Déclaration de fonction, ( myos/shell/pipe.h )
#ifndef __SHELL_PIPE_H
#define __SHELL_PIPE_H
#include "global.h"
#define PIPE_FLAG 0xFFFF
bool is_pipe(uint32_t local_fd);
#endif
Code de support, modification ( myos/fs/fs.c )
static uint32_t fd_local2global(uint32_t local_fd)
pour
uint32_t fd_local2global(uint32_t local_fd)
Et ajoutez la déclaration de fonction, modifiez ( myos/fs/fs.h )
uint32_t fd_local2global(uint32_t local_fd);
sys_pipe
Utilisé pour créer un pipeline, le noyau consiste à créer une structure de fichiers ouverte globale, puis à postuler pour une page de noyau et à laisser les membres de la structure de fichiers précédente fd_inode
pointer vers cette page de noyau (dans le système de fichiers précédent, ce membre a pointé vers un struct inode), puis créez struct ioqueue
et initialisez la position de départ de cette page du noyau. Installez ensuite deux descripteurs de fichiers dans le processus, pointant vers cette structure de fichiers. Enregistrez enfin ces deux descripteurs de fichiers.
Modifier ( myos/shell/pipe.c )
#include "ioqueue.h"
/* 创建管道,成功返回0,失败返回-1 */
int32_t sys_pipe(int32_t pipefd[2])
{
int32_t global_fd = get_free_slot_in_global();
/* 申请一页内核内存做环形缓冲区 */
file_table[global_fd].fd_inode = get_kernel_pages(1);
/* 初始化环形缓冲区 */
ioqueue_init((struct ioqueue *)file_table[global_fd].fd_inode);
if (file_table[global_fd].fd_inode == NULL)
{
return -1;
}
/* 将fd_flag复用为管道标志 */
file_table[global_fd].fd_flag = PIPE_FLAG;
/* 将fd_pos复用为管道打开数 */
file_table[global_fd].fd_pos = 2;
pipefd[0] = pcb_fd_install(global_fd);
pipefd[1] = pcb_fd_install(global_fd);
return 0;
}
pipe_read
Transmettez le descripteur de fichier du canal, une adresse de tampon et le nombre d'octets à lire. Recherchez le tampon en anneau via le descripteur de fichier du canal struct ioqueue
, puis appelez-le pour ioq_getchar
en lire les données.
Modifier ( myos/shell/pipe.c )
/* 从管道中读数据 */
uint32_t pipe_read(int32_t fd, void *buf, uint32_t count)
{
char *buffer = buf;
uint32_t bytes_read = 0;
uint32_t global_fd = fd_local2global(fd);
/* 获取管道的环形缓冲区 */
struct ioqueue *ioq = (struct ioqueue *)file_table[global_fd].fd_inode;
/* 选择较小的数据读取量,避免阻塞 */
uint32_t ioq_len = ioq_length(ioq);
uint32_t size = ioq_len > count ? count : ioq_len;
while (bytes_read < size)
{
*buffer = ioq_getchar(ioq);
bytes_read++;
buffer++;
}
return bytes_read;
}
pipe_write
Transmettez le descripteur de fichier du canal, une adresse de tampon et le nombre d'octets à écrire. Recherchez le tampon en anneau via le descripteur de fichier du canal struct ioqueue
, puis appelez-le ioq_putchar
pour y écrire des données.
Modifier ( myos/shell/pipe.c )
/* 往管道中写数据 */
uint32_t pipe_write(int32_t fd, const void *buf, uint32_t count)
{
uint32_t bytes_write = 0;
uint32_t global_fd = fd_local2global(fd);
struct ioqueue *ioq = (struct ioqueue *)file_table[global_fd].fd_inode;
/* 选择较小的数据写入量,避免阻塞 */
uint32_t ioq_left = bufsize - ioq_length(ioq);
uint32_t size = ioq_left > count ? count : ioq_left;
const char *buffer = buf;
while (bytes_write < size)
{
ioq_putchar(ioq, *buffer);
bytes_write++;
buffer++;
}
return bytes_write;
}
Déclaration de fonction, modification ( myos/shell/pipe.h )
int32_t sys_pipe(int32_t pipefd[2]);
uint32_t pipe_read(int32_t fd, void *buf, uint32_t count);
uint32_t pipe_write(int32_t fd, const void *buf, uint32_t count);
sera sys_pipe
transformé en un appel système
Augmentez le numéro d'appel système et modifiez ( myos/lib/user/syscall.h )
enum SYSCALL_NR
{
SYS_GETPID,
SYS_WRITE,
SYS_MALLOC,
SYS_FREE,
SYS_FORK,
SYS_READ,
SYS_PUTCHAR,
SYS_CLEAR,
SYS_GETCWD,
SYS_OPEN,
SYS_CLOSE,
SYS_LSEEK,
SYS_UNLINK,
SYS_MKDIR,
SYS_OPENDIR,
SYS_CLOSEDIR,
SYS_CHDIR,
SYS_RMDIR,
SYS_READDIR,
SYS_REWINDDIR,
SYS_STAT,
SYS_PS,
SYS_EXECV,
SYS_EXIT,
SYS_WAIT,
SYS_PIPE
};
Ajouter une entrée d'appel système en mode utilisateur et modifier ( myos/lib/user/syscall.c )
/* 生成管道,pipefd[0]负责读入管道,pipefd[1]负责写入管道 */
int32_t pipe(int32_t pipefd[2])
{
return _syscall1(SYS_PIPE, pipefd);
}
Déclarez la fonction d'appel système en mode utilisateur, modifiez ( myos/lib/user/syscall.h )
int32_t pipe(int32_t pipefd[2]);
Dans la table des appels système, ajoutez la fonction de traitement des appels système réelle et modifiez ( myos/userprog/syscall-init.c )
#include "pipe.h"
/* 初始化系统调用 */
void syscall_init(void)
{
put_str("syscall_init start\n");
syscall_table[SYS_GETPID] = sys_getpid;
syscall_table[SYS_WRITE] = sys_write;
syscall_table[SYS_MALLOC] = sys_malloc;
syscall_table[SYS_FREE] = sys_free;
syscall_table[SYS_FORK] = sys_fork;
syscall_table[SYS_READ] = sys_read;
syscall_table[SYS_PUTCHAR] = sys_putchar;
syscall_table[SYS_CLEAR] = cls_screen;
syscall_table[SYS_GETCWD] = sys_getcwd;
syscall_table[SYS_OPEN] = sys_open;
syscall_table[SYS_CLOSE] = sys_close;
syscall_table[SYS_LSEEK] = sys_lseek;
syscall_table[SYS_UNLINK] = sys_unlink;
syscall_table[SYS_MKDIR] = sys_mkdir;
syscall_table[SYS_OPENDIR] = sys_opendir;
syscall_table[SYS_CLOSEDIR] = sys_closedir;
syscall_table[SYS_CHDIR] = sys_chdir;
syscall_table[SYS_RMDIR] = sys_rmdir;
syscall_table[SYS_READDIR] = sys_readdir;
syscall_table[SYS_REWINDDIR] = sys_rewinddir;
syscall_table[SYS_STAT] = sys_stat;
syscall_table[SYS_PS] = sys_ps;
syscall_table[SYS_EXECV] = sys_execv;
syscall_table[SYS_EXIT] = sys_exit;
syscall_table[SYS_WAIT] = sys_wait;
syscall_table[SYS_PIPE] = sys_pipe;
put_str("syscall_init done\n");
}
sys_close
Ajoutez le code pour fermer le fichier pipeline. Appelez d'abord pour is_pipe
déterminer que la structure de fichier correspondant au descripteur de fichier est un fichier pipeline, puis fd_pos
-1 dans la structure de fichier (ce membre enregistre le nombre de fois où le fichier pipeline est ouvert. Dans le système de fichiers précédent, ce membre a enregistré la position actuelle de l'opération), si fd_pos
elle est 0 à ce moment, alors libérez directement la page de mémoire correspondant au tampon en anneau.
Modifier ( myos/fs/fs.c/sys_close )
#include "pipe.h"
/* 关闭文件描述符fd指向的文件,成功返回0,否则返回-1 */
int32_t sys_close(int32_t fd)
{
int32_t ret = -1; // 返回值默认为-1,即失败
if (fd > 2)
{
uint32_t global_fd = fd_local2global(fd);
if (is_pipe(fd))
{
/* 如果此管道上的描述符都被关闭,释放管道的环形缓冲区 */
if (--file_table[global_fd].fd_pos == 0)
{
mfree_page(PF_KERNEL, file_table[global_fd].fd_inode, 1);
file_table[global_fd].fd_inode = NULL;
}
ret = 0;
}
else
{
ret = file_close(&file_table[global_fd]);
}
running_thread()->fd_table[fd] = -1; // 使该文件描述符位可用
}
return ret;
}
sys_write
Ajoutez du code pour écrire des fichiers tubes et fd == stdout_no
ajoutez un appel pour is_pipe
déterminer si le fichier correspondant au descripteur de fichier est un fichier tubes (la sortie standard peut être redirigée vers un fichier tubes), et si c'est le cas, appelez-le pipe_write
. Ajoutez ensuite fd pour déterminer s'il s'agit d'un fichier tube même s'il ne s'agit pas d'une sortie standard. Si c'est le cas, appelez pipe_write
write.
Modifier ( myos/fs/fs.c/sys_write )
/* 将buf中连续count个字节写入文件描述符fd,成功则返回写入的字节数,失败返回-1 */
int32_t sys_write(int32_t fd, const void *buf, uint32_t count)
{
if (fd < 0)
{
printk("sys_write: fd error\n");
return -1;
}
if (fd == stdout_no)
{
/* 标准输出有可能被重定向为管道缓冲区, 因此要判断 */
if (is_pipe(fd))
{
return pipe_write(fd, buf, count);
}
else
{
char tmp_buf[1024] = {
0};
memcpy(tmp_buf, buf, count);
console_put_str(tmp_buf);
return count;
}
}
else if (is_pipe(fd))
{
/* 若是管道就调用管道的方法 */
return pipe_write(fd, buf, count);
}
else
{
uint32_t _fd = fd_local2global(fd);
struct file *wr_file = &file_table[_fd];
if (wr_file->fd_flag & O_WRONLY || wr_file->fd_flag & O_RDWR)
{
uint32_t bytes_written = file_write(wr_file, buf, count);
return bytes_written;
}
else
{
console_put_str("sys_write: not allowed to write file without flag O_RDWR or O_WRONLY\n");
return -1;
}
}
}
sys_read
Ajoutez du code pour lire les fichiers tuyaux et fd == stdoin_no
ajoutez un appel pour is_pipe
déterminer si le fichier correspondant au descripteur de fichier est un fichier tuyau (l'entrée standard peut être redirigée vers un fichier tuyau), et si c'est le cas, appelez-le pipe_read
. Ajoutez ensuite fd pour déterminer s'il s'agit d'un fichier tube même s'il ne s'agit pas d'une entrée standard. Si c'est le cas, appelez pipe_read
readout.
Modifier ( myos/fs/fs.c/sys_read )
/* 从文件描述符fd指向的文件中读取count个字节到buf,若成功则返回读出的字节数,到文件尾则返回-1 */
int32_t sys_read(int32_t fd, void *buf, uint32_t count)
{
ASSERT(buf != NULL);
int32_t ret = -1;
uint32_t global_fd = 0;
if (fd < 0 || fd == stdout_no || fd == stderr_no)
{
printk("sys_read: fd error\n");
}
else if (fd == stdin_no)
{
/* 标准输入有可能被重定向为管道缓冲区, 因此要判断 */
if (is_pipe(fd))
{
ret = pipe_read(fd, buf, count);
}
else
{
char *buffer = buf;
uint32_t bytes_read = 0;
while (bytes_read < count)
{
*buffer = ioq_getchar(&kbd_buf);
bytes_read++;
buffer++;
}
ret = (bytes_read == 0 ? -1 : (int32_t)bytes_read);
}
}
else if (is_pipe(fd))
{
/* 若是管道就调用管道的方法 */
ret = pipe_read(fd, buf, count);
}
else
{
global_fd = fd_local2global(fd);
ret = file_read(&file_table[global_fd], buf, count);
}
return ret;
}
update_inode_open
Ajoutez du code de traitement pour les fichiers de pipeline, si oui, alors fd_pos
+ 1
Modifier ( myos/userprog/fork.c/update_inode_open )
#include "pipe.h"
/* 更新inode打开数 */
static void update_inode_open_cnts(struct task_struct *thread)
{
int32_t local_fd = 3, global_fd = 0;
while (local_fd < MAX_FILES_OPEN_PER_PROC)
{
global_fd = thread->fd_table[local_fd];
ASSERT(global_fd < MAX_FILE_OPEN);
if (global_fd != -1)
{
if (is_pipe(local_fd))
{
file_table[global_fd].fd_pos++;
}
else
{
file_table[global_fd].fd_inode->i_open_cnts++;
}
}
local_fd++;
}
}
release_prog_resource
Ajoutez du code de traitement pour les ressources de fichiers de tubes ouverts à la fin du programme.Le principe est sys_close
le même que le code ajouté.
Par défaut ( myos/userprog/wait_exit.c/release_prog_resource )
#include "pipe.h"
#include "file.h"
static void release_prog_resource(struct task_struct *release_thread)
{
uint32_t *pgdir_vaddr = release_thread->pgdir;
uint16_t user_pde_nr = 768, pde_idx = 0;
uint32_t pde = 0;
uint32_t *v_pde_ptr = NULL; // v表示var,和函数pde_ptr区分
uint16_t user_pte_nr = 1024, pte_idx = 0;
uint32_t pte = 0;
uint32_t *v_pte_ptr = NULL; // 加个v表示var,和函数pte_ptr区分
uint32_t *first_pte_vaddr_in_pde = NULL; // 用来记录pde中第0个pte的地址
uint32_t pg_phy_addr = 0;
/* 回收页表中用户空间的页框 */
while (pde_idx < user_pde_nr)
{
v_pde_ptr = pgdir_vaddr + pde_idx;
pde = *v_pde_ptr;
if (pde & 0x00000001)
{
// 如果页目录项p位为1,表示该页目录项下可能有页表项
first_pte_vaddr_in_pde = pte_ptr(pde_idx * 0x400000); // 一个页表表示的内存容量是4M,即0x400000
pte_idx = 0;
while (pte_idx < user_pte_nr)
{
v_pte_ptr = first_pte_vaddr_in_pde + pte_idx;
pte = *v_pte_ptr;
if (pte & 0x00000001)
{
/* 将pte中记录的物理页框直接在相应内存池的位图中清0 */
pg_phy_addr = pte & 0xfffff000;
free_a_phy_page(pg_phy_addr);
}
pte_idx++;
}
/* 将pde中记录的物理页框直接在相应内存池的位图中清0 */
pg_phy_addr = pde & 0xfffff000;
free_a_phy_page(pg_phy_addr);
}
pde_idx++;
}
/* 回收用户虚拟地址池所占的物理内存*/
uint32_t bitmap_pg_cnt = (release_thread->userprog_vaddr.vaddr_bitmap.btmp_bytes_len) / PG_SIZE;
uint8_t *user_vaddr_pool_bitmap = release_thread->userprog_vaddr.vaddr_bitmap.bits;
mfree_page(PF_KERNEL, user_vaddr_pool_bitmap, bitmap_pg_cnt);
/* 关闭进程打开的文件 */
uint8_t local_fd = 3;
while (local_fd < MAX_FILES_OPEN_PER_PROC)
{
if (release_thread->fd_table[local_fd] != -1)
{
if (is_pipe(local_fd))
{
uint32_t global_fd = fd_local2global(local_fd);
if (--file_table[global_fd].fd_pos == 0)
{
mfree_page(PF_KERNEL, file_table[global_fd].fd_inode, 1);
file_table[global_fd].fd_inode = NULL;
}
}
else
{
sys_close(local_fd);
}
}
local_fd++;
}
}
Processus utilisateur pour tester le tube ( myos/command/prog_pipe.c )
#include "stdio.h"
#include "syscall.h"
#include "string.h"
#include "stdint.h"
int main(int argc, char **argv)
{
int32_t fd[2] = {
-1};
pipe(fd);
int32_t pid = fork();
if (pid)
{
// 父进程
close(fd[0]); // 关闭输入
write(fd[1], "Hi, my son, I love you!", 24);
printf("\nI`m father, my pid is %d\n", getpid());
return 8;
}
else
{
close(fd[1]); // 关闭输出
char buf[32] = {
0};
read(fd[0], buf, 24);
printf("\nI`m child, my pid is %d\n", getpid());
printf("I`m child, my father said to me: \"%s\"\n", buf);
return 9;
}
}
Script pour gérer prog_pipe
#### 此脚本应该在command目录下执行
if [[ ! -d "../lib" || ! -d "../build" ]];then
echo "dependent dir don\`t exist!"
cwd=$(pwd)
cwd=${
cwd##*/}
cwd=${
cwd%/}
if [[ $cwd != "command" ]];then
echo -e "you\`d better in command dir\n"
fi
exit
fi
CC="gcc-4.4"
BIN="prog_pipe"
CFLAGS="-Wall -c -fno-builtin -W -Wstrict-prototypes \
-Wmissing-prototypes -Wsystem-headers -m32 -fno-stack-protector"
LIBS="-I ../lib/ -I ../lib/kernel/ -I ../lib/user/ -I \
../kernel/ -I ../device/ -I ../thread/ -I \
../userprog/ -I ../fs/ -I ../shell/"
OBJS="../build/string.o ../build/syscall.o \
../build/stdio.o ../build/assert.o start.o"
DD_IN=$BIN
DD_OUT="/home/rlk/Desktop/bochs/hd60M.img"
nasm -f elf ./start.S -o ./start.o
ar rcs simple_crt.a $OBJS start.o
$CC $CFLAGS $LIBS -o $BIN".o" $BIN".c"
ld $BIN".o" simple_crt.a -o $BIN -m elf_i386
SEC_CNT=$(ls -l $BIN|awk '{printf("%d", ($5+511)/512)}')
if [[ -f $BIN ]];then
dd if=./$DD_IN of=$DD_OUT bs=512 \
count=$SEC_CNT seek=300 conv=notrunc
fi
Code de test pour charger prog_pipe de hd60M.img dans hd80M.img
#include "print.h"
#include "init.h"
#include "fork.h"
#include "stdio.h"
#include "syscall.h"
#include "assert.h"
#include "shell.h"
#include "console.h"
#include "ide.h"
#include "stdio-kernel.h"
void init(void);
int main(void)
{
put_str("I am kernel\n");
init_all();
uint32_t file_size = 21432;
uint32_t sec_cnt = DIV_ROUND_UP(file_size, 512);
struct disk *sda = &channels[0].devices[0];
void *prog_buf = sys_malloc(file_size);
ide_read(sda, 300, prog_buf, sec_cnt);
int32_t fd = sys_open("/prog_pipe", O_CREAT | O_RDWR);
if (fd != -1)
{
if (sys_write(fd, prog_buf, file_size) == -1)
{
printk("file write error!\n");
while (1)
;
}
}
cls_screen();
console_put_str("[rabbit@localhost /]$ ");
thread_exit(running_thread(), true);
return 0;
}
/* init进程 */
void init(void)
{
uint32_t ret_pid = fork();
if (ret_pid)
{
// 父进程
int status;
int child_pid;
/* init在此处不停的回收僵尸进程 */
while (1)
{
child_pid = wait(&status);
printf("I`m init, My pid is 1, I recieve a child, It`s pid is %d, status is %d\n", child_pid, status);
}
}
else
{
// 子进程
my_shell();
}
panic("init: should not be here");
}
Section k :
Tubes de support en coque
D'une manière générale, le clavier sert de source d'entrée du programme, tandis que l'écran est la destination de sortie du programme, appelée entrée et sortie standard. Cependant, un programme peut également recevoir des entrées d'un fichier ou envoyer sa sortie vers un fichier, une méthode connue sous le nom d'entrée et de sortie non standard. Lorsque nous voulons passer d’une entrée et d’une sortie standard à une entrée et une sortie de fichier, nous utilisons la redirection d’entrée et de sortie. De cette façon, nous pouvons utiliser la sortie d’une commande comme entrée d’une autre commande, ce qui est exactement ce que font les tuyaux. Sous Linux, cette opération est généralement effectuée via le caractère pipe de ligne de commande "|". Par exemple, dans une commande ls | grep kanshan
, ls
la commande répertorie tous les fichiers du répertoire courant et les afficherait à l'origine à l'écran, mais en raison de la présence du caractère barre verticale |
, sa sortie sera redirigée vers l'entrée grep
de la commande à l'aide d'une barre verticale. .
sys_fd_redirect
Sa fonction est de rediriger un descripteur de fichier existant old_local_fd
vers un autre descripteur de fichier new_local_fd
. L'utilisation réelle est la suivante : fd_redirect(1,fd[1]);
(fd[1] est le descripteur de fichier correspondant au fichier pipeline, et son index de structure de fichier global doit être supérieur à 2) utilisé pour déplacer l'entrée standard vers le fichier pipeline (combiné avec la compréhension) ; sys_write
utilisé fd_redirect(1,1);
pour restaurer le résultat standard (combiné à sys_write
la compréhension)
Modifier ( myos/shell/pipe.c )
/* 将文件描述符old_local_fd重定向为new_local_fd */
void sys_fd_redirect(uint32_t old_local_fd, uint32_t new_local_fd)
{
struct task_struct *cur = running_thread();
/* 针对恢复标准描述符 */
if (new_local_fd < 3)
{
cur->fd_table[old_local_fd] = new_local_fd;
}
else
{
uint32_t new_global_fd = cur->fd_table[new_local_fd];
cur->fd_table[old_local_fd] = new_global_fd;
}
}
Déclaration de fonction, modification ( myos/shell/pipe.h )
void sys_fd_redirect(uint32_t old_local_fd, uint32_t new_local_fd);
sys_help
Commandes internes prises en charge par le système d'impression
Modifier ( myos/fs/fs.c )
/* 显示系统支持的内部命令 */
void sys_help(void)
{
printk("\
buildin commands:\n\
ls: show directory or file information\n\
cd: change current work directory\n\
mkdir: create a directory\n\
rmdir: remove a empty directory\n\
rm: remove a regular file\n\
pwd: show current work directory\n\
ps: show process information\n\
clear: clear screen\n\
shortcut key:\n\
ctrl+l: clear screen\n\
ctrl+u: clear input\n\n");
}
Ajouter une déclaration, modifier ( myos/fs/fs.h )
void sys_help(void);
Faire sys_fd_redirect
du et sys_help
un appel système
Ajouter le numéro d'appel système et modifier ( myos/lib/user/syscall.h )
enum SYSCALL_NR
{
SYS_GETPID,
SYS_WRITE,
SYS_MALLOC,
SYS_FREE,
SYS_FORK,
SYS_READ,
SYS_PUTCHAR,
SYS_CLEAR,
SYS_GETCWD,
SYS_OPEN,
SYS_CLOSE,
SYS_LSEEK,
SYS_UNLINK,
SYS_MKDIR,
SYS_OPENDIR,
SYS_CLOSEDIR,
SYS_CHDIR,
SYS_RMDIR,
SYS_READDIR,
SYS_REWINDDIR,
SYS_STAT,
SYS_PS,
SYS_EXECV,
SYS_EXIT,
SYS_WAIT,
SYS_PIPE,
SYS_FD_REDIRECT,
SYS_HELP
};
Implémenter l'entrée et la modification des appels système en mode utilisateur ( myos/lib/user/syscall.c )
/* 将文件描述符old_local_fd重定向到new_local_fd */
void fd_redirect(uint32_t old_local_fd, uint32_t new_local_fd)
{
_syscall2(SYS_FD_REDIRECT, old_local_fd, new_local_fd);
}
/* 显示系统支持的命令 */
void help(void)
{
_syscall0(SYS_HELP);
}
Déclarez la fonction d'appel système en mode utilisateur, modifiez ( myos/lib/user/syscall.h )
void fd_redirect(uint32_t old_local_fd, uint32_t new_local_fd);
void help(void);
Ajoutez la fonction de traitement des appels système réelle à la table des appels système et modifiez-la ( myos/userprog/syscall-init.c )
/* 初始化系统调用 */
void syscall_init(void)
{
put_str("syscall_init start\n");
syscall_table[SYS_GETPID] = sys_getpid;
syscall_table[SYS_WRITE] = sys_write;
syscall_table[SYS_MALLOC] = sys_malloc;
syscall_table[SYS_FREE] = sys_free;
syscall_table[SYS_FORK] = sys_fork;
syscall_table[SYS_READ] = sys_read;
syscall_table[SYS_PUTCHAR] = sys_putchar;
syscall_table[SYS_CLEAR] = cls_screen;
syscall_table[SYS_GETCWD] = sys_getcwd;
syscall_table[SYS_OPEN] = sys_open;
syscall_table[SYS_CLOSE] = sys_close;
syscall_table[SYS_LSEEK] = sys_lseek;
syscall_table[SYS_UNLINK] = sys_unlink;
syscall_table[SYS_MKDIR] = sys_mkdir;
syscall_table[SYS_OPENDIR] = sys_opendir;
syscall_table[SYS_CLOSEDIR] = sys_closedir;
syscall_table[SYS_CHDIR] = sys_chdir;
syscall_table[SYS_RMDIR] = sys_rmdir;
syscall_table[SYS_READDIR] = sys_readdir;
syscall_table[SYS_REWINDDIR] = sys_rewinddir;
syscall_table[SYS_STAT] = sys_stat;
syscall_table[SYS_PS] = sys_ps;
syscall_table[SYS_EXECV] = sys_execv;
syscall_table[SYS_EXIT] = sys_exit;
syscall_table[SYS_WAIT] = sys_wait;
syscall_table[SYS_PIPE] = sys_pipe;
syscall_table[SYS_FD_REDIRECT] = sys_fd_redirect;
syscall_table[SYS_HELP] = sys_help;
put_str("syscall_init done\n");
}
Sera help
encapsulé dans une commande intégrée et modifié ( myos/shell/buildin_cmd.c )
/* 显示内建命令列表 */
void buildin_help(uint32_t argc UNUSED, char **argv UNUSED)
{
help();
}
Déclaration, modification ( myos/shell/buildin_cmd.h )
void buildin_help(uint32_t argc UNUSED, char **argv UNUSED);
cmd_execute
Pour remplacer la fonction d'exécution de commandes internes et externes dans le shell d'origine
Modifier ( myos/shell/shell.c )
/* 执行命令 */
static void cmd_execute(uint32_t argc, char **argv)
{
if (!strcmp("ls", argv[0]))
{
buildin_ls(argc, argv);
}
else if (!strcmp("cd", argv[0]))
{
if (buildin_cd(argc, argv) != NULL)
{
memset(cwd_cache, 0, MAX_PATH_LEN);
strcpy(cwd_cache, final_path);
}
}
else if (!strcmp("pwd", argv[0]))
{
buildin_pwd(argc, argv);
}
else if (!strcmp("ps", argv[0]))
{
buildin_ps(argc, argv);
}
else if (!strcmp("clear", argv[0]))
{
buildin_clear(argc, argv);
}
else if (!strcmp("mkdir", argv[0]))
{
buildin_mkdir(argc, argv);
}
else if (!strcmp("rmdir", argv[0]))
{
buildin_rmdir(argc, argv);
}
else if (!strcmp("rm", argv[0]))
{
buildin_rm(argc, argv);
}
else if (!strcmp("help", argv[0]))
{
buildin_help(argc, argv);
}
else
{
// 如果是外部命令,需要从磁盘上加载
int32_t pid = fork();
if (pid)
{
// 父进程
int32_t status;
int32_t child_pid = wait(&status); // 此时子进程若没有执行exit,my_shell会被阻塞,不再响应键入的命令
if (child_pid == -1)
{
// 按理说程序正确的话不会执行到这句,fork出的进程便是shell子进程
panic("my_shell: no child\n");
}
printf("child_pid %d, it's status: %d\n", child_pid, status);
}
else
{
// 子进程
make_clear_abs_path(argv[0], final_path);
argv[0] = final_path;
/* 先判断下文件是否存在 */
struct stat file_stat;
memset(&file_stat, 0, sizeof(struct stat));
if (stat(argv[0], &file_stat) == -1)
{
printf("my_shell: cannot access %s: No such file or directory\n", argv[0]);
exit(-1);
}
else
{
execv(argv[0], argv);
}
}
}
}
Nouveau my_shell
, la fonction principale est d'obtenir une entrée de ligne de commande de l'utilisateur, d'analyser et d'exécuter la commande, en particulier |
la fonction de prise en charge des commandes de pipeline.
Nouvelle partie majeure : vérifiez si l'entrée utilisateur contient un symbole de tuyau|
- S'il existe une commande pipeline :
- Créez un pipeline.
- Redirigez la sortie standard vers l’extrémité d’écriture du canal.
- Analysez et exécutez la première commande.
- Redirigez l’entrée standard vers l’extrémité de lecture du canal.
- Pour chaque commande intermédiaire (sauf la dernière) :
- Analysez et exécutez la commande.
- Restaurez la sortie standard à l’écran.
- Exécutez la dernière commande du pipeline.
- Restaurez la saisie standard sur le clavier.
- Fermez le tuyau.
- Sans commande pipe :
- Analyser les commandes saisies par l'utilisateur.
- Si le nombre de paramètres dépasse la valeur maximale définie, une erreur sera affichée.
- Sinon, exécutez la commande.
Modifier ( myos/shell/shell.c )
#include "pipe.h"
void my_shell(void)
{
cwd_cache[0] = '/';
while (1)
{
print_prompt();
memset(final_path, 0, MAX_PATH_LEN);
memset(cmd_line, 0, MAX_PATH_LEN);
readline(cmd_line, MAX_PATH_LEN);
if (cmd_line[0] == 0)
{
// 若只键入了一个回车
continue;
}
/* 针对管道的处理 */
char *pipe_symbol = strchr(cmd_line, '|');
if (pipe_symbol)
{
/* 支持多重管道操作,如cmd1|cmd2|..|cmdn,
* cmd1的标准输出和cmdn的标准输入需要单独处理 */
/*1 生成管道*/
int32_t fd[2] = {
-1}; // fd[0]用于输入,fd[1]用于输出
pipe(fd);
/* 将标准输出重定向到fd[1],使后面的输出信息重定向到内核环形缓冲区 */
fd_redirect(1, fd[1]);
/*2 第一个命令 */
char *each_cmd = cmd_line;
pipe_symbol = strchr(each_cmd, '|');
*pipe_symbol = 0;
/* 执行第一个命令,命令的输出会写入环形缓冲区 */
argc = -1;
argc = cmd_parse(each_cmd, argv, ' ');
cmd_execute(argc, argv);
/* 跨过'|',处理下一个命令 */
each_cmd = pipe_symbol + 1;
/* 将标准输入重定向到fd[0],使之指向内核环形缓冲区*/
fd_redirect(0, fd[0]);
/*3 中间的命令,命令的输入和输出都是指向环形缓冲区 */
while ((pipe_symbol = strchr(each_cmd, '|')))
{
*pipe_symbol = 0;
argc = -1;
argc = cmd_parse(each_cmd, argv, ' ');
cmd_execute(argc, argv);
each_cmd = pipe_symbol + 1;
}
/*4 处理管道中最后一个命令 */
/* 将标准输出恢复屏幕 */
fd_redirect(1, 1);
/* 执行最后一个命令 */
argc = -1;
argc = cmd_parse(each_cmd, argv, ' ');
cmd_execute(argc, argv);
/*5 将标准输入恢复为键盘 */
fd_redirect(0, 0);
/*6 关闭管道 */
close(fd[0]);
close(fd[1]);
}
else
{
// 一般无管道操作的命令
argc = -1;
argc = cmd_parse(cmd_line, argv, ' ');
if (argc == -1)
{
printf("num of arguments exceed %d\n", MAX_ARG_NR);
continue;
}
cmd_execute(argc, argv);
}
}
panic("my_shell: should not be here");
}
cat
Ajout de la possibilité d'obtenir une entrée du clavier lorsqu'il n'y a pas de paramètres (n'oubliez pas de supprimer le chat dans le hd80M.img d'origine)
(myos/command/cat.c)
#include "syscall.h"
#include "stdio.h"
#include "string.h"
#include "fs.h"
int main(int argc, char **argv)
{
if (argc > 2)
{
printf("cat: argument error\n");
exit(-2);
}
if (argc == 1)
{
char buf[512] = {
0};
read(0, buf, 512);
printf("%s", buf);
exit(0);
}
int buf_size = 1024;
char abs_path[512] = {
0};
void *buf = malloc(buf_size);
if (buf == NULL)
{
printf("cat: malloc memory failed\n");
return -1;
}
if (argv[1][0] != '/')
{
getcwd(abs_path, 512);
strcat(abs_path, "/");
strcat(abs_path, argv[1]);
}
else
{
strcpy(abs_path, argv[1]);
}
int fd = open(abs_path, O_RDONLY);
if (fd == -1)
{
printf("cat: open: open %s failed\n", argv[1]);
return -1;
}
int read_bytes = 0;
while (1)
{
read_bytes = read(fd, buf, buf_size);
if (read_bytes == -1)
{
break;
}
write(1, buf, read_bytes);
}
free(buf);
close(fd);
return 66;
}
Script qui gère le chat
#### 此脚本应该在command目录下执行
if [[ ! -d "../lib" || ! -d "../build" ]];then
echo "dependent dir don\`t exist!"
cwd=$(pwd)
cwd=${cwd##*/}
cwd=${cwd%/}
if [[ $cwd != "command" ]];then
echo -e "you\`d better in command dir\n"
fi
exit
fi
CC="gcc-4.4"
BIN="cat"
CFLAGS="-Wall -c -fno-builtin -W -Wstrict-prototypes \
-Wmissing-prototypes -Wsystem-headers -m32 -fno-stack-protector"
LIBS="-I ../lib/ -I ../lib/kernel/ -I ../lib/user/ -I \
../kernel/ -I ../device/ -I ../thread/ -I \
../userprog/ -I ../fs/ -I ../shell/"
OBJS="../build/string.o ../build/syscall.o \
../build/stdio.o ../build/assert.o start.o"
DD_IN=$BIN
DD_OUT="/home/rlk/Desktop/bochs/hd60M.img"
nasm -f elf ./start.S -o ./start.o
ar rcs simple_crt.a $OBJS start.o
$CC $CFLAGS $LIBS -o $BIN".o" $BIN".c"
ld $BIN".o" simple_crt.a -o $BIN -m elf_i386
SEC_CNT=$(ls -l $BIN|awk '{
printf("%d", ($5+511)/512)}')
if [[ -f $BIN ]];then
dd if=./$DD_IN of=$DD_OUT bs=512 \
count=$SEC_CNT seek=300 conv=notrunc
fi
Code de test, ( myos/kernel/main.c )
#include "print.h"
#include "init.h"
#include "fork.h"
#include "stdio.h"
#include "syscall.h"
#include "assert.h"
#include "shell.h"
#include "console.h"
#include "ide.h"
#include "stdio-kernel.h"
void init(void);
int main(void)
{
put_str("I am kernel\n");
init_all();
uint32_t file_size = 21816;
uint32_t sec_cnt = DIV_ROUND_UP(file_size, 512);
struct disk *sda = &channels[0].devices[0];
void *prog_buf = sys_malloc(file_size);
ide_read(sda, 300, prog_buf, sec_cnt);
int32_t fd = sys_open("/cat", O_CREAT | O_RDWR);
if (fd != -1)
{
if (sys_write(fd, prog_buf, file_size) == -1)
{
printk("file write error!\n");
while (1)
;
}
}
cls_screen();
console_put_str("[rabbit@localhost /]$ ");
thread_exit(running_thread(), true);
return 0;
}
/* init进程 */
void init(void)
{
uint32_t ret_pid = fork();
if (ret_pid)
{
// 父进程
int status;
int child_pid;
/* init在此处不停的回收僵尸进程 */
while (1)
{
child_pid = wait(&status);
printf("I`m init, My pid is 1, I recieve a child, It`s pid is %d, status is %d\n", child_pid, status);
}
}
else
{
// 子进程
my_shell();
}
panic("init: should not be here");
}
Une erreur s'est produite pendant le fonctionnement et doit être modifiée après le dépannage ( myos/device/ioqueue.h )
#define bufsize 64 //定义缓冲区大小.
pour
#define bufsize 2048 //定义缓冲区大小.