2014025659 《嵌入式程序设计》第五周学习总结
第5周主要学习内容
主要学习了fopen、fread和fwrite函数,对进程函数fork()以及exec函数族、停止进程函数进行了了解学习。
一、Linux下文件的相关操作(fopen、fread、fwrite)
1.fopen函数
函数原型
#include <stdio.h>
FILE * fopen(const char * path,const char * mode);
参数path字符串包含欲打开的文件路径及文件名,参数mode字符串则代表着流形态。mode有下列几种形态字符串:
命令 | 说明 |
"r"或"rb" | 以只读方式打开文件,该文件必须存在。 |
"w"或"wb" | 以写方式打开文件,并把文件长度截短为零。 |
"a"或"ab" | 以写方式打开文件,新内容追加在文件尾。 |
"r+"或"rb+"或"r+b" | 以更新方式打开(读和写) |
"w+"或"wb+"或"w+b" | 以更新方式打开,并把文件长度截短为零。 |
"a+"或"ab+"或"a+b" | 以更新方式打开,新内容追加在文件尾。 |
返回值:文件顺利打开后,指向该流的文件指针就会被返回。若果文件打开失败则返回NULL,并把错误代码存在errno中。
2.fread函数
函数原型
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
- 参数解释:
- ptr: 指向一块存储空间,用来存放本次读取到的数据
- size: 表示一次读取的数据单元大小
- nmemb: 本次读取多少个数据元素(也可以叫一个数据单元,我不知是否准确)
- stream: 将要读取的文件流
- 返回值:
- 如果读取成功,返回 nmemb,即返回读取到的元素个数(不是读取的字符个数)
- 如果遇到文件结束,返回实际读取到的元素个数,可能小于 nmemb
- 如果失败,返回0
3.fwrite函数
函数原型
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
- 参数解释:
- ptr: 指向一块存储空间,用来存放本次写入的数据
- size: 表示一次写入的数据单元大小
- nmemb: 表示写入的次数
- stream: 目标文件指针
- 返回值:
- 成功时fwrite返回的值与nmemb相等
- 若小于nmemb表示出错了(可以使用perror函数查看错误原因)
二、Linux下进程创建相关的系统调用
1.进程函数fork()
fork()函数用于从已存在的进程中创建一个新进程。(新进程称为子进程,原进程称为父进程)
- fork()函数所需头文件:
#include<sys/types.h> //提供类型pid_t的定义
#include<unistd.h>
- 函数原型:
pid_t fork(void)
- fork仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
- 在父进程中,fork返回新创建子进程的进程ID(大于0的整数);
- 在子进程中,fork返回0;
- 如果出现错误,fork返回-1;
在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
- fork出错可能有两种原因:
- 当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
- 系统内存不足,这时errno的值被设置为ENOMEM。
创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。每个进程都有一个独特(互不相同)的进程标识符process ID
,可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。
2.exec函数族
在Linux中,并不存在exec()函数,exec指的是一组函数,一共有6个,分别是:
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。
- exec函数族提供了一个在进程中启动另一个程序执行的方法。
- Linux中使用exec函数族的两种情况:
- 当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用exec函数族中的任意一个函数让自己重生;
- 如果一个进程想执行另一个程序,那么它就可以调用fork()函数新建一个进程,然后调用exec函数族中的任意一个函数,这样看起来就像通过执行应用程序而产生了一个新进程。(这种情况非常普遍)
3.exit()和_exit()函数
- exit()就是退出,传入的参数是程序退出时的状态码,0表示正常退出,其他表示非正常退出,一般都用-1或者1。
- _exit()直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;exit() 函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序,也是因为这个原因,有些人认为exit已经不能算是纯粹的系统调用。
exit()函数与_exit()函数最大的区别就在于exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是"清理I/O缓冲"。
_exit返回后直接返回内核,而exit要进行清除工作,那么两者到底有什么不同呢?
其实exit结束进程也是调用了_exit函数,但是它在之前做了两点:
1.调用atexit() // 注册的函数(出口函数)
按ATEXIT注册时相反的顺序调用所有由它注册的函数,这使得我们可以指定在程序终止时执行自己的清理动作.例如,保存程序状态信息于某个文件,解开对共享数据库上的锁等.
2.cleanup();
关闭所有打开的流,这将导致写所有被缓冲的输出,删除用TMPFILE函数建立的所有临时文件。
4.wait()和waitpid()函数
- wait()作用:用于使父进程(也就是调用wait()的进程)阻塞,直到一个子进程结束或者该进程接到了一个指定的信号为止。如果该父进程没有子进程或者它的子进程已经结束,则wait()就会立即返回。
- waitpid()作用:waitpid()的作用和wait()一样,但它并不一定要等待第一个终止的子进程,它还有若干选项,如可提供一个非阻塞版本的wait()功能,也能支持作业控制。
实际上wait()函数只是waitpid()函数的一个特例,在Linux内部实现wait()函数时直接调用的就是waitpid()函数。
函数原型
pid_t wait (int *status)
pid_t waitpid(pid_t pid,int * status,int options)
waitpid函数提供了wait函数没有提供的三个功能:
- waitpid等待一个特定的进程,而wait则返回任一终止子进程的状态 。
- waitpid提供了一个wait的非阻塞版本,有时希望取得一个子进程的状态,但不想进程阻塞。
- waitpid支持作业控制。
学习进度条
| | | | |
| -------- | :----------------😐:----------------😐:---------------: | :--------------: |
| | 代码行数(新增/累积)| 博客量(新增/累积)|学习时间(新增/累积)|重要成长|
| 目标 | 5000行 | 30篇 | 400小时 | |
| 第一周 | 100/100 | 1/1 | 10/10 |复习了vim、gcc的相关知识|
| 第二周 | 300/400 | 1/2 | 18/28 | 学习了有关gcc、gdb、静态库和动态库、Makefile的相关知识及操作|
| 第三周 | 600/1000 | 0/2 | 18/46 |学习了如何搭建Linux交叉开发环境|
| 第四周 | 1000/2000 | 1/3 | 20/66 |学习了Bootloader的配置、移植和编译|
| 第五周 | 3000/5000 | 1/4 | 30/96 |学习了fopen、fread和fwrite函数,对进程函数fork()以及exec函数族、停止进程函数进行了了解学习|