【Linux】 —— Llamada del sistema operativo de archivos

1. Función de E / S de archivo

Las funciones de E / S de archivo disponibles que llamamos incluyen abrir un archivo, leer un archivo y escribir un archivo. La mayoría de las E / S de archivos en sistemas UNIX requieren 5 funciones: abrir, leer, escribir, buscar y cerrar. Para el núcleo, se hace referencia a todos los archivos abiertos a través de descriptores de archivo. Al abrir un archivo existente o crear un nuevo archivo, el núcleo devuelve un descriptor de archivo al proceso. Al leer o escribir un archivo, use abrir o crear para devolver el descriptor de archivo para identificar el archivo y pasarlo como un parámetro para leer o escribir. A continuación, echemos un vistazo a algunas de estas cinco funciones.

1. Abrir
llama a la función de abrir para abrir o crear un archivo

#include<fcntl.h>
int open(const char* filename,int flag,.../*mode_t mode */);

(1) nombre de archivo: el nombre del archivo que se abrirá o creará. Atencion Si solo le da el nombre del archivo, buscará debajo de la ruta actual, por lo que debe ser la ruta y el nombre del archivo.
(2) bandera: indica la forma en que se abre el archivo. Puede usar una o más de las siguientes constantes para realizar la operación OR para formar el parámetro indicador
. Las tres constantes en la siguiente figura deben especificar una y solo una
Inserte la descripción de la imagen aquí. La constante en la siguiente figura es el
O_APPEND opcional : se agrega al archivo cada vez que se escribe. al final de
O_CREAT: si no existe este archivo, lo crea. Pero en este momento, debe usar el tercer modo de parámetro, que se utiliza para especificar el bit de permiso de acceso
O_EXCL del archivo: si también se especifica O_CREAT y el archivo existe, se producirá un error. Este método se puede usar para probar si existe un archivo. Si no existe, se crea el archivo. Esto hace que tanto la creación como la prueba sean una operación atómica.
O_TRUNC: si este archivo existe, y se abre para solo escritura o lectura-escritura, la longitud se trunca a 0.
Parte de las opciones de entrada y salida de sincronización
O_DSYNC: hace que cada escritura espere a que se complete la operación de E / S física, pero si la operación de escritura No afecta la lectura de los datos recién escritos, no espera a que se actualicen los atributos del archivo
O_RSYNC: hace que cada descriptor de archivo como una operación de lectura de parámetros espere hasta que se complete cualquier operación de escritura pendiente en la misma parte del archivo
O_SYNC: hace que cada La segunda escritura espera hasta que se complete la operación de E / S física, incluida la E / S requerida para la actualización del atributo del archivo causada por la operación de escritura

2, cierra
llama a la función de cierre para cerrar un archivo abierto

#include<fcntl.h>
int close(int filename);

Al cerrar un archivo también se liberan todos los bloqueos de registro que el proceso agregó al archivo. Atencion Cuando finaliza un proceso, el núcleo cierra automáticamente todos los archivos abiertos. Muchos programas aprovechan esta característica sin cerrar explícitamente el archivo abierto con cierre.

3. lseek
puede llamar a lseek para establecer explícitamente el desplazamiento del archivo abierto. Cada archivo abierto tiene un "desplazamiento de archivo actual" asociado. En general, las operaciones de lectura y escritura comienzan desde el desplazamiento del archivo actual y aumentan el desplazamiento por el número de bytes leídos y escritos. Según el valor predeterminado del sistema, al abrir un archivo, a menos que se especifique la opción O_APPEND, el desplazamiento se establece en 0.

#include<fcntl.h>
int lseek(int fd,int size,int flag);

(1) bandera: es la marca en movimiento, la posición inicial del movimiento. SEEK_SET es establecer el desplazamiento del archivo en bytes de tamaño desde el comienzo del archivo. SEEK_CUR es establecer el desplazamiento del archivo a su valor actual más el tamaño, el tamaño puede ser positivo o negativo. SEEK_END es establecer el desplazamiento del archivo a la longitud del archivo más el tamaño, y el tamaño puede ser positivo o negativo.
(2) Valor de retorno: si lseek se ejecuta con éxito, devuelve el nuevo desplazamiento del archivo. Atencion Para los archivos normales, el desplazamiento no debe ser negativo, pero algunos dispositivos también pueden permitir desplazamientos negativos. Por lo tanto, al comparar el valor de retorno de lseek, no pruebe si es menor que 0, pero pruebe si es igual a -1.

lseek solo registra el desplazamiento del archivo actual en el núcleo, no causa ninguna operación de E / S. Este desplazamiento se utiliza para la siguiente operación de lectura o escritura. El desplazamiento del archivo puede ser mayor que la longitud actual del archivo. En este caso, la siguiente escritura en el archivo alargará el archivo y formará un agujero en el archivo, lo que en realidad está permitido. Los bytes que están en el archivo pero que no se han escrito se leen como 0. También se debe tener en cuenta que para los datos recién escritos, se deben asignar bloques de disco, pero para el área vacía que mencionamos antes, no se requieren bloques de disco.

4, read
llama a la función de lectura para leer datos del archivo abierto

#include<fcntl.h>
int read(int fd,void *buf,size_t size);

(1) fd: el archivo leído, especificado por el valor de retorno de open
(2) Valor de retorno: si tiene éxito, devuelve el número de bytes leídos. Si se alcanza el final del archivo, devuelve 0. Las siguientes situaciones pueden hacer que la cantidad real de bytes leídos sea menor que la cantidad de bytes necesarios para leer.
a. Al leer el archivo, se alcanza el final del archivo antes de leer el número requerido de bytes,
b. Al leer desde el dispositivo terminal, generalmente se lee como máximo una línea a la vez
c. Al leer desde la red, el mecanismo de búfer en la red puede causar un valor de retorno Menos de la cantidad de bytes necesarios para leer
d. Al leer desde una tubería o FIFO, si la tubería contiene menos de la cantidad requerida de bytes, la lectura solo devolverá la cantidad real de bytes disponibles
e. Al grabar desde ciertos aspectos El dispositivo (como una cinta) lee a lo sumo un registro a la vez
f. Cuando una señal provoca una interrupción
(3), se usa nulo * para indicar un puntero general.

5, escribir
llama a la función de escritura para escribir datos en el archivo abierto

#include<fcntl.h>
int write(int fd,void *buf,size_t size);

Su valor de retorno es el mismo que el valor del tamaño del parámetro, de lo contrario significa un error. La causa común de los errores es que el disco está lleno o excede el límite de longitud de archivo de un proceso determinado.
Para archivos ordinarios, la operación de escritura comienza en el desplazamiento actual del archivo. Si la opción O_APPEND se especifica cuando se abre el archivo, el desplazamiento del archivo se establece en el final actual del archivo antes de cada operación de escritura. Después de una escritura exitosa, el desplazamiento del archivo aumenta el número de bytes realmente escritos.

6, funciones dup y dup2
Estas dos funciones se utilizan para copiar un descriptor de archivo existente

#include<unistd.h>
int dup(int fileds);
int dup2(int fileds,int fileds2);

El nuevo descriptor de archivo devuelto por dup debe ser el valor más pequeño en el descriptor de archivo disponible actualmente. Con dup2, puede usar el parámetro filedes2 para frenar el valor del nuevo descriptor. Si filedes2 ya está abierto, ciérrelo primero. Si fileds es igual a fileds2, entonces dup2 devuelve filedes2 sin cerrarlo.

7, funciones stat, fstat y lstat

#include<sys/stat.h>
int stat(const char *restrict pathname,struct stat *restrict buf);
int fstat(int fileds,struct stat *buf);
int lstat(const char *restrict pathname,struct stat *restrict buf);

Valor de retorno: las tres funciones devuelven 0 en caso de éxito y -1 en caso de error. stat devuelve la estructura de información relacionada con el archivo nombrado. La función fstat obtiene información sobre el archivo que se ha abierto en los archivos descriptores del archivo. La función lstack es similar a stat, pero cuando el archivo nombrado es un enlace simbólico, lstat devuelve la información sobre el enlace simbólico. Información.

Dos, operación atómica

Una operación atómica se refiere a una operación compuesta de múltiples pasos. Si la operación se realiza atómicamente, todos los pasos se ejecutan o un paso no se ejecuta. Es imposible ejecutar solo un subconjunto de todos los pasos.
(1) Agregar a un archivo
No hay una opción O_APPEND en la operación de apertura descrita anteriormente. No hay ningún efecto en un solo proceso, pero para un proceso múltiple usar este método para agregar datos al mismo archivo al mismo tiempo, causará problemas. Debido a que la relación entre las estructuras de datos es compartida, se supone que hay dos procesos independientes A y B que agregan el mismo archivo, cada proceso ha abierto el archivo pero no usa el indicador O_APPEND, cada uno El proceso tiene su propia entrada de archivo, pero comparte una entrada de nodo v, de modo que la operación de escritura de los dos procesos hará que se sobrescriban los datos del archivo.
La operación lógica "localizar hasta el final del archivo y escribir" utiliza dos llamadas a funciones separadas. La solución al problema es que estas dos operaciones se convierten en una operación atómica para otros procesos.
(2) Funciones de pread y pwrite El
prototipo de la función es el siguiente:

#include<unistd.h>
ssize_t pread(int flags,void *buf,size_t nbytes,off_t offset);
ssize_t pwrite(int flags,void *buf,size_t nbytes,off_t offset);

Valor de retorno: Pread lee el número de bytes, si ha llegado al final del archivo, devuelve 0, si falla, devuelve -1; pwrite devuelve el número de bytes escritos si tiene éxito y devuelve -1 si hay un error.
Llamar a pred es equivalente a llamar secuencialmente Buscar y leer, llamar a Pwrite es equivalente a llamar a Iseek y escribir secuencialmente

3. Ejemplos

Práctica uno:
con las funciones básicas anteriores para realizar operaciones de E / S, practiquemos y almacenemos los datos ingresados ​​por el usuario en la interfaz a a.txt, y luego visualicemos todo el contenido de a.txt en el terminal como un todo Arriba

int main()
{
	int fd = open("a.txt", O_RDWR | O_CREAT, 0664);//权限设置值
	assert(-1 != fd);
	
	while(1)
	{
		printf("input: ");
		char buff[128] = {0};
		fgets(buff,128,stdin);//从用户获取数据,stdin标准输入,会把最后的回车符也放在buff中
		
		if(strncmp(buff,"end",3) == 0)
		{
			break;
		}
		
		int n =write(fd,buff,strlen(buff));
		if(n<=0)
		{
			perror("write error:");//和printf很像,但是他主要是打的出错信息
			exit(0);
		}
	}
	
	printf(****************************a.txt:*************************\n);
	lseek(fd,0,SEEK_SET);//将文件读写游标移动到开始位置
	
	while(1)
	{
		char buff[128] = {0};//从文件里面读取数据往buff中写
		int n = read(fd,buff,127);
		if( n == 0 )
		{
			printf("END\n");
			break;
		}
		else if(n<0)
		{
			perror("read error: ");
			exit(0);
		}
		else
		{
			printf("%s",buff);
		}
	}
	close(fd);
}

Práctica dos:
pruebe el proceso primario y secundario a través del código para compartir el descriptor de archivo abierto antes de la bifurcación

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>

int main()
{
	int fd = open("a.txt", O_RDWR | O_CREAT, 0664);//权限设置值
	assert(-1 != fd);

	pid_t n = fork();
	assert(-1 != n);

	if(0 == n)
	{
		while(1)
		{
			char c = 0;
			int len = read(fd, &c, 1);
			if(n <= 0)
			{
				break;
			}
			printf("child:: %c\n", c);
			sleep(1);
		}
	}
	else
	{
		while(1)
		{
			char c = 0;
			int len = read(fd, &c, 1);
			if(n <= 0)
			{
				break;
			}
			printf("father:: %c\n", c);
			sleep(1);
		}
	}

	close(fd);

	exit(0);
}

Los dos resultados de ejecución diferentes son los siguientes: a
Inserte la descripción de la imagen aquí
partir de los resultados de ejecución anteriores, podemos hacer que el descriptor de archivo se abra antes de que los procesos padre e hijo puedan acceder a la bifurcación, y se comparte el desplazamiento de lectura y escritura del archivo. Los personajes no son compartidos . El proceso de implementación interna es el siguiente:
Inserte la descripción de la imagen aquí

Cuarto, la diferencia entre las funciones de biblioteca y las funciones de llamada del sistema.

(1) Concepto
Primero, debemos aclarar qué es una función de biblioteca y qué es una función de llamada al sistema. Las fuentes fopen, fread, fwrite, fclose y fseek que aprendimos antes son todas las funciones de la biblioteca a las que nos referimos, y las funciones de lectura, escritura, cierre, etc. que enumeramos al principio de este artículo son funciones de llamadas al sistema. Por ejemplo, a menudo nos encontramos con esa pregunta, ¿cuál de fread y read es más eficiente? De hecho, no es una lectura absoluta con alta eficiencia. Cuando hay pocos archivos leídos, porque fread tendrá un consumo de llamadas del modo de usuario al modo kernel, pero para leer una gran cantidad de datos, la operación de fread es todo Ponga en el área de acceso de los usuarios la cantidad de usuarios que usan para obtener la cantidad, pero la operación de lectura es cuántos datos leer.
Esto nos lleva al concepto de funciones de biblioteca y funciones de llamada del sistema. La función de llamada del sistema es una interfaz que el núcleo del sistema ejecuta para llamar en el espacio del usuario. La función de llamada del sistema se llama por modo de usuario y se ejecuta en modo de núcleo. En correspondencia con esto está la función de biblioteca, la función de biblioteca se implementa en el archivo de biblioteca de funciones y solo necesita ejecutarse en el modo de usuario durante la ejecución.

(2) Diferencia
De hecho, en el concepto, podemos conocer claramente su diferencia: la función de biblioteca está en el archivo de biblioteca de funciones y la función de llamada del sistema está implementada en el núcleo del sistema. A continuación, explicamos cuidadosamente el principio de implementación de la función de llamada del sistema con open como columna.
En nuestro sistema, la relación entre ellos es la que se muestra en la siguiente figura:
Inserte la descripción de la imagen aquí
1. Primero encuentre el número de llamada del sistema correspondiente a la función, guárdelo en el registro exa
2, la función de llamada del sistema activa la interrupción 0x80 y luego cae en el núcleo, el núcleo comienza a ejecutar la interrupción Handler Las instrucciones importantes de la interrupción 0x80 son las siguientes

call [_sys_call_table+eax*4]

Esta instrucción es principalmente para permitir que el número de llamada del sistema almacenado en el registro eax encuentre el método de la función del núcleo en la tabla de llamadas del sistema del núcleo y lo ejecute.
3. Después de invocar la función, habrá un valor de retorno fd y un valor entero. Coloque el valor entero en el registro eax y luego cambie al modo de usuario. Luego, una instrucción mov mueve el valor del registro eax a la dirección señalada por fd. Esto es equivalente a guardar el valor de retorno de la función.
El proceso específico es el siguiente:
Inserte la descripción de la imagen aquí

Publicado 98 artículos originales · ganado elogios 9 · vistas 3641

Supongo que te gusta

Origin blog.csdn.net/qq_43412060/article/details/105460239
Recomendado
Clasificación