【1】标准IO(input\output)
1. 概念:
标准IO是指在C库中提供的一组专门用于输入输出的函数
2. 特点:
不仅在UNIX系统,在很多操作系统上都实现了标准I/O库
标准I/O库由ANSI C标准说明
标准I/O通过缓冲机制减少系统调用,实现更高效率
标准I/O在系统调用函数基础上构造的,它便于用户使用
标准IO默认打开了三个流:stdin、stdout、stderr
3. 流:
定义:所有的I/O操作仅是简单的从程序移进或者移出,
这种字节流,就称为流
4. FILE:(文件流指针)
每个被使用的文件都在内存中开辟一个区域,用来存放文件
的有关信息,这些信息是保存在一个结构体类型的变量中,
该结构体类型是由系统定义的,取名为FILE。
标准I/O库的所有操作都是围绕流(stream)来进行的,
在标准I/O中,流用FILE *来描述。
使用vi -t FILE
vi -t FILE ---> ctrl+](追朔源码定义)-->struct _IO_FILE
ctrl+t:回退上一次定义
【2】缓存区:
1.全缓存:与文件相关
刷新缓存区的条件:
1)程序正常退出
2)缓存区满刷新
3)fflush强制刷新
fflush( FILE* 流指针);
2.行缓存:与终端相关
刷新缓存区的条件:
1)\n刷新
2)程序正常退出
3)缓存区满刷新
4)fflush强制刷新
3.不缓存:没有缓存区,stderr
练习:测试标准输出缓存区的大小
1Byte*1024=1K
Byte KB MB GB
1GB = 1024MB
1MB = 1024KB
1KB = 1024Byte
【3】打开文件:
FILE *fopen(const char *path, const char *mode);
功能:打开文件
参数:path:文件路径
mode:打开文件的方式
r:只读,文件不存在,会报错
r+:可读可写,文件不存在,会报错
w:只写,文件不存在创建,文件存在清空
w+:可读可写,文件不存在创建,文件存在清空
a:追加(可写),文件不存在创建,存在追加
a+:可读可写,文件不存在创建,存在追加
注:以"a"开头的附加模式下打开后续对该流操作总会写入文件末尾。
返回值:成功:返回文件流指针
失败:NULL
关闭文件:
int fclose(FILE *fp);
练习:使用读写并且不存在创建存在清空权限,打开一个文件,然后关闭这个文件。
FILE *freopen(const char *pathname, const char *mode, FILE* fp)
功能:将指定的文件流指针重定向到打开的文件中
参数:path:文件路径
mode:打开文件的方式(同上)
fp:文件流指针
返回值:成功:返回文件流指针
失败:NULL
练习:打开一个文件并将标准输出重定向到这个文件,使用printf("hello world\n");来验证
补充:
void perror(const char *s);
功能:当你调用“某些”函数出错时,会将参数 s 所指的字符串和错误的原因输出到标准设备。
char *strerror(int errnum);
功能:根据错误号返回错误原因字符串
参数:errnum:errno号
int fprintf(FILE *stream, const char *format, ...);
功能:向指定的流中输出数据
参数:
stream:指定的文件流
const char *format, ...:向流输入的内容
返回值:
成功返回向文件流输入内容的字符个数
失败返回0
练习1:指定一个文件,向这个文件输入hello world.
练习2:测试一个进程最多可以打开多少个文件?
main()
{
int num=0;
FILE *fp;
while(1)
{
fp = fopen("./test.txt","r");
if(fp == NULL)
{
break;
}
num++;
}
printf("num:%d\n",num);
}
【4】每次一个字符的读写:
int fgetc(FILE *stream);
功能:每次一个字符的读
参数:stream:文件流指针
返回值:成功:读到字符的ASCII
失败或文件结尾:EOF(-1)
int fputc(int c, FILE *stream);
功能:向指定的文件中写一个字符
参数:c 要写的字符
stream 要写到的流
返回值:成功:写入的字符;
失败:EOF
练习:用fgetc和fputc实现cat的功能,从命令行输入文件名.
【5】每次一行的IO:
char *fgets(char *s, int size, FILE *stream);
功能:从指定的流中读取最多size-1个字符,第size个字符是'\0'
存放在s所指的空间
参数:s 读到数据的存放位置
size 一次期望读到的数据,若遇到'\n'会直接返回
stream 流
返回值:成功返回s的地址
失败或读到文件结尾:NULL
int fputs(const char *s, FILE *stream);
功能:向指定的文件流中写入一串字符
参数:s:要写入的字符内容
stream 流
返回值: 成功 实际写入的数据的个数
失败 EOF
注意:
fgets最多可以读size - 1个,最后一个字符必须是'\0'字符
fgets函数是每次读一行的函数,遇到'\n'字符返回
练习1:用fgets实现wc –l的功能,文件名从命令行输入
int line = 0;
while(1)
{
fgets(buf)
if(NULL==fgets())
break;
buf[strlen(buf)-1] == '\n';
line++;
}
练习2:用fgets和fputs实现cp的功能,两个文件名从命令行输入
cp 文件1 文件2
while(1)
{
if(fgets(buf,sizeof(buf),fp1) == NULL)
break;
fputs(buf,fp2)
}
作业:
1、使用fgets从终端输入字符串,将此字符串写入指定文件里,当输入"quit"退出.
【1】直接IO(二进制IO)
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);//从流里读
功能:从文件流读取多个元素
参数: ptr :用来存放读取元素的地址
size :元素大小 sizeof(数据类型)
nmemb :读取元素的个数
stream :要读取的文件
返回值:成功:读取的元素的个数;读到文件尾: 0
失败: -1
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);//往流里写
功能:按对象写
参数:同上
返回值:成功:写的元素个数
失败 :-1
注意:
两个函数的返回值为:读或写的对象数
对于二进制数据我们更愿意一次读或写整个结构
练习1:从一个文件中读取数据然后将数据往另外一个文件里去写
【2】文件的偏移:
int fseek(FILE *stream, long offset, int whence);
功能:设定stream流的文件位置
参数: stream 要偏移的流指针
offset 偏移量
正数:向文件结尾位置移动 负数:向文件开始位置
whence 相对位置
SEEK_SET 开始位置
SEEK_CUR 当前位置
SEEK_END 结尾位置
返回值:成功:0 失败:-1
long ftell(FILE *stream);
功能:取得当前的文件位置
参数:要检测的流
返回值:当前的文件位置,出错则为-1L
void rewind(FILE *stream);
功能:设定流的文件位置指示为文件开始
注意:rewind(fp)的功能和fseek(fp, 0, SEEK_SET)的功能相同
练习:
将指定文件的光标向后移动10个字节单位,并且写入一个字符
【3】文件IO:
1.定义:
posix(可移植操作系统接口)定义的一组函数
2.特点:
不带缓冲机制,每次操作都引起系统调用
通过文件描述符来访问文件
可以访问Linux下各种类型文件
3.文件描述符:
每个打开的文件都对应一个文件描述符。
文件描述符是一个非负整数。Linux为程序中每个打开的文件分配一个文件描述符。
文件描述符从0开始分配,依次递增。
在文件IO中默认打开了三个文件描述符0,1,2对应标准输入、标准输出、标准出错
3,4,5
关闭(3)---打开后,3
【4】打开文件:
int open(const char *pathname, int flags);
功能:打开文件
参数:pathname:文件路径名
flags:打开文件的方式
O_RDONLY:只读
O_WRONLY:只写
O_RDWR:可读可写
O_CREAT:文件不存在创建
O_TRUNC:清空文件内容
O_APPEND:追加
返回值:成功:文件描述符
失败:-1
当open函数的第二个参数指定为O_CREAT时,
需要用下面的函数,给他指定文件权限
int open(const char *pathname, int flags, mode_t mode);
标准IO 文件IO:
r O_RDONLY
r+ O_RDWR
w O_WRONLY|O_CREAT|O_TRUNC,0666
w+ O_RDWR|O_CREAT|O_TRUNC,0666
a O_WRONLY|O_CREAT|O_APPEND,0666
a+ O_RDWR|O_CREAT|O_APPEND,0666
【5】关闭文件:
close(int fd);
【6】读写文件:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:从一个已打开的可读文件中读取数据
参数:fd 文件描述符
buf 存放位置
count 期望的个数
返回值:成功:实际读到的个数
返回-1:表示出错,并设置errno号
返回0:表示读到文件结尾
练习:从指定文件中使用read读取文件的内容,并在终端打印出来。
ssize_t write(int fd, const void *buf, size_t count);
功能:向指定文件描述符中,写入 count个字节的数据。
参数:fd 文件描述符
buf 要写的内容
count 期望值
返回值:成功:实际写入数据的个数
-1:失败
注:写要准确的个数,读可以不要准确的个数。
练习:用文件IO实现cp功能,文件名从命令行输入
open(argv[1],O_RDONLY)
open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0666);
while(1)
{
if(read(buf) == 0)
{
printf("read success\n");
break;
}
write();
}
【7】文件的偏移:
off_t lseek(int fd, off_t offset, int whence);
功能:设定文件的偏移位置
参数:fd:文件描述符
offset偏移量
正数:向文件结尾位置移动
负数:向文件开始位置
whence 相对位置
SEEK_SET 开始位置
SEEK_CUR 当前位置
SEEK_END 结尾位置
返回值:
off_t :long long int
成功:文件的当前位置
失败:-1
练习:求文件的长度。
练习:
1-- 打开一个文件,不存在创建,存在清零
2-- 向文件中第 10 位置处写一个字符,
3-- 在文件此时的位置,后20个位置处,写一行字符串hello进去
【8】获取文件属性:
int stat(const char *path, struct stat *buf);
功能:获取文件属性
参数:path:文件路径名
buf:保存文件属性信息的结构体
返回值:成功:0
失败:-1
注:stat 文件名:在终端下可查看对应文件的文件属性
struct stat {
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
off_t st_size; /* total size, in bytes */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
};
S_IRUSR 00400
00400 --> 000 000 100 000 000
if(st_mode & S_IRUSR)
putchar('r');
else
putchar('-');
结果为非0,说明有用户读权限
为0,说明没有读权限
struct group *getgrgid(gid_t gid);
功能:将用户组id转换成组名
struct group {
char *gr_name; /* group name */
char *gr_passwd; /* group password */
gid_t gr_gid; /* group ID */
char **gr_mem; /* group members */
};
struct passwd *getpwuid(uid_t uid);
功能:将用户id转换成用户名
char *ctime(const time_t *timep);
功能:将时间转换成字符串
"Wed Jun 30 21:49:08 1993\n"
笔记:
目录的操作:
DIR *opendir(const char *name); "." ".."
功能:获得目录流
参数:要打开的目录
返回值:成功:目录流
失败:NULL
man opendir观察此函数得返回值类型
struct dirent *readdir(DIR *dirp);
功能:读目录
参数:要读的目录流
返回值:成功读到的信息 失败NULL
man readdir 观察此函数得返回值类型
返回值为结构体,该结构体成员为描述该目录下的文件信息
struct dirent {
ino_t d_ino; /* 索引节点号*/
off_t d_off; /*在目录文件中的偏移*/
unsigned short d_reclen; /* 文件名长度*/
unsigned char d_type; /* 文件类型 */
char d_name[256]; /* 文件名 */
};
int closedir(DIR *dirp);
关闭目录流
【1】目录的操作:
DIR *opendir(const char *name); "." ".."
功能:获得目录流
参数:要打开的目录
返回值:成功:目录流
失败:NULL
struct dirent *readdir(DIR *dirp);
功能:读目录
参数:要读的目录流
返回值:成功读到的信息 失败NULL
返回值为结构体,该结构体成员为描述该目录下的文件信息
struct dirent {
ino_t d_ino; /* 索引节点号*/
off_t d_off; /*在目录文件中的偏移*/
unsigned short d_reclen; /* 文件名长度*/
unsigned char d_type; /* 文件类型 */
char d_name[256]; /* 文件名 */
};
int chdir(const char *path); 将调用进程当前的工作目录改为path指定的目录。
int closedir(DIR *dirp);
关闭目录流
练习:编程实现ls的功能
【2】库
1.定义:本质上来说库是一种可执行代码的二进制形式;
通俗讲就是把一些常用函数的目标文件打包在一起,提供相应
函数的接口,便于程序员使用;它可以被操作系统载入内存执行。
由于windows和linux的本质不同,因此二者库的二进制是不兼容的
2.库的分类:
1)静态库:静态库在程序编译时会被链接到目标代码中,
程序运行时将不再需要该静态库,因此体积较大。
2)动态库在程序编译时并不会被连接到目标代码中,
而是在程序运行时才被载入,因此在程序运行时还需要动态库
存在,因此代码体积较小
3.静态库的制作步骤:
1-将源文件编译生成目标文件
gcc -c add.c -o add.o
2-创建静态库用ar命令,它将很多.o转换成.a
ar crs libmyadd.a add.o
//ar crs命令将.o文件变成库文件
静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a
3-使用静态链接库
gcc main.c -L. -lmyadd
-L为添加静态库的搜索路径,.为链接库当前路径
4-执行./a.out
优缺点:
优点: 程序中已包含代码,运行时不再需要静态库。
运行时无需加载库,运行速度更快。
缺点: 静态库中的代码复制到了程序中,使程序会占更多的磁盘和内存空间
静态库升级后,程序需要重新编译链接
4.动态库的制作步骤:
1-我们用gcc来创建共享库
gcc -fPIC -c hello.c -o hello.o
-fPIC 创建与地址无关的编译程序
gcc -shared -o libmyhello.so hello.o
fPIC的全称Position Independent Code
2-编译代码
gcc main.c -L. -lmyhello
3-为了让执行程序顺利找到动态库,有三种方法 :
(1)把库拷贝到/usr/lib和/lib目录下。
(2)在LD_LIBRARY_PATH环境变量中加上库所在路径。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
(终端关闭,环境变量就没在了)
(3) 添加/etc/ld.so.conf.d/*.conf文件,
把库所在的路径加到文件末尾,并执行ldconfig刷新
sudo vi xx.conf 注意:xx为动态库名
添加动态库存在的绝对路径,如:
/home/linux/19072/IO/day3/
优缺点:
优点: 程序在执行时加载动态库,代码体积小
将一些程序升级变得简单。
不同的应用程序如果调用相同的库,那么在内存
里只需要有一份该共享库的实例。
缺点:运行时还需要动态库的存在,移植性较差
【3】进程:
1.进程和程序的区别
程序:编译好的可执行文件
存放在磁盘上的指令和数据的有序集合(文件)
程序是静态的,没有任何执行的概念
进程:一个独立的可调度的任务
执行一个程序所分配的资源的总称
进程是程序的一次执行过程
进程是动态的,包括创建、调度、执行和消亡
2.进程包含三个段:
1)“数据段”存放的是全局变量、常数以及动态数据分配的数据空间
(如malloc函数取得的空间)等。
2)“正文段”存放的是程序中的代码
3)“堆栈段”存放的是函数的返回地址、函数的参数以及程序中的局部变量
3.进程的类型:
1)交互进程:交互进程既可以在前台运行,也可以在后台运行。
该类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入后,
该类进程会立刻响应。例如:ctrl+c
2)批处理进程:该类进程不属于某个终端,它被提交到一个队列中以便顺序执行
3)守护进程:该类进程在后台运行的特殊进程。它一般在Linux启动时开始执行,系统关闭时才结束,守护进程名通常以d结尾。
4.进程的运行状态:
1)运行态:此时进程或者正在运行,或者准备运行。
2)等待态:此时进程在等待一个事件的发生或某种系统资源。
可中断:处在这种状态下的进程可以被信号中断,接收到信号或被显示地唤醒呼叫,唤醒之后,进程将转变为运行态。
不可中断:它不会处理信号,只有在它所等待的事件发生时,进程才被显示的唤醒,
或者处于该状态的进程被使用wake_up()函数明确唤醒时才能转换到可运行的就绪状态。
3)停止态:此时进程被中止。
4)死亡态:这是一个已终止的进程,但还在进程向量数组中占有一个task_struct结构。
D uninterruptible sleep (usually IO)(不可中断的等待态)
R running or runnable (on run queue)(运行态)
S interruptible sleep (可中断的等待态)
T stopped, either by a job control signal or because it is
being traced.(停止态)
X dead (should never be seen)(死亡态)
Z defunct ("zombie") process, terminated but not reaped by its
parent.(僵尸态)
For BSD formats and when the stat keyword is used, additional
characters may be displayed:
< high-priority (not nice to other users)(高优先级)
N low-priority (nice to other users)(低优先级)
s is a session leader(会话组组长)
l is multi-threaded (线程)
+ is in the foreground process group.(前台)
空:表示后台
【4】相关命令:
1.ps aux:
2.top:
shift+‘>’:向下查找
shift+‘<’:向上查找
q:退出
3.nice:按用户指定的优先级运行进程
PR=NI+20 NI [-20,19]数字越小优先级越高
nice n ./a.out
例如:nice 5 ./a.out 修改成优先级为5
4.renice:改变正在运行进程的优先级
renice 5 PID
5.kill:发送信号(包括后台进程)
kill -num PID 给指定的进程,发送一个信号。
kill -l 查看进程信号
2) SIGINT 停止信号,默认杀死进程。ctrl + c
3) SIGQUIT 退出信号,默认也是杀死进程。 ctrl + \
9) SIGKILL 杀死进程,不能被忽略,不能被捕捉
14) SIGALRM 闹钟信号,默认也是杀死进程
17)SIGCHLD 儿子状态改变,内核会给它的父亲发送此信号
18) SIGCONT 唤醒信号,唤醒之后变为后台运行
19) SIGSTOP 暂停信号, 不能忽略,不能被捕捉
20) SIGTSTP 暂停信号, ctrl + z
补充:killall 进程名 :结束进程
后台运行./a.out &
6.bg 将暂停的进程在后台继续执行
bg 工作号
7.fg 把后台运行的进程放到前台运行
fg 工作号
8.jobs 查看所有的后台进程工作号:
【5】创建进程函数:
pid_t fork(void);
返回值:
失败:-1
成功:
0 :在子进程中
>0: 在父进程中返回的是子进程的进程号
特点:
1)fork函数是用来创建进程的,fork之后产生了两个进程
,每个进程都会有返回值,所以父进程中返回的是子进程
的进程号(>0);在子进程中返回0
2)子进程几乎拷贝了父进程的全部内容。包括代码、
数据、系统数据段中的pc值、栈中的数据、父进程中打开
的文件等;但它们的PID、PPID是不同的。
3)父子进程有独立的地址空间,互不影响;当在相应的
进程中改变全局变量、静态变量,都互不影响。
4)若父进程先结束,子进程成为孤儿进程,被init进程
收养,子进程变成后台进程。
5)若子进程先结束,父进程如果没有及时回收,子进程
变成僵尸进程(要避免僵尸进程产生)
问题:main函数调用进程函数fork时,那main是fork的父进程吗?
答:main函数是一个入口函数,在调用fork函数时,
父子进程都拥有进入main函数的权利,故此,父子进程各调用一遍main函数。
【6】获取进程号
pid_t getpid(void);
功能:获取当前进程的进程号
pid_t getppid(void);
功能:获取当前进程的父进程号
【6】进程退出函数
void exit(int status);
功能:结束进程,刷新缓存
void _exit(int status);
功能:结束进程,不刷新缓存
status是一个整型的参数,可以利用这个参数传递进程
结束时的状态。
通常0表示正常结束;
其他的数值表示出现了错误,进程非正常结束。
在实际编程时,可以用wait系统调用接收子进程的返回值
,进行相应的处理。
【7】进程回收函数
pid_t wait(int *status);
功能:阻塞等待子进程退出,回收资源(回收僵尸进程)
参数:status是一个整型指针,指向的对象用来保存
子进程退出时的状态。
? status若为空,表示忽略子进程退出时的状态
? status若不为空,表示保存子进程退出时的状态
另外,子进程的结束状态可由Linux中一些特定的宏来测定。
返回值:成功:子进程的进程号
失败:-1
pid_t waitpid(pid_t pid, int *status, int options);
功能:阻塞等待子进程退出,回收资源
参数:pid:>0 指定子进程进程号
=-1任意子进程
=0 等待其组ID等于调用进程的组ID的任一子进程
<-1等待其组ID等于pid的绝对值的任一子进程
status:同上
options:WNOHANG:不阻塞
0:阻塞
返回值:正常:结束的子进程的进程号
使用选项WNOHANG且没有子进程结束时:0
出错:-1
waitpid(-1,NULL,0) ==> wait(NULL);