linux应用开发基础知识(三)——标准IO和文件IO、进程

标准IO和文件IO

标准IO和文件IO的区别

文件I/O:文件I/O称之为不带缓存的IO(unbuffered I/O)。不带缓存指的是每个read,write都调用内核中的一个系统调用。也就是一般所说的低级I/O——操作系统提供的基本IO服务,与os绑定,特定于linix或unix平台。

标准I/O:标准I/O是ANSI C建立的一个标准I/O模型,是一个标准函数包和stdio.h头文件中的定义,具有一定的可移植性。标准I/O库处理很多细节。例如缓存分配,以优化长度执行I/O等。标准的I/O提供了三种类型的缓存。

(1)全缓存:当填满标准I/O缓存后才进行实际的I/O操作。
(2)行缓存:当输入或输出中遇到新行符时,标准I/O库执行I/O操作。
(3)不带缓存:stderr就是了。
通过文件I/O读写文件时,每次操作都会执行相关系统调用。这样处理的好处是直接读写实际文件,坏处是频繁的系统调用会增加系统开销,标准I/O可以看成是在文件I/O的基础上封装了缓冲机制。先读写缓冲区,必要时再访问实际文件,从而减少了系统调用的次数。
文件I/O中用文件描述符表现一个打开的文件,可以访问不同类型的文件如普通文件、设备文件和管道文件等。而标准I/O中用FILE(流)表示一个打开的文件,通常只用来访问普通文件。

在这里插入图片描述

缓冲区

缓冲区是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。
1、为什么要引入缓冲区
比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。
又比如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的CPU可以处理别的事情。现在您基本明白了吧,缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。

2、缓冲区的类型
缓冲区 分为三种类型:全缓冲、行缓冲和不带缓冲。
1)全缓冲
在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。
2)行缓冲
在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是键盘输入数据。
3)不带缓冲
也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。

3、缓冲区什么时候进行刷新
1)缓冲区满时;
2)执行flush语句;(flush:冲洗,冲走)
3)执行endl语句;(end line)
4)关闭文件。

系统调用和库函数的区别

系统调用就是操作系统提供的接口函数。
如果我们把系统调用封装成库函数就可以起到隔离的作用,提供程序的可移植性。库函数可以大大减少
具体可以看这篇文章链接: link

标准IO

1、读单个字符

 #include  <stdio.h>
 int  fgetc(FILE *stream);
 int  getc(FILE *stream);   //宏
 int  getchar(void);
 //成功时返回读取的字符;若到文件末尾或出错时返回EOF(-1)

文件

在这里插入图片描述
挂载: linux操作系统将所有的设备都看作文件,它将整个计算机的资源都整合成一个大的文件目录。我们要访问存储设备中的文件,必须将文件所在的分区挂载到一个已存在的目录上,然后通过访问这个目录来访问存储设备。挂载就是把设备放在一个目录下,让系统知道怎么管理这个设备里的文件,了解这个存储设备的可读写特性之类的过程。
简而言之就是Linux中的根目录以外的文件要想被访问,需要将其“关联”到根目录下的某个目录来实现,这种关联操作就是“挂载”,这个目录就是“挂载点”,解除次关联关系的过程称之为“卸载”。

文件IO

特点:
不提供缓冲机制,每次读写操作都引起系统调用
核心概念是文件描述符
访问各种类型文件
Linux下, 标准IO基于文件IO实现
具体函数参考链接: link
注意的是:就是open函数的输入的参数需要注意。后续需要读操作又需要写操作的,一定要有O_RDWR

进程

进程的创建

进程是程序的实例。程序是静态的,是存放在硬盘上的,程序运行起来就形成了进程。程序从磁盘到内存里之后就形成了进程。
程序:代码段、数据段、BSS段
进程:堆、栈、进程控制块、代码段、数据段、BSS段
1、名词解释
BSS段:存放程序中未初始化的全局变量
数据段:已初始化的全局变量
代码段:程序执行代码
堆(heap):malloc等函数分配内存
栈(stack):局部变量,函数参数,函数的返回值
进程控制块(pcb):PID,进程用户, 进程优先级,文件描述符表
2、进程的状态
运行态:进程正在运行,或者准备运行
等待态:进程在等待一个事件的发生或某种系统资源可中断或
不可中断
停止态:进程被中止,收到信号后可继续运行
死亡态:已终止的进程,但pcb没有被释放
创建子进程

//头文件
#include <unistd.h>
//创建子进程,每调用一次,产生一个子进程
pid_t fork(void);   
//创建新的进程,失败时返回-1,成功时父进程返回子进程的进程号,子进程返回0。通过fork的返回值区分父进程和子进程
#include <sys/types.h>   
//通过 getpid() 和 getppid() 获取当前进程的进程号和父进程号
pid_t getpid(void);
pid_t getppid(void);
//pid_t 即 int ,子进程的内容通过复制父进程得到,父子之间共享文件描述符 fd 等内容

程序作用:
创建一个子进程,并返回子进程号和父进程号

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc,char *argv[])
{
	pid_t pid;
        if((pid=fork())==-1)
	{
		perror("fork");
		return -1;
	}
	if(pid==0)
	{
		printf("[son world]pid:%d,ppid:%d\n",getpid(),getppid());
	}
	else
	{
		printf("[father world]pid:%d,ppid:%d\n",getpid(),getppid());
	}
	return 0;
}   

可以看到父进程的pid等于子进程的ppid。
在这里插入图片描述

进程退出

#include <stdlib.h>  //头文件
#include  <unistd.h>
 void  exit(int  status);   
 void  _exit(int  status);
//结束当前的进程并将status返回
//exit结束进程时会刷新(流)缓冲区,_exit不会

注意: status是进程的退出值,如果子进程正常退出,则 status 一般为 0。如果子进程异常退出,则 statuc 一般为非 0。

回收进程资源

在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。但是仍然为其保留一定的信息,这些信息主要主要指进程控制块PCB的信息(包括进程号、退出状态、运行时间等)。
子进程结束时由父进程回收
孤儿进程由init进程回收
若没有及时回收会出现僵尸进程

#include <sys/wait.h>
pid_t wait(int *status); 
//成功时返回子进程的进程号,失败时返回EOF(-1)

子进程通过exit / _exit / return 返回某个值(0-255)
父进程调用wait(&status)回收
WIFEXITED(status) : 判断子进程是否正常结束
WEXITSTATUS(status) : 获取子进程返回值
WIFSIGNALED(status) : 判断子进程是否被信号结束
WTERMSIG(status) : 获取结束子进程的信号类型

#include  <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int option);
//成功时返回回收的子进程的pid或0;失败时返回EOF(-1)
参数解释:
pid可用于指定回收哪个子进程或任意子进程
status指定用于保存子进程返回值和结束方式的地址
option指定回收方式,0 或 WNOHANG

pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用
WNOHANG :若由pid指定的子进程未发生状态改变(没有结束),则waitpid()不阻塞,立即返回0
WUNTRACED: 返回终止子进程信息和因信号停止的子进程信息
wait(wait_stat) 等价于waitpid(-1,wait_stat,0)

子进程的终极作用

功能在进程中加载新的程序文件或者脚本,覆盖原有代码,重新运行
头文件#include <unistd.h>
原型int execl(const char *path, const char *arg, …); int execv(const char *path, char *const argv[ ]); int execle(const char *path, const char *arg, …, char * const envp[ ]); int execlp(const char *file, const char *arg, …); int execvp(const char *file, char *const argv[ ]); int execvpe(const char *file, char *const argv[ ],char *const envp[ ]);
参数path:即将被加载执行的 ELF 文件或脚本的路径
参数file:即将被加载执行的 ELF 文件或脚本的名字
参数arg:以列表方式罗列的 ELF 文件或脚本的参数
参数argv:以数组方式组织的 ELF 文件或脚本的参数
参数envp:用户自定义的环境变量数组
返回值成功:直接加载程序,不返回;失败:返回-1

exec类函数共有6种,只有execve是系统调用,六种函数的参数各不相同,逐个分析:

整体的区别

“l”:以列表形式传递,需要将参数全部列出来 :

int  execl (const char *path, const char *arg, ...)
int  execlp (const char *file, const char *arg, ...)

‘v’:vector,数组,调用带有v的exec函数,需要将参数存到一个指针数组中,再把数组首地址给函数:

int execv(const char *path, char *const argv[ ])
int execvp(const char *file, char *const argv[ ])

第一个参数

l、v: 将要跳转的程序的路径(同时包括程序名),可以是相对路径,也可以是绝对路径。
lp、vp: 则为将要跳转的程序的程序名(!!!只有程序名,但是只有目标程序在环境变量PATH中才可以跳转成功),也可以给出路径,这时候的效果就分别和上面两个函数一样
怎么把路径添加进PATH内,可以参考这篇文章链接:Linux系统查看和添加环境变量PATH

第二个参数及以后

这后面的参数其实是传递给,第一个参数的可执行文件内的main()函数的参数,也就是main()函数内的argv[]。如果不需要传递参数的话,需要加上 “” 假装成一个参数。同时最后一个参数必须为NULL。

验证

#include<unistd.h>
#include<stdio.h>
int main()
{
	execl("./hello","",NULL); //程序替换函数
  	printf("这段代码是在execl执行成功是不会执行的\n");
  	return 0;
}
int main()
{
	char * argv[]={"",NULL};
	execv("./hello",argv);
	printf("这段代码是在execv执行成功是不会执行的\n");
  	return 0;
}
#include<unistd.h>
#include<stdio.h>
int main()
{
	char * argv[]={"",NULL};
	execvp("hello",argv);//失败的原因可能是因为程序所在的路径没有添加进PATH
	printf("这段代码是在execvp执行成功是不会执行的\n");
  	return 0;
}

执行结果如下:
在这里插入图片描述

  • 25
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值