系统编程
系统调用: 内核提供的函数
库调用: 程序库中的函数
1.用read和write实现的cp指令
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char *argv[]){
char buf[1024];
int n = 0;
int fd1 = open(argv[1], O_RDONLY);
if(fd1 == -1){
perror(“open argv[1] error”);
exit(1);
}
int fd2 = open(argv[2], O_RDWR|O_CREAT|O_TRUNC, 0664);/* 创建文件最终权限 mode &~umask, umask = 0002 */
if(fd2 == -1){
perror(“open argv[2] error”);
exit(1);
}
while((n = read(fd1, buf, 1024)) != 0){
write(fd2, buf, n);
}
close(fd1);
close(fd2);
return 0;
}
用 read/write
实现的 copy
和 fgetc/fputc
实现的 cp
对比:
原因分析:
read/write
这块,每次写一个字节,会疯狂进行内核态和用户态的切换,所以非常耗时。
fgetc/fputc
,有个缓冲区,4096
,所以它并不是一个字节一个字节地写,内核和用户切换就比较少
预读入,缓输出机制。
所以系统函数并不是一定比库函数牛逼,能使用库函数的地方就使用库函数。
标准 IO
函数自带用户缓冲区,系统调用无用户级缓冲。系统缓冲区是都有的。
文件描述符
文件描述符是指向一个文件结构体的指针
PCB 进程控制块:本质 结构体。
成员:文件描述符表。
文件描述符: 0/1/2/3/4... /1023
表中可用的最小的。
0 - STDIN_FILENO
1 - STDOUT_FILENO
2 - STDERR_FILENO
最大打开文件数:一个进程默认打开文件的个数1024.
命令查看 ulimit -a 查看 open files 对应值。 默认为1024
也可以通过修改系统配置文件永久修改该值,不建议。
阻塞和非阻塞
阻塞、非阻塞: 是设备文件、网络文件的属性。
阻塞请求:A调用B ,A一直等着B的返回,别的事情什么也不干。
非阻塞请求:A调用B,A不用一直等着B的返回,先去忙别的事情了。
产生阻塞的场景。 读设备文件。读网络文件。(读常规文件无阻塞概念。)
/dev/tty
– 终端文件。
open("/dev/tty", O_RDWR|O_NONBLOCK)
— 设置 /dev/tty
非阻塞状态。 (默认为阻塞状态)
阻塞状态,从标准输入读,写到标准输出
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
char buf[10];
int n;
n = read(STDIN_FILENO, buf, 10);
if (n < 0){
perror("read STDIN_FILENO");
exit(1);
}
write(STDOUT_FILENO, buf, n);
return 0;
}
执行程序,会发现程序再阻塞等待输入。
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MSG_TRY "try again\n"
#define MSG_TIMEOUT "time out\n"
int main(){
char buf[10];
int fd, n, i;
fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
if (fd < 0){
perror("open /dev/tty");
exit(1);
}
printf("open /dev/tty ok... %d\n", fd);
for (i = 0; i < 5; i++){
n = read(fd, buf, 10);
if (n > 0){
break;
}
if (errno != EAGAIN){
perror("read /dev/tty");
exit(1);
}else {
write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
sleep(2);
}
}
if (i == 5){
write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT));
} else{
write(STDOUT_FILENO, buf, n);
}
close(fd);
return 0;
}
运行结果为
ymyy@ymyy-virtual-machine:~/systemcode$ ./nonblock_readtty
open /dev/tty ok... 3
try again
try again
try again
try again
try again
time out
fcntl
fcntl用来改变一个【已经打开】的文件的访问控制属性,重点掌握两个参数的使用, F_GETFL, F_SETFL
fcntl:
int (int fd, int cmd, …)
fd 文件描述符 cmd 命令,决定了后续参数个数
int flags = fcntl(fd, F_GETFL);
flags |= O_NONBLOCK
fcntl(fd, F_SETLF, flags);
获取文件状态:F_GETFL
设置文件状态:F_SETFL
终端文件默认阻塞读,用fcntl将其改为非阻塞读。
#include<stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#define MSG_TRY "try again\n"
int main(void)
{
char buf[10];
int flags, n;
flags = fcntl(STDIN_FILENO, F_GETFL); //获取stdin属性信息
if(flags == -1){
perror("fcntl error");
exit(1);
}
flags |= O_NONBLOCK;
int ret = fcntl(STDIN_FILENO, F_sETF, flags);//设置终端文件状态
if(ret == -1){
perror("fcntl error");
exit(1);
}
}
结果为: 非阻塞读取
ymyy@ymyy-virtual-machine:~/systemcode$ ./fcntl
try again
try again
try again
try again
try again
24452
try again
24452
##lseek函数
off_t lseek(int fd, off_t offset, int whence);
参数:
fd
:文件描述符
offset
: 偏移量,就是将读写指针从 whence
指定位置向后偏移 offset
个单位
whence
:起始偏移位置: SEEK_SET/SEEK_CUR/SEEK_END
返回值:
成功:较起始位置偏移量
失败: -1 errno
应用场景:
-
文件的“读” 、 “写” 使用同一偏移位置。
-
使用 lseek 获取文件大小
-
使用 lseek 拓展文件大小:要想使文件大小真正拓展,必须引起 IO 操作。
使用 truncate
函数,直接拓展文件。 int ret = truncate("dict.cp", 250)
;
lseek
示例,写一个句子到空白文件,完事调整光标位置,读取刚才写那个文件。
这个示例中,如果不调整光标位置,是读取不到内容的,因为读写指针在内容的末尾
代码如下:
#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fscntl>
int main(int argc, char *argv[]){
int fd, n;
char msg[] = "It's a test for lseek";
char ch;
fd = open("lseek.txt", O_RDWR|O_CREAT, 0644);
if(fd < 0){
perror("open lseek.txt error");
exit(1);
}
write(fd, msg, strlen(msg)); // 使用fd对打开的文件进行写操作,文件读写位置位于文件结尾处
lseek(fd, 0, SEEK_SET); //修改文件指针位于文件开头
while((n = read(fd, &ch, 1))){
if(n < 0){
perror("read error");
exit(1);
}
write(STDOUT_FILENO, &ch, n);
}
close(fd);
return 0;
}
ymyy@ymyy-virtual-machine:~/systemcode$ ./lseek_test
It's a test for lseekymyy@ymyy-virtual-machine:~/systemcode$
ymyy@ymyy-virtual-machine:~/systemcode$
用lseek的偏移来读取文件大小:
#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
int main(int argc, char *argv[]){
int fd = open(argv[1], O_RDWR);
if(fd == -1){
perror("open error");
exit(1);
}
int lenth = lseek(fd, 0, SEEK_END);
printf("file size: %d\n", lenth);
close(fd);
return 0;
}
ymyy@ymyy-virtual-machine:~/systemcode$ make
gcc lseek_test1.c -o lseek_test1
ymyy@ymyy-virtual-machine:~/systemcode$ ./lseek_test1 ./fcntl.c
file size: 1041
ymyy@ymyy-virtual-machine:~/systemcode$ ll fcntl.c
-rw-rw-r-- 1 ymyy ymyy 1041 6月 8 23:27 fcntl.c
修改偏移量,可以扩展文件。但是要使文件大小真正扩展,必须IO操作。拓展文件直接使用 truncate
,简单粗暴:使用 truncate
函数,直接拓展文件。 int ret = truncate("dict.cp", 250)
;
##传入传出参数
传入参数:
-
指针作为函数参数。
-
同常有 const 关键字修饰。
-
指针指向有效区域, 在函数内部做读操作。
传出参数:
-
指针作为函数参数。
-
在函数调用之前,指针指向的空间可以无意义,但必须有效。
-
在函数内部,做写操作。
-
函数调用结束后,充当函数返回值。
传入传出参数:
-
指针作为函数参数。
-
在函数调用之前,指针指向的空间有实际意义。
-
在函数内部,先做读操作,后做写操作。
-
函数调用结束后,充当函数返回值