"Sistema operativo" por Li Zhijun | Experimento 6 - Implementación y aplicación de semáforo

15407395:

Tabla de contenido

1. Propósito del experimento

2. Contenido experimental

(1) Usar semáforos para resolver el problema productor-consumidor

(2) Realice el semáforo y use el programa productor-consumidor para verificar

3. Preparación del experimento

1, cantidad de señal

2. Archivos compartidos multiproceso

3. La terminal también es un recurso crítico

4. Operaciones atómicas, dormir y despertar

4. Proceso experimental

(1) Escribir un programa de inspección productor-consumidor

1. Escribe pc.c

2. Montaje pc.c

(2) Realizar semáforo

1. Agregar API de llamada al sistema

2. Nueva sem.h

3. Nuevo sem.c

4. Modificar unistd.h

5. Modificar system_call.s

6. Modificar sys.h

7. Modificar el Makefile

8. Monte el archivo

9. Recompilar

(3) Ejecutar el programa productor-consumidor

1. Compile y ejecute pc.c

2. Ver salida

3. Salida del resultado


1. Propósito del experimento

1. Profundizar en la comprensión de los conceptos de sincronización de procesos y exclusión mutua.

2. Dominar el uso del semáforo y aplicarlo para resolver el problema productor-consumidor.

3. Domina el principio de realización del semáforo.

2. Contenido experimental

(1) Usar semáforos para resolver el problema productor-consumidor

Escriba aplicaciones en Ubuntu , resuelva el clásico problema productor-consumidor y realice las siguientes funciones: pc.c

  • Crear un proceso productor y N procesos consumidores (N > 1)
  • Crear un búfer compartido con un archivo
  • El proceso productor escribe secuencialmente los números enteros 0, 1, 2, ..., M en el búfer compartido (M >= 500)
  • El proceso del consumidor lee del búfer compartido, uno a la vez, y elimina los números leídos del búfer, y luego genera "este ID de proceso + número eliminado" a la salida estándar
  • El búfer solo puede guardar hasta 10 números al mismo tiempo

[Ejemplo] Un posible efecto de salida

10: 0
10: 1
10: 2
10: 3
10: 4
11: 5
11: 6
12: 7
10: 8
12: 9
12: 10
12: 11
12: 12
……
11: 498
11: 499
  • El orden de la identificación del proceso puede cambiar mucho, pero el número después de los dos puntos debe comenzar desde 0 y aumentar en 1.

pc.c Además       , las llamadas al sistema relacionadas con semáforos como ,  y  se utilizarán en , que debemos implementar en Linux 0.11 por nosotros mismos. sem_open()sem_close()sem_wait()sem_post()

(2) Realice el semáforo y use el programa productor-consumidor para verificar

       La versión 0.11 de Linux aún no implementa semáforos, Linus le deja este desafiante trabajo a usted. Si puede realizar una copia del semáforo que cumpla completamente con la especificación POSIX (interfaz de sistema operativo portátil de UNIX), sin duda tendrá una sensación de logro. Pero el tiempo no nos permite hacerlo por el momento, por lo que primero podemos implementar un conjunto de versión reducida de semáforos tipo POSIX.Su prototipo de función no es exactamente el mismo que el estándar, y solo incluye los siguientes cuatro sistemas llamadas:

sem_t *sem_open(const char *name, unsigned int value);
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_unlink(const char *name);

 

Las funciones específicas y los parámetros relacionados de las cuatro funciones anteriores se explican a continuación:

  • sem_open()
Función Cree un semáforo o abra un semáforo existente.
parámetro

sem_t: El tipo de semáforo, personalizado según las necesidades de la implementación.

name: El nombre del semáforo. Si el semáforo no existe, cree un nuevo semáforo name llamado ; si el semáforo existe, abra el namesemáforo existente llamado . Diferentes procesos pueden compartir el mismo semáforo nameproporcionando semáforo.

value: El valor inicial del semáforo, este parámetro es válido solo al crear un nuevo semáforo, de lo contrario value se ignora.

valor devuelto Cuando la creación o apertura es exitosa, el valor de retorno es el identificador único del semáforo (como: la dirección en el kernel, ID, etc.), que es utilizado por las otras dos llamadas al sistema; cuando falla, el retorno el valor es NULL. sem_open()

 

  • sem_wait()
Función

Es la operación atómica P del semáforo, y su función es restar 1 al valor del semáforo. Si no se cumplen las condiciones para continuar la ejecución, el proceso de llamada se hace esperar en el semáforo sem.

parámetro sem : Puntero al semáforo.
valor devuelto Devuelve 0 para el éxito y -1 para el fracaso.

 

  • sem_post()
Función Es la operación atómica V del semáforo, y su función es sumar 1 al valor del semáforo. Si hay procesos esperando en sem, despierta uno de ellos.
parámetro sem : Puntero al semáforo.
valor devuelto Devuelve 0 para el éxito y -1 para el fracaso.

 

  • sem_unlink()
Función Elimina el semáforo name llamado .
parámetro name: El nombre del semáforo.
valor devuelto Devuelve 0 para el éxito y -1 para el fracaso.

【Consejos para experimentos】

Podemos crear un nuevo archivo        en kernel el directorio para realizar las funciones de las cuatro llamadas al sistema anteriores. sem.c Luego será portado pc.cdesde Ubuntu para ejecutarse bajo Linux 0.11 para probar el semáforo implementado.

 

 

3. Preparación del experimento

1, cantidad de señal

       Semaphore, semáforo en inglés, fue diseñado por primera vez por el científico holandés y ganador del Premio Turing EW Dijkstra. La parte de "sincronización de procesos" de cualquier libro de texto de sistema operativo se describirá en detalle. El semáforo garantiza que la cooperación de múltiples procesos sea razonable y ordenada.

       El semáforo de Linux se adhiere a la especificación POSIX y los usuarios  man sem_overview  pueden ver información relacionada.

       Las llamadas al sistema relacionadas con semáforos involucradas en este experimento incluyen: y .sem_open()sem_wait()sem_post() sem_unlink()

problema productor-consumidor

La solución al problema productor-consumidor se encuentra en casi todos los libros de texto de sistemas operativos, y su estructura básica es:

Producer()
{
    // 生产一个产品 item;

    /* 空闲缓存资源 */
    P(Empty);

    /* 互斥信号量 */
    P(Mutex);

    // 将item放到空闲缓存中;

    V(Mutex);

    /* 产品资源 */
    V(Full);
}

Consumer()
{
    P(Full);
    P(Mutex);

    //从缓存区取出一个赋值给item;

    V(Mutex);

    // 消费产品item;
    V(Empty);
}
  • Obviamente, al demostrar este proceso, se deben crear dos tipos de procesos, un tipo ejecuta la función y el otro tipo ejecuta la funciónProducer() Consumer()

2. Archivos compartidos multiproceso

Al usar el lenguaje C en Linux, puede usar los siguientes tres métodos para leer y escribir archivos (pero solo los primeros dos métodos se pueden usar en Linux 0.11):

(1) Utilice las C estándar , , , etc.fopen()fread()fwrite()fseek() fclose()

(2) Usar llamadas al   sistema , , ,  etc. open()read()write()lseek()close()

(3) A través del archivo de imagen de memoria, utilice  la llamada al sistema.mmap() 

       fork() Después de que la llamada sea exitosa, el proceso secundario creado heredará la mayoría de los recursos que posee el proceso principal, incluidos los archivos abiertos por el proceso principal. Por lo tanto, el proceso secundario puede usar directamente el puntero/descriptor/identificador de archivo creado por el proceso principal y acceder al mismo archivo que el proceso principal.

       Cuando se utilizan funciones de operación de archivo C estándar, se debe tener en cuenta que utilizan el búfer de archivo en el espacio del proceso , y el búfer no se comparte entre el proceso principal y el proceso secundario. Por lo tanto, después de que cualquier proceso complete la operación de escritura, debe  forzar la actualización de los datos en el disco, para que otros procesos puedan leer los datos requeridos. fflush()

       En resumen, se recomienda utilizar las llamadas al sistema directamente para las operaciones con archivos.

3. La terminal también es un recurso crítico

       Es natural usarlo para enviar información printf()al terminal, pero cuando varios procesos envían al mismo tiempo, el terminal también se convierte en un recurso crítico, por lo que también se requiere protección de exclusión mutua, de lo contrario, la información de salida puede estar desordenada.

       Además, printf()después de eso , la información solo se guarda en el búfer de salida y no se ha enviado realmente a la salida estándar (generalmente la consola del terminal), lo que también puede causar que la sincronización de la información de salida sea inconsistente. Así que printf() después de stdio.h llamada fflush(stdout), para asegurarse de que los datos se envían a la terminal.

4. Operaciones atómicas, dormir y despertar

       Linux 0.11 es un sistema operativo moderno que soporta concurrencia, aunque no ha implementado bloqueos ni semáforos para las aplicaciones, debe usar un mecanismo de bloqueo interno , es decir, cuando múltiples procesos acceden a datos compartidos del kernel, deben implementarse a través de bloqueos. y sincronización.

       El bloqueo debe ser una operación atómica (una operación que no será interrumpida por el mecanismo de programación. Una vez que esta operación se inicia, se ejecutará hasta que finalice). Los semáforos se pueden implementar emulando los bloqueos de Linux 0.11.

       Por ejemplo, el acceso simultáneo al disco por varios procesos es un lugar donde se necesitan bloqueos. El método de procesamiento básico para que Linux 0.11 acceda al disco es reservar una sección de caché de disco en la memoria para acelerar el acceso al disco. La solicitud de acceso al disco realizada por el proceso primero debe buscarse en el caché del disco y, si se encuentra, se devolverá directamente; si no se encuentra, solicitará un caché de disco libre e iniciará una solicitud de lectura y escritura del disco con esto. caché de disco como parámetro. Después de que se envía la solicitud, el proceso tiene que dormir y esperar (debido a que la lectura y escritura del disco es muy lenta, en este momento la CPU debe entregarse a otros procesos para su ejecución). Este enfoque es el más general adoptado por muchos sistemas operativos (incluidos los modernos Linux, UNIX, etc.). Esto implica que varios procesos operen el caché de disco juntos, y el proceso puede programarse durante la operación y perder la CPU. Por lo tanto, se deben considerar los problemas de exclusión mutua al operar el caché de disco, por lo que se deben usar bloqueos y el proceso también debe estar acostumbrado a dormir y despertar.

[Ejemplo] Las siguientes son dos funciones tomadas del archivo: kernel/blk_drv/ll_rw_blk.c

static inline void lock_buffer(struct buffer_head * bh)
{
    // 关中断
    cli();

    // 将当前进程睡眠在 bh->b_wait
    while (bh->b_lock)
        sleep_on(&bh->b_wait);
    bh->b_lock = 1;

    // 开中断
    sti();
}

static inline void unlock_buffer(struct buffer_head * bh)
{
    if (!bh->b_lock)
        printk("ll_rw_block.c: buffer not locked\n\r");
    bh->b_lock = 0;

    // 唤醒睡眠在 bh->b_wait 上的进程
    wake_up(&bh->b_wait);
}

lock_buffer()Se puede       ver a partir del análisis que al acceder a la variable de bloqueo b_lock , la operación atómica se realiza activando y desactivando la interrupción para evitar que ocurra un cambio de proceso. Por supuesto, este método también tiene desventajas y no es adecuado para su uso en un entorno multiprocesador, pero para Linux 0.11, es un mecanismo simple, directo y efectivo. Porque el Linux 0.11 simulado por bochs en nuestro experimento es un sistema de una sola CPU.

       Además, la función anterior muestra que Linux 0.11 proporciona una interfaz de este tipo: utilícela para  sleep_on() realizar la suspensión del proceso y utilícela  wake_up() para realizar la reactivación del proceso. Su parámetro es un puntero de estructura  struct task_struct *(es decir, el PCB del proceso, definido en sched.h), es decir, el proceso duerme o se despierta en una lista enlazada de estructura de PCB de proceso a la que apunta este parámetro.

       Por lo tanto, en este experimento, también podemos implementar operaciones atómicas al cambiar interrupciones y realizar sleep_on() procesos suspensión y activación mediante llamadas y eso viene con Linux 0.11. wake_up()

【Nota】

  • sleep_on() La función es dormir el proceso actual en la lista vinculada especificada por el parámetro (tenga en cuenta que esta lista vinculada es una lista vinculada muy oculta, consulte "Notas" para obtener más detalles)
  • wake_up() La función de es despertar todos los procesos durmientes en la lista enlazada . Estos procesos se programarán para ejecutarse, por lo que después de que se activen, deben volver a evaluar si pueden continuar ejecutándose. Consulte el ciclo while en lock_buffer() 

 

 

4. Proceso experimental

       En general, el contenido básico de este experimento es realizar el semáforo en el kernel de Linux 0.11 y proporcionar al usuario una interfaz que usa el semáforo, y el usuario usa esta interfaz para resolver un problema real de sincronización de procesos.

(1) Escribir un programa de inspección productor-consumidor

1. Escribe pc.c

oslab/exp_06Crear un nuevo directorio bajopc.c

【pc.c】

#define __LIBRARY__
#include <unistd.h>
#include <linux/sem.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/sched.h>

/* 添加系统调用API */
_syscall2(sem_t *,sem_open,const char *,name,unsigned int,value)
_syscall1(int,sem_wait,sem_t *,sem)
_syscall1(int,sem_post,sem_t *,sem)
_syscall1(int,sem_unlink,const char *,name)

const char *FILENAME = "/usr/root/buffer_file";  /* 消费or生产的产品存放的缓冲文件的路径 */
const int NR_CONSUMERS = 5;                      /* 消费者数量 */
const int NR_ITEMS = 520;                        /* 产品最大量 */
const int BUFFER_SIZE = 10;                      /* 缓冲区大小,表示可同时存在的产品数量 */
sem_t *mutex, *full, *empty;                     /* 3个信号量 */
unsigned int item_pro, item_used;                /* 刚生产的产品号,刚消费的产品号 */
int fi, fo;                                      /* 供生产者写入或消费者读取的缓冲文件的句柄 */

int main(int argc, char *argv[])
{
    char *filename;
    int pid;
    int i;

    filename = argc > 1 ? argv[1] : FILENAME;

    /* 
     * O_TRUNC 表示:当文件以只读或只写打开时,若文件存在,则将其长度截为0(即清空文件)
     * 0222 表示:文件只写(前面的0是八进制标识)
     * 0444 表示:文件只读
    */

    /* 以只写方式打开文件给生产者写入产品编号 */
    fi = open(filename, O_CREAT| O_TRUNC| O_WRONLY, 0222);
    /* 以只读方式打开文件给消费者读出产品编号 */
    fo = open(filename, O_TRUNC| O_RDONLY, 0444);

    mutex = sem_open("MUTEX", 1);    /* 互斥信号量,防止生产和消费同时进行 */
    full = sem_open("FULL", 0);      /* 产品剩余信号量,大于0则可消费 */
    empty = sem_open("EMPTY", BUFFER_SIZE);    /* 空信号量,它与产品剩余信号量此消彼长,大于0时生产者才能继续生产 */

    item_pro = 0;

    if ( (pid = fork()) )    /* 父进程用来执行生产者动作 */
    {
        printf("pid %d:\tproducer created....\n", pid);

        /* 
         * printf输出的信息不会马上输出到标准输出(通常为终端控制台),而是先保存到输出缓冲区。
         * 为避免偶然因素的影响造成输出信息时序不一致,
         * 每次printf()后都调用一下 stdio.h 中的 fflush(stdout),
         * 来确保将输出内容立刻输出到标准输出。 
        */

        fflush(stdout);

        while (item_pro <= NR_ITEMS)    /* 生产完所需产品 */
        {
            sem_wait(empty);  /* P(empty) */
            sem_wait(mutex);  /* P(mutex) */

            /* 
             * 生产完一轮产品(文件缓冲区只能容纳 BUFFER_SIZE 个产品编号)后,
             * 将缓冲文件的位置指针重新定位到文件首部。
            */
            if( !(item_pro % BUFFER_SIZE) )  /* item_pro = 10 */
                lseek(fi, 0, 0);

            write(fi, (char *) &item_pro, sizeof(item_pro));  /* 写入产品编号 */ 
            printf("pid %d:\tproduces item %d\n", pid, item_pro);
            fflush(stdout);
            item_pro++;

            sem_post(full);        /* 唤醒消费者进程 */
            sem_post(mutex);
        }
    }
    else    /* 子进程来创建消费者 */
    {
        i = NR_CONSUMERS;
        while(i--)
        {
            if( !(pid=fork()) )    /* 创建i个消费者进程 */
            {
                pid = getpid();
                printf("pid %d:\tconsumer %d created....\n", pid, NR_CONSUMERS-i);
                fflush(stdout);

                while(1)
                {
                    sem_wait(full);
                    sem_wait(mutex);

                    /* read()读到文件末尾时返回0,将文件的位置指针重新定位到文件首部 */
                    if(!read(fo, (char *)&item_used, sizeof(item_used)))
                    {
                        lseek(fo, 0, 0);
                        read(fo, (char *)&item_used, sizeof(item_used));
                    }

                    printf("pid %d:\tconsumer %d consumes item %d\n", pid, NR_CONSUMERS-i+1, item_used);
                    fflush(stdout);

                    sem_post(empty);    /* 唤醒生产者进程 */
                    sem_post(mutex);

                    if(item_used == NR_ITEMS)    /* 如果已经消费完最后一个商品,则结束 */
                        goto OK;
                }
            }
        }
    }
OK:
    close(fi);
    close(fo);
    return 0;
}

2. Montaje pc.c

pc.c Copie en el directorio Linux 0.11 de la máquina virtual./usr/root/

// oslab 目录下
sudo ./mount-hdc
cp ./exp_06/pc.c ./hdc/usr/root/
sudo umount hdc/

 

(2) Realizar semáforo

Esta parte del contenido puede hacer referencia a la llamada al sistema del Experimento 3: "Sistema operativo" de Li Zhijun | Experimento 3 - Llamada al sistema_Blog de Amentos-Blog de CSDN

1. Agregar API de llamada al sistema

Agregue el siguiente código pc.ca (agregado arriba).

_syscall2(sem_t *,sem_open,const char *,name,unsigned int,value)
_syscall1(int,sem_wait,sem_t *,sem)
_syscall1(int,sem_post,sem_t *,sem)
_syscall1(int,sem_unlink,const char *,name)

2. Nueva sem.h

linux-0.11/include/linuxCree uno nuevo en el directorio para definir la estructura de datos del semáforo, incluido el nombre del semáforo, el valor del semáforo y una cola de proceso en espera. sem.h

【sem.h】

#ifndef _SEM_H
#define _SEM_H

#include <linux/sched.h>

#define SEMTABLE_LEN    20
#define SEM_NAME_LEN    20

typedef struct semaphore
{
    char name[SEM_NAME_LEN];    /* 信号量名称 */
    int value;                  /* 信号量值 */
    struct task_struct *queue;  /* 信号量等待队列 */
} sem_t;

extern sem_t semtable[SEMTABLE_LEN];  /* 定义一个信号量表 */

#endif

La función de #ifndef, #define y #endif aquí es evitar la compilación repetida de archivos de encabezado causada por referencias repetidas. Para principios específicos, consulte este artículo: ¿ Por qué agregar #ifndef #define #endif al archivo de encabezado?

3. Nuevo sem.c

linux-0.11/kernel En el directorio , cree un nuevo archivo de código fuente sem.cpara implementar cuatro funciones de semáforo.

【sem.c】

#include <linux/sem.h>
#include <linux/sched.h>
#include <unistd.h>
#include <asm/segment.h>
#include <linux/tty.h>
#include <linux/kernel.h>
#include <linux/fdreg.h>
#include <asm/system.h>
#include <asm/io.h>
//#include <string.h>

sem_t semtable[SEMTABLE_LEN];  /* 定义一个信号量表 */
int cnt = 0;

sem_t *sys_sem_open(const char *name,unsigned int value)
{
    char kernelname[100];   
    int isExist = 0;
    int i = 0;
    int name_cnt = 0;

    while( get_fs_byte(name+name_cnt) != '\0' )
        name_cnt++;

    if( name_cnt > SEM_NAME_LEN )
        return NULL;

    /* 从用户态复制到内核态 */
    for(i=0;i<name_cnt;i++)
        kernelname[i] = get_fs_byte(name+i);

    int name_len = strlen(kernelname);
    int sem_name_len = 0;
    sem_t *p = NULL;

    for(i=0;i<cnt;i++)
    {
        sem_name_len = strlen(semtable[i].name);
        if(sem_name_len == name_len)
        {
                if( !strcmp(kernelname,semtable[i].name) )
                {
                    isExist = 1;
                    break;
                }
        }
    }

    if(isExist == 1)
    {
        p = (sem_t*)(&semtable[i]);
        //printk("find previous name!\n");
    }
    else
    {
        i = 0;
        for(i=0;i<name_len;i++)
        {
            semtable[cnt].name[i] = kernelname[i];
        }
        semtable[cnt].value = value;
        p = (sem_t*)(&semtable[cnt]);
        //printk("creat name!\n");
        cnt++;
    }
    return p;
}


int sys_sem_wait(sem_t *sem)
{
    cli();   /* 关中断 */

    while( sem->value <= 0 )
        sleep_on( &(sem->queue) );    /* 所有小于0的进程都阻塞 */
    sem->value--;
             
    sti();   /* 开中断 */
    return 0;   
}


int sys_sem_post(sem_t *sem)
{
    cli();
    sem->value++;
    if( (sem->value) <= 1 )
        wake_up( &(sem->queue) );
    sti();
    return 0;
}


int sys_sem_unlink(const char *name)
{
    char kernelname[100];   /* 应该足够大了 */
    int isExist = 0;
    int i = 0;
    int name_cnt = 0;

    while( get_fs_byte(name+name_cnt) != '\0' )
        name_cnt++;

    if( name_cnt > SEM_NAME_LEN )
        return NULL;

    for(i=0;i<name_cnt;i++)
        kernelname[i] = get_fs_byte(name+i);

    int name_len = strlen(name);
    int sem_name_len = 0;

    for(i=0;i<cnt;i++)
    {
        sem_name_len = strlen(semtable[i].name);
        if(sem_name_len == name_len)
        {
            if( !strcmp(kernelname,semtable[i].name) )
            {
                isExist = 1;
                break;
            }
        }
    }

    if(isExist == 1)
    {
        int tmp = 0;

        for(tmp=i;tmp<=cnt;tmp++)
        {
            semtable[tmp] = semtable[tmp+1];
        }
        cnt = cnt-1;
        return 0;
    }
    else
        return -1;
}

4. Modificar unistd.h

Se han agregado cuatro nuevas llamadas al sistema, ingrese linux-0.11/includeal directorio , ábralo unistd.h y agregue un nuevo número de llamada al sistema.

#define __NR_sem_open	xx
#define __NR_sem_wait	xx
#define __NR_sem_post	xx
#define __NR_sem_unlink	xx

5. Modificar system_call.s

Ingrese al directorio, ábralo y modifique el número total de llamadas al sistema. linux-0.11/kernel system_call.s

6. Modificar sys.h

Ingrese linux-0.11/include/linux, abra , agregue nombres de funciones de llamadas al sistema para las cuatro nuevas llamadas al sistema y mantenga la tabla de llamadas al sistema. sys.h

  Tenga en cuenta que    la posición del nombre de la función de llamada al sistema en la matriz sys_call_table debe ser la misma que el valor de __NR_name en unistd.h

7. Modificar el Makefile

linux-0.11/kernelRealice los siguientes cambios en debajo del directorio Makefile.

En primer lugar, agregue después de [OBJS]:

sem.o

En segundo lugar, agregue después de [Dependencias]:

sem.s sem.o: sem.c ../include/linux/sem.h ../include/linux/kernel.h \
../include/unistd.h

8. Monte el archivo

Copie lo escrito sem.hy modificado unistd.hal sistema Linux 0.11, que es el mismo que el principio de "llamada al sistema" en el experimento tres.

// oslab 目录下
sudo ./mount-hdc
cp ./linux-0.11/include/unistd.h ./hdc/usr/include/
cp ./linux-0.11/include/linux/sem.h ./hdc/usr/include/linux/
sudo umount hdc/

9. Recompilar

// linux-0.11 目录下
make all

(3) Ejecutar el programa productor-consumidor

1. Compile y ejecute pc.c

Introduzca Linux 0.11 en el directorio oslab ./run  , compile y ejecute pc.c , y redirija la información de salida al pc.txt archivo .

gcc -o pc pc.c
./pc > pc.txt
sync

¡Tenga en cuenta que debe sincronizar al final!

2. Ver salida

pc.txtCópielo y compruébelo en Ubuntu.

sudo ./mount-hdc
sudo cp ./hdc/usr/root/pc.txt ./exp_06
sudo chmod 777 exp_06/pc.txt
cat exp_06/pc.txt | more

Puede verlo a través catdel comando , o puede hacer doble clic directamente pc.txt para abrirlo.

Tenga en cuenta que si se muestra "No tiene los permisos necesarios para abrir el archivo", modifique los permisos emitiendo el siguiente comando:

sudo chmod 777 exp_06/pc.txt

 

3. Salida del resultado

……

 

【Consejos para experimentos】

1. Lidiando con la caótica pantalla virtual de bochs

       No sé si es un error de Linux 0.11 o bochs, si se envía más información a la terminal, la pantalla virtual de bochs se confundirá. En este momento, presione Ctrl+L para reiniciar la pantalla, pero si hay demasiada información de salida, seguirá siendo confuso. Por ejemplo, al ejecutar el programa directamente desde el principio ./pc , los resultados se muestran de la siguiente manera.

 

       Por lo tanto, se recomienda redirigir la información de salida a un archivo: ./pc > pc.txt (即重定向到 pc.txt)y luego usar vi, más y otras herramientas para ver este archivo presionando la pantalla, lo que básicamente puede resolver este problema. También puede copiar el archivo al sistema Ubuntu para verlo.

vi pc.txt:

 

2. Consejos sobre string.h

       Los problemas que se describen a continuación pueden no tener un significado universal y son solo un recordatorio, preste atención a los experimentadores.

       include/string.h implementa un conjunto completo de operaciones de cadenas en lenguaje C, y todas están optimizadas por ensamblado + en línea. Pero en uso, pueden surgir algunos problemas extraños en algunos casos. Por ejemplo, alguien se encontró con el problema de que strcmp() destruiría el contenido de los parámetros. Si encuentra algunas situaciones "extrañas" durante la depuración, puede intentar no incluir archivos de encabezado, que generalmente se pueden resolver. Debido a que string.h no está incluido, estas funciones no se llamarán en línea y funcionarán con más normalidad.

Supongo que te gusta

Origin blog.csdn.net/Amentos/article/details/131002529
Recomendado
Clasificación