学自电子科技大学和黑马程序员
文档中的函数都可以用 man funcName
查看介绍
- 系统调用使用
man 2 funcName
查看 - 库函数使用
man 3 funcName
查看
Linux初识
UNIX和Linux
C语言是UNIX的副产品!
80年代UNIX版本的剧增以及各种UNIX版本之间的差别不断扩大导致了以美国政府为代表许多用户要求对其标准化,以增强各种应用程序在这些UNIX操作系统之间的可移植性
重要的UNIX标准包括:ANSI C、IEEE POSIX等
UNIX标准只是对应用程序接口进行统一(内部实现机制则留给操作系统开发者自行实现)
ANSI C
- ANSI C是美国国家标准协会(ANSI)于1983年发布的C语言标准
- 1989年,此标准被采纳为国际标准ISO/IEC 9899:1990
- ISO C标准现旨在提供应用程序的可移植性,使其能适应于不同的操作系统,而不仅仅是UNIX操作系统
POSIX
- 1986年,IEEE制定了IEEE P1003标准,这套标准被称为POSIX(Potable Operating System Interface)
- POSIX定义了一整套的应用程序接口,包括系统调用、库函数、公共命令
- POSIX标准希望在源代码级别保证应用程序可移植性
Stallman在 1983 年发起了GNU计划,GNU是“GNU‘s Not Unix”的递归缩写,其目标是创建一个完全自由的类Unix操作系统,GNU计划也开发了大批其他的自由软件,例如 Emacs 、Glibc、GCC、BASH等
1985年Stallman又创立了自由软件基金会(Free Software Foundation)来为GNU计划提供技术、法律以及财政支持
自由软件并不是指“免费”的,而是指具有“自由度”的软件。什么是自由度呢?也就是使用者运行、复制、发布、研究、修改和改进该软件的自由
GNU通用公共许可协议(GNU GPL)是一个广泛被使用的自由软件许可协议条款,最初Stallman为GNU计划而撰写,GPL授予程序接受人以下权利,或称“自由”:
- 以任何目的运行此程序的自由;
- 再发行复制件的自由;
- 改进此程序,并公开发布改进的自由
1984年”,安德鲁·斯图尔特·塔能鲍姆( Andrew Stuart Tanenbaum )自己编写了兼容于UNIX的Minix系统,用于教学
1991年,芬兰郝尔辛基大学研究生林纳斯·托瓦兹(Linus Torvalds)受Minix系统影响,开发了针对386机器的Linux内核
1991年Linux的第一个版本公开发行时,GNU计划已经完成除了操作系统内核之外的大部分软件(其中包括了shell程序,C语言程序库以及C语言编译器)。 Linus Torvalds及其他早期Linux开发人员加入了这些工具,而完成了Linux操作系统
Linux是在GNU通用公共许可证下发行,它却不是GNU计划的一部分
UNIX是可以应用从大型计算机到普通PC机等多种不同的平台上,是应用面最广、影响力最大的操作系统。
Linux是一种外观和性能与UNIX相同或更好的操作系统,但Linux不源于任何版本的UNIX的源代码,是一个类似于UNIX的产品
Linux遵循POSIX规范,成功的模仿了UNIX系统和功能,更具体地讲,Linux兼容于System V以及BSD UNIX:
- 对于System V,应用程序源代码在Linux下重新编译之后就可以运行
- 对于BSD UNIX,应用程序可执行文件可以直接在Linux环境下运行
Linux体系结构
内核的功能
- 内存管理
- 内存分配调用: 包括静态分配方式、动态分配方式;
- 内存保护:确保每个程序在自己的内存空间运行、互不干扰。方法是使用界限寄存器或存储保护键;
- 地址映射:实现程序的逻辑地址与存储器的物理地址之间的映射功能;
- 内存扩充:从逻辑上扩充物理内存,以允许比物理内存更大的程序在机器内运行,为此操作系统必须具有:请求调入功能与置换功能
- 进程管理
- 进程控制:包括进程创建、进程撤销、进程阻塞、进程唤醒
- 进程协调:由于进程运行的异步性,因此进程同步的任务是对诸进程的运行协调,包括两种方式:进程互斥方式与进程同步方式;
- 进程通信:主要完成同一台机器上不同进程间通信和不同机器上进程间的通信,以共同完成一相同的任务;
- 进程调度:操作系统按照一定的规则对等待运行的多道程序进行调度,以保证每个程序都能有机会得到运行,并最终完成
- 文件管理
- 文件存储空间的管理:为每一文件分配必要的外存空间。为提高外部存储空间的利用率,系统应设置相应的数据结构,用于记录文件存储空间的使用情况;
- 目录管理:为了方便对用户的文件进行管理,对文件系统建立一定结构的目录结构,同时要求快速的目录查询手段;
- 文件的读、写管理和存取控制:利用一定的系统调用对文件进行读写操作。同时,为防止系统中的文件被非法访问和窃取,文件系统中必须提供有效存取控制功能;
- 设备管理
- 缓冲管理:管理各种类型的缓冲区,如字符缓冲区和块缓冲区,以缓和CPU和I/O速度不匹配的矛盾,最终达到提高CPU和I/O设备的利用率,进而提高系统吞吐量的目的;
- 设备分配:根据用户的I/O请求,为之分配其所需要的设备;
- 设备处理:又称为设备驱动程序,任务是实现CPU和设备控制器之间的通信;
- 设备独立性和虚拟设备:一方面保证用户程序独立于物理设备,另一方面保证多个进程能并发地共享同一个设备;
UNIX/Linux操作系统结构:
系统调用与库函数
操作系统用户接口:
- 命令接口:以命令形式呈现在用户面前,方便用户直接或间接控制自己的作业
程序接口:为应用程序使用系统功能而设置,是应用程序取得操作系统服务的唯一途径。由一系列系统调用组成,每一个系统调用都是一个能完成特定功能的子程序。
图形接口:采用了图形化的操作界面,将各种应用程序和文件,直观、逼真地表示出来。
系统调用是内核提供的程序接口,是应用程序和硬件设备之间的中间层:
为应用程序提供了系统服务和硬件抽象能力,例如,当需要读文件时,应用程序可以不管磁盘类型和介质,甚至不用去管文件所在的文件系统到底是哪种类型;
系统调用保证了系统的稳定和安全
每个进程都运行在虚拟系统中
Research UNIX系统的第7个版本提供了大给50个系统调用,4.4BSD提供了大约110个,SVR4有大约120个。Linux根据不同的版本有240到260个系统调用的接口
man 2 syscalls
查看所有的系统调用接口
系统调用的类型:
- 文件操作类系统调用: 如打开、创建、读取、删除、修改文件;
- 进程控制类系统调用:如创建进程、设置或获取进程属性等;
- 通信类系统调用:创建进程间的通信连接,发送、接收消息,或其他的通信方式;
- 设备管理类系统调用:打开、关闭和操作设备;
- 信息维护类系统调用:在用户程序和OS之间传递信息。例如,系统向用户程序传送当前时间、日期、操作系统版本号等。
系统调用与C库函数:
系统调用和C库函数之间并不是一一对应的关系,可能几个不同的函数会调用到同一个系统调用:
malloc函数和free函数都是通过sbrk系统调用来扩大或缩小进程的堆栈空间;
execl、execlp、execle、execv、execvp和execve函数都是通过execve系统调用来执行一个可执行文件
并非所有的库函数都会调用系统调用,例如,printf函数会调用write系统调用以输出一个字符串,但strcpy和atoi函数则不使用任何系统调用
C库IO函数的工作流程
库函数和系统调用的关系
UNIX/Linux软件层次架构:
一般而言,应用程序使用API而不是直接使用系统调用来编程
进程UNIX/Linux的C库遵循POSIX规范,以库函数的形式实现了POSIX API(在API中使用系统调用完成相应功能)。
参考:/usr/include/asm/unistd.h
(没有的话,用 find / -name "unistd.h"
找一下,出来一大堆,感觉应该是/usr/include/unistd.h
)
Linux命令
文件目录命令
浏览目录命令:ls
、pwd
目录操作命令:cd
、 mkdir
、 rmdir
浏览文件命令:cat
、more
、less
、head
、tail
文件操作命令:cp
、rm
、 mv
、find
、 grep
、tar
命令名称:ls
- 命令英文原意:
list
- 功能描述:显示目录文件
- 语法:ls 选项[-ald] [文件或目录]
-a
显示所有文件,包括隐藏文件-l
详细信息显示-d
查看目录属性
命令名称:pwd
- 命令英文原意:
print working directory
- 语法:pwd
- 功能描述:显示当前所在的工作目录
命令名称:cd
- 命令英文原意:
change directory
- 语法:
cd [目录]
- 功能描述:切换目录
- 范例:
$ cd / 切换到根目录
$ cd .. 回到上一级目录
命令名称:mkdir
- 命令英文原意:
make directory
- 语法:
mkdir [目录名]
- 功能描述:创建新目录
- 范例:
$ mkdir newdir
命令名称:rmdir
- 命令英文原意:
remove directory
- 语法:
rmdir [目录名]
- 功能描述:删除空目录
- 范例:
$ rmdir newdir
命令名称:cat
- 命令英文原意:
concatenate and display files
- 功能描述:连接文件并打印到标准输出设备上
- 选项:
-E
在每一行的末尾显示$
-n
为显示行添加行号
- 参数:指定要连接的文件列表。
- 范例:
cat m1
(在屏幕上显示文件ml的内容)cat m1 m2
(同时显示文件ml和m2的内容)
命令名称:more
- 语法:
more [文件名]
(空格)或f
显示下一页(Enter)
显示下一行q或Q
退出
- 功能描述:分页显示文件内容
- 范例:
$ more /etc/services
less
命令的用法与more命令类似,也可以用来浏览超过一页的文件。所不同的是less命令除了可以按空格键向下显示文件外,还可以利用上下键来卷动文件。
head
命令:用于显示文件前几行的内容
- 格式:
head [参数] <文件名>
-n
:显示前n行,不指定此参数显示前10行[root@linux root]# head /etc/passwd
tail
命令:用于显示文件后几行的内容
- 格式:
tail [参数] <文件名>
-n
:显示后n行,不指定此参数显示后10行+n
:从第n行显示到文件尾-F
:用于跟踪显示不断增长的文件结尾内容(通常用于显示日志文件)。[root@linux root]# tail /etc/passwd
cp
(copy)命令可以将文件从一处复制到另一处。一般在使用cp命令时将一个文件复制成另一个文件或复制到某目录时,需要指定源文件名与目标文件名或目录。
- 格式:
cp [参数] <源文件路径> <目标文件路径>
-p
:连同文件的属性一起复制,而非使用默认方式,常用于备份-i
:若目标文件已经存在时,在覆盖时会先询问操作的进行-r
:递归持续复制,用于目录的复制行为-u
:目标文件与源文件有差异时才会复制
- 范例:
- 将test1.txt复制成test2.txt:
[test@linux test]$ cp test1.txt test2.txt
- 将test3.txt复制到/tmp目录中:
[test@linux test]$ cp test3.txt /tmp
- 目录test1拷贝:
[test@linux test]$ cp –r test1 test2
- 将test1.txt复制成test2.txt:
rm
(remove)命令:删除文件或目录
- 格式:
rm [参数] <目标文件路径>
-f
:就是force的意思,忽略不存在的文件,不会出现警告消息-i
:互动模式,在删除前会询问用户是否操作-r
:递归删除,最常用于目录删除,它是一个非常危险的参数
- 范例:
- 删除myfiles文件:
[test@linux test]$ rm myfiles
- 删除当前目录下的所有文件:
[test@linux test]$ rm *
- 递归删除某个目录所有内容:
`[test@linux test]$ rm -r myfolder
- 强迫删除所有后缀名为txt文件:
[test@linux test]$ rm –f *.txt
- 删除当前目录下的所有文件:
[test@linux test]$ rm –i *
(删除文件时会询问,可按Y或N键表示允许或拒绝删除文件)
- 删除myfiles文件:
find
命令:用来寻找文件或目录
- 格式:
find 路径 [参数]
-name filename
:找出文件名为filename的文件-size [+-]SIZE
:找出比SIZE
还要大(+)或小(-)的文件-tpye TYPE
:查找文件的类型为TYPE的文件,TYPE的值主要有:一般文件(f)、设备文件(b、c)、目录(d)、连接文件(l)、socket(s)、FIFO管道文件(p);-perm mode
:查找文件权限刚好等于mode的文件,mode用数字表示,如0755
grep
命令(Globally search a Regular Expression and Print)
- 功能:在文件中搜索匹配的字符并进行输出
- 格式:
grep[参数] <要找的字串> <要寻找字串的源文件>
-a
:将binary
文件以text
文件的方式查找数据-c
:计算找到“查找字符串”的次数-i
:忽略大小写的区别,即把大小写视为相同-v
:反向选择,即显示出不包含‘查找字符串’内容的那一行
tar
(tape archive)命令能够将用户所指定的文件或目录打包成一个文件,也可以通过指定参数开启压缩/解压缩功能
- 格式:
tar [参数] <文件>
-c
:新建打包文件-t
:查看打包文件的内容含有哪些文件名-x
:解打包或解压缩的功能,可以搭配-C
(大写)指定解压的目录,注意-c,-t,-x
不能同时出现在同一条命令中-j
:通过bzip2
的支持进行压缩/解压缩-z
:通过gzip
的支持进行压缩/解压缩-v
:在压缩/解压缩过程中,将正在处理的文件名显示出来-f filename
:filename为要处理的文件-C dir
:指定压缩/解压缩的目录dir
,dir
必须已存在
范例:
将当前目录的所有文件打包成test.tar:
[root@linux test]# tar -cvf test.tar *
将当前目录的所有文件打包成test.tar,再用gzip命令压缩:
[root@linux test]# tar -czvf test.tar.gz *
查看test.tar文件中包括了哪些文件:
[root@linux ljr]# tar -tf test.tar
将test.tar解打包:
[root@linux test]# tar -xvf test.tar
将foo.tar.gz解压缩并解打包:
[root@linux test]# tar -xzvf foo.tar.gz
-C
的示例,打包Cprogram内的文件,不把Cprogram
文件夹名也打包
进程控制类命令
查看系统中的进程命令:ps
、top
控制系统中的进程命令:kill
、killall
、 nice
、 renice
进程后台运行命令 &
进程的挂起和恢复
程序和进程的区别
程序是一个包含可执行代码的文件,它放在磁盘等介质上。
当程序被操作系统装载到内存并分配给它一定资源后,此时可称为进程。
程序是静态概念,进程是动态概念。
进程状态
ps
命令是用来显示系统瞬间的进程信息,它可以显示出在用户输入ps命令时系统的进程及进程的相关信息。
- 格式:
ps [参数]
-l
长格式输出-u
按用户名和启动时间的顺序来显示进程-j
用任务格式来显示进程-f
用树形格式来显示进程-a
显示所有用户的所有进程(包括其它用户)-x
显示无控制终端的进程-r
显示运行中的进程
top
命令
功能:动态监视系统任务的工具,输出结果是连续的
格式:
top [参数]
-b
以批量模式运行,但不能接受命令行输入-c
显示命令行,而不仅仅是命令名-d N
显示两次刷新时间的间隔,比如-d 5
,表示两次刷新间隔为5秒-i
禁止显示空闲进程或僵尸进程-n NUM
显示更新次数,然后退出。比如-n 5
,表示top更新5次数据就退出-p PID
仅监视指定进程的ID;PID
是一个数值-q
不经任何延时就刷新-s
安全模式运行,禁用一些效互指令-S
累积模式,输出每个进程的总的CPU时间
范例:
kill
命令,该命令用于向某个进程(通过PID标识)传送一个信号,它通常与ps
和jobs
命令一起使用
- 格式:
kill –signal PID
,常用的signal
参数如下:1:SIGHUP
,启动被终止的进程2:SIGINT
,相当于输入ctrl+c
,中断一个程序的进行9:SIGKILL
,强制中断一个进程的进行15:SIGTERM
,以正常的结束进程方式来终止进程17:SIGSTOP
,相当于输入ctrl+z
,暂停一个进程的进行
- 范例:
- 以正常的结束进程方式来终止第一个后台工作进程
kill -SIGTERM %1
- 重新启动进程ID为PID的进程
kill -SIGHUP PID
- 以正常的结束进程方式来终止第一个后台工作进程
killall
命令使用进程的名称来杀死进程,使用此指令可以杀死一组同名进程
使用kill命令可以杀死指定进程PID的进程,如果要根据进程名称找到需要杀死的进程,还需要在之前使用ps等命令再配合grep来查找进程,而killall把这两个过程合二为一
- 用法:
killall [参数] <正在运行的进程名>
-e
:对长名称进行精确匹配-I
:忽略大小写的不同-p
:杀死进程所属的进程组-i
:交互式杀死进程,杀死进程前需要进行确认-l
:打印所有已知信号列表-q
:如果没有进程被杀死,则不输出任何信息-r
:使用正规表达式匹配要杀死的进程名称-s
:用指定的进程号代替默认信号“SIGTERM”-u
:杀死指定用户的进程
- 使用范例:
[root@localhost test]# killall game
nice
命令:允许在默认优先级的基础上进行增大或减小的方式来运行命令
- 格式:
nice [参数] <command [arguments...]>
- command 是系统中任意可执行文件的名称
-n
,--adjustment
指定程序运行优先级的调整值- 优先级的调整值范围为
-20 ~ 19
,其中数值越小优先级越高,数值越大优先级越低 - 若 nice命令未指定优先级的调整值,则以缺省值10来调整程序运行优先级,既在命令通常运行优先级基础之上增加10
- 使用范例:
[root@host root]# nice -n -5 myprogram&
在后台以通常运行优先级-5
的优先级运行myprogram
renice
命令:改变一个正在运行的进程的nice
值
- 格式:
renice [参数] <pid>
-n
指定程序运行优先级的调整值
- 使用范例:
[root@host root]# renice -5 777
将正在运行的PID为777的进程nice值改为-5
后台运行程序的&
命令
[root@host root]# cp –r /usr/* test &
将/usr 目录下的所有子目录及文件复制到/root/test目录下的工作放到后台运行
进程的中止(挂起)和终止
- 挂起(Ctrl+Z)
- 终止(Ctrl+C)
进程的恢复
- 恢复到前台继续运行
fg
:fg [n]
- 恢复到后台继续运行
bg
:bg [n]
- 查看被挂起的进程
jobs
用户和权限管理
用户管理类命令:useradd
、usermod
、 passwd
、userdel
、 su
、id
、 whoami
、w
、 finger
用户组管理类命令:groupadd
、 groupmod
、groupdel
文件权限管理类命令:chmod
、 chown
、chgrp
Linux用户分为三类:
- 超级用户:拥有最高权限
- 系统用户:与系统服务相关,但不能用于登录
- 普通用户:由超级用户创建并赋予权限,只能操作其拥有权限的文件和目录,只能管理自己启动的进程
用户信息:
- 用户名:唯一,由字母、数字和符号组成。
- 口令
- 用户
ID(uid)
:每个用户拥有的唯一的识别号码。超级用户为0,系统用户1-499,普通用户从500开始 - 用户组
id(gid)
- 用户主目录
- 全称:用户帐户的附加信息,可以为空
- 登录Shell:默认使用Bash
与用户相关的文件:
- 用户帐号信息文件
/etc/passwd
- 文件中每一行为一个用户的信息
- 文件中各字段从左到右依次为:用户名、口令、用户ID、用户组、全称、用户主目录和登录Shell。
- 口令字段用
x
来填充,加密后的口令保存在/etc/shadow
文件中。
- 用户口令信息文件
/etc/shadow
shadow
文件只有超级用户才能查看并修改其内容,且加密(AES-256)存储。
用户组:
- Linux将相同特性的用户划归为同一用户组,可以大大简化用户的管理,方便用户之间文件的共享,任何用户都至少属于一个用户组。
- 一个用户只能属于一个用户组,但可以同时属于多个附加组。用户不仅拥有其用户组的权限,还同时拥有其附加组的权限。
- 用户组包括系统用户组与私人用户组
与用户组相关的文件:
- 用户组账号信息文件
/etc/group
- 每一行为一个用户组信息
- 文件中各字段从左到右依次为:
用户组名、口令、用户组ID和附加用户列表
。
- 用户组口令信息文件
/etc/gshadow
- 同
/etc/shadow
- 同
useradd
命令:新建用户帐号(超级用户可用)
- 格式:
useradd [参数] <用户名>
-d
指定用户登入时的主目录-e
账号终止日期-g
指定账户所属的用户组-G
指定账户所属的附加组-s
指定账户登录后所使用的shell-u
指定用户ID号
- 举例:新建一个用户zhangsan,用户组为net04:
useradd –g net04 zhangsan
passwd
命令:设置或修改用户的口令以及口令的属性
- 格式:
passwd [参数] <用户>
-d
删除用户的口令-l
暂时锁定指定的用户帐号-u
解除指定用户帐号的锁定-s
显示指定用户帐号的状态
- 范例:
- 设置与修改属性
passwd zhangsan
- 删除口令
passwd –d zhangsan
- 锁定用户帐号
passwd –l zhangsan
- 解锁用户帐号
passwd –u zhangsan
- 显示用户帐号状态
passwd –s zhangsan
- 设置与修改属性
usermod
命令:修改用户的属性(超级用户可用)
- 格式:
usermod [参数] <用户名>
-d
指定用户登入时的主目录-e
账号终止日期-g
指定账户所属的用户组-G
指定账户所属的附加组-s
指定账户登录后所使用的shell-u
指定用户ID号-l
新用户名(用于修改用户名)
- 举例:将zhangsan改为zhangs :
usermod –l zhangs zhangsan
userdel
命令:删除指定的用户帐号(超级用户可用)
- 格式:userdel [参数] <用户名>
-r
:不仅删除此用户帐号,而且删除用户主目录及本地邮件存储的目录或文件-f
:删除用户登入目录以及目录中所有文件
- 如果删除用户属于私人组群,而该组群没有其他用户,组群也一并删除。
- 正在使用系统的用户不能删除。
su
命令:切换用户身份
- 格式:
su <用户名>
- 超级用户可以切换为任何普通用户,而不需要输入口令;普通用户转换为其他用户时需要输入被转换用户的口令
- 使用
exit
可以返回到本来的用户身份
id
命令:查看用户的UID、GID和用户所属用户组的信息,如果不指定用户,则显示当前用户的相关信息。
- 格式:
id <用户名>
whoami
命令:查看当前用户名
w
命令:查看当前登录系统用户和详细信息
groupadd
命令:新建组群(超级用户可用)
- 格式:
groupadd [参数] <用户组名>
-g
:指定用户组ID-o
:允许组ID号不唯一
groupmod
命令:修改指定用户组的属性(超级用户可用)
- 格式:groupmod [参数] <用户组名>
-g
:指定新的用户组ID-n
:指定新的用户组名字-o
: 允许组ID号不唯一
groupdel
命令:删除指定的用户组(超级用户可用)
- 格式:
groupdel <用户组名>
- 注意:在删除指定用户组之前必须保证该用户组不是任何用户的主要组群,否则要先删除以此用户组为主要组群的用户才可以删除该用户组
文件权限
- 读取权限:浏览文件/目录中内容的权限
- 写入权限:
- 对文件而言是修改文件内容的权限
- 对目录而言是删除、添加和重命名目录内文件的权限
- 执行权限:
- 对可执行文件而言是允许执行的权限
- 对目录而言是进入目录的权限。
文件用户分类:
- 文件所有者:建立文件和目录的用户
- 文件所有者所在组用户:文件所有者所属用户组中的其他用户
- 其他用户:既不是文件所有者,又不是文件所有组所在组的其他所有用户
- 超级用户:负责整个系统的管理和维护,拥有系统中所有文件的全部访问权限。
chmod
命令:修改文件的访问权限
- 格式:
chmod <模式> <文件>
- 模式:
- 对象:
u
文件所有者 、g
同组用户、o
其他用户 - 操作符:
+
增加、-
删除、=
赋予 - 权限:
r
读、w
写、x
执行、s
设置用户ID
- 对象:
- 模式:
- 举例:
- 取消同组用户对file文件的写入权限
chmod g-w file
- 将pict目录的访问权限设置为775
chmod 775 pict
- 设置file文件的设置用户ID位
chmod u+s file
- 取消同组用户对file文件的写入权限
chown
命令:将指定文件的拥有者改为指定的用户或用户组
- 格式:
chown [选项] <所有者/组> <文件>...
-c
:显示更改的部分的信息-f
:忽略错误信息-h
:修复符号链接-R
:处理指定目录以及其子目录下的所有文件-v
:显示详细的处理信息-deference
:作用于符号链接的指向,而不是链接文件本身- 用户是用户名或者用户ID,用户组可以是组名或者组ID
- 文件是以空格分开的要改变权限的文件列表,支持通配符
- 举例:将
ex1
的所有者由root改为hellen:chown hellen ex1
chgrp
命令:改变文件的所属用户组
- 格式:
chgrp [选项] <组> <文件>
-c
:显示更改的部分的信息-f
:忽略错误信息-h
:修复符号链接-R
:处理指定目录以及其子目录下的所有文件-v
:显示详细的处理信息-deference
:作用于符号链接的指向,而不是链接文件本身- 用户组可以是组名或者组ID
- 举例:将ex1文件所属的用户组由root改为staff:
chgrp staff ex1
编译调试
gcc概述
gcc是GNU计划的一个项目。是一个自由编译器,如今的gcc已经是一个包含众多语言的编译器了( C,C++,Ada,Object C,Java及Go等)。所以,GCC也由原来的GNU C Compiler变为
GNU Compiler Collection
GCC主要包括:
cpp
(预处理器)gcc
(c编译器)、g++
(c++编译器)等编译器binutils
等二进制工具.as
(汇编器)ld
(链接器)- …
源文件-》预处理-》编译-》汇编-》链接-》可执行
cpp -o hello.i hello.c
ccl -o hello.s hello.i
as -o hello.o hello.s
ld -o hello hello.o
gcc基础使用方法:
- gcc命令格式:
gcc [选项] <文件名>
-o filename
:指定输出文件为filename.该选项不在乎gcc产生什么输出,无论是可执行文件,目标文件,汇编文件还是预处理后的C代码- 如果没有使用
-o
选项,默认的输出结果是:可执行文件为a.out
,编译后产生的目标文件是sourcename.o
,汇编文件是sourcename.s
,而预处理后的C源代码送往标准输出
- 对于源代码
main.c
,可以通过如下命令编译成最终可执行文件(默认包含了预处理、编译、汇编及链接四个阶段):gcc main.c –o main
gcc常用编译选项:
-D
: 宏定义选项,等同于代码中的#define MACRO
,但-D
定义的宏作用于所有的源文件。#define PI 3.14159
(如果程序用到PI则用3.14159代替)gcc -DPI=3.14159 main.c
(但如果没有定义宏的话,就可以直接在编译的时候赋值再运行)
-I
头文件的搜索路径:如果用户的头文件不在gcc的搜索路径中,可以用此选项指定额外搜索路径。gcc helloworld.c –I /usr/include –o helloworld
(将/usr/inlcude
加入到文件头文件的搜索路径中)
警告选项
- 警告是针对程序结构的诊断信息,程序不一定有错误,而是存在风险,或者可能存在错误。
- 所有以
-W
开头的选项基本上均可使用-Wno-option
来关闭该警告信息 - 如
-Wunused
在某个局部变量除了声明就没再使用,或者声明了静态函数但是没有定义,或者某条语句的运算结果显然没有使用时, 编译器就发出警告。使用`-Wno-unused
可禁止该警告信息。 -w
:禁止所有警告信息.-Wall
:打开所有警告选项,输出警告信息- 通常建议打开
-Wall
,这样至少可以看出你的代码里有哪些地方可能存在问题
静态库编译和使用:
- 把
.c
编译成.o
:gcc –c increase.c –o increase.o
- 把
.o
归档成 静态库.a
:ar –r libincrease.a increase.o
- 静态库和其它源文件链接成可执行文件:
gcc main.c –L –static –o main
动态库编译:
- 生成动态链接库:
gcc -shared -fPIC -o libinc.so increase.c
- 动态链接库的名字必须以
lib
开头.so
结束,这是linux系统上的强制约束,否则无法使用该共享库 -shared
生成共享文件-fPIC
生成位置独立的代码,此类代码可以在不同进程间共享。
- 动态链接库的名字必须以
动态库的使用:
-l library
名字为library的动态链接库。事实上此动态链接库在文件系统中的名字为liblibrary .so。连接器会自动加上lib*.so。-L dir
共享库搜索目录。gcc除了会在自定义的目录中搜索共享库外,用户也自定义目录让gcc搜索。gcc main.c -o main -linc -L./
gdb概述
gdb是GNU计划开发的程序调试工具
gdb可以完成以下四个方面的功能:
- 启动程序,可以按照自定义的要求随心所欲的运行程序
- 可让被调试的程序在所指定的调置的断点处停住(断点可以是条件表达式)
- 当程序被停住时,可以检查此时程序中所发生的情况
- 动态的改变程序的执行环境
gdb的启动:
- 直接在shell中运行
gdb
命令,进入gdb界面后用file program
装载程序。 - 在shell中启动gdb并加载可执行文件
gdb <program>
- 用gdb同时调试一个运行程序和core文件(core是程序非法执行后core dump后产生的文件)
gdb program core
- 调试正在运行的进程
gdb program <processid>
- 进入gdb后用
attach <processid>
调试正在运行的进程。
- 进入gdb后用
gdb常用命令:
break
命令:
- 功能:断点设置命令
break
(缩写b
),当gdb执行到该断点时会让程序暂停运行。此时程序员可以查看运行中程序的情况。 - 格式:
break [LOCATION] [thread THREADNUM] [if CONDITION]
[LOCATION]
:linenum
(行号),如b 123
function
(函数名) ,如b main
filename:linenum
,如b increase:123
filename:function
,如b increase:main
- class:function(c++)
[thread THREADNUM]
调试多线程程序时,切换到哪个线程或者在那个线程中设置断点。break frik.c:13 thread 28
[if CONDITION]
: 当条件满足时,断点才生效。一般称为条件断点。CONDITION跟C语言一样。b 123 if index==2
当index为2时,程序在123行停下
watchpoint
命令:
watchpoint
称为观察点,当观察对象的值有变化时,程序立即停止执行。watch <expr>
:为表达式(变量)expr设置一个观察点。一旦表达式值有变化时,马上停住程序。rwatch <expr>
:当表达式(变量)expr被读时,停住程序。awatch <expr>
:当表达式(变量)的值被读或被写时,停住程序。info watchpoints
:列出当前所设置了的所有观察点
清除禁止断点或观察点:
clear [linenum] [function name]
清除所有断点,不会清除watchpointsdelete <num>
清除编号为num的断点或者watchpointdisable <num>
禁止某个断点enable <num>
开启某个断点
gdb调试命令:
step
单步调试命令,一次执行一行程序。next
单步调试命令,但跳过函数调用。finish
单步调试时直接从一个函数中返回disassemble
显示汇编代码。backtrace
或者bt
查看目前程序的堆栈情况。where
查看当前位置。up/down
向上或者向下移动一个堆栈。frame<num>
或者f
移动到第num个堆栈。- 当移动到某个堆栈时,便可以用gdb命令查看在那个堆栈中的局部变量。
Linux文件系统
一切皆文件
文件系统是以合理有效的层次结构组织的文件和目录的集合
“一切皆是文件”是 Unix/Linux 的基本哲学之一
普通文件、目录、字符设备、块设备、 套接字等在 Unix/Linux 中都是文件
类型不同的文件都是通过相同的API对其进行操作
Unix/Linux 中允许不同的文件系统共存,如 ext2, ext3, ext4, xfs, btrfs 等
文件系统 | 适用场景 | 原因 |
---|---|---|
ext2 |
U盘 | ext2不写日志,对安全性要求不高,兼容FAT |
ext3 |
对稳定性要求高的地方 | ext4稳定性不高 |
ext4 |
小文件较少 | 不支持inode动态分配 |
xfs |
小文件多 | 支持iNode动态分配 |
btrfs |
没有频繁的写操作 | 功能众多 |
通过统一的文件 I/O 系统调用API即可对系统中的任意文件进行操作而无需考虑其所在的具体文件系统格式
文件操作可以跨文件系统执行
与windows文件系统的区别:
Unix/Linux文件系统 | Windows文件系统 |
---|---|
根目录文件系统 | 多根目录文件系统 |
文件名区分大小写 | 文件名不区分大小写 |
目录路径使用正斜杠,如/home/name |
目录路径使用反斜线,如C:\Users\zxy |
没有驱动器号,一切文件都在root目录下 | 驱动器号标记分区和设备 |
无独占访问权限 | 独占访问权限 |
文件系统架构
VFS
虚拟文件系统VFS
- 虚拟文件系统是Linux 内核中的一个软件层,对内实现文件系统的抽象,允许不同的文件系统共存,对外向应用程序提供统一的文件系统接口
- 为了能够支持不同文件系统,VFS 定义了所有文件系统都支持的基本的、抽象的接口和数据结构
- 实际文件系统实现VFS 定义的抽象接口和数据结构,将自身的诸如文件、目录等概念在形式上与VFS的定义保持一致,在统一的接口和数据结构下隐藏了具体的实现细节
VFS中的数据结构:
- 超级块(super block):用于存储文件系统的控制信息的数据结构。描述文件系统的状态、文件系统类型、大小、区块数、索引节点数等,存放于磁盘的特定扇区中。
- 索引节点(inode):用于存储文件的元数据(文件的基本信息)的一个数据结构,包含诸如文件的大小、拥有者、创建时间、磁盘位置等信息。
- 目录项(dentry):目录被用来容纳文件,目录可以包含子目录,层层嵌套以形成文件路径。
- 文件对象(file):一组在逻辑上具有完整意义的信息项的系列
super block
VFS-超级块(super block):
- 超级块用来描述整个文件系统的信息。每个具体的文件系统都有各自的超级块
- VFS超级块是各种具体文件系统在安装时建立的,并在卸载时被自动删除,其数据结构是
super_block
- 所有超级块对象以双向环形链表的形式链接在一起
1 | struct super_block { |
inode
VFS-索引节点(inode):
- 文件系统处理文件所需要的所有信息都放在称为索引节点的数据结构inode中
- 具体文件系统的索引节点是存放在磁盘上的,是一种静态结构,要使用它,必须调入内存,填写VFS的索引节点,因此,也称VFS索引节点是动态节点
- 文件名可以随时更改,但是索引节点对文件是唯一的,并且随文件的存在而存在
- 每个inode节点的大小,一般是128字节或256字节
1 | struct inode { |
dentry
VFS-目录项对象(dentry):
- 每个文件除了有一个索引节点inode数据结构外,还有一个目录项dentry数据结构。
dentry
结构代表的是逻辑意义上的文件,描述的是文件逻辑上的属性,目录项对象在磁盘上并没有对应的映像inode
结构代表的是物理意义上的文件,记录的是物理上的属性,对于一个具体的文件系统,其inode
结构在磁盘上就有对应的映像- 一个索引节点对象可能对应多个目录项对象
1 | struct dentry { |
file
VFS-文件对象(file):
- 进程是通过文件描述符来访问文件的
- Linux中专门用了一个
file
文件对象来保存打开文件的文件位置,这个对象称为打开的文件描述(open file description) - 文件描述符是用来描述打开的文件的。每个进程用一个
files_struct
结构来记录文件描述符的使用情况,这个files_struct
结构称为用户打开文件表,它是进程的私有数据 - file结构中主要保存了文件位置,此外,还把指向该文件索引节点的指针也放在其中。file结构形成一个双链表,称为系统打开文件表。
1 | struct file { |
1 | struct files_struct { |
VFS数据结构之间的关系
- 超级块是对一个文件系统的描述
- 索引节点是对一个文件物理属性的描述
- 目录项是对一个文件逻辑属性的描述
- 一个进程所处的位置是由
fs_struct
来描述的,而一个进程(或用户)打开的文件是由files_struct
来描述的,而整个系统所打开的文件是由file
结构来描述
文件系统的注册和注销
- 当内核被编译时,就已经确定了可以支持哪些文件系统,这些文件系统在系统引导时,在 VFS 中进行注册。
- VFS的初始化函数用来向VFS注册,即填写文件注册表
file_system_type
数据结构 - 注册调用
register_filesystem()
函数 - 注销即删除一个
file_system_type
结构,需调用unregister_filesystem()
函数
1 | struct file_system_type { |
文件系统的安装
- 安装一个文件系统实际上是安装一个物理设备
- 自己(一般是超级用户)安装文件系统时,需要指定三种信息:文件系统的名称、包含文件系统的物理块设备、文件系统在已有文件系统中的安装点。
$ mount -t iso9660 /dev/hdc /mnt/cdrom
其中,iso9660
是光驱文件系统的名称,/dev/hdc
是包含文件系统的物理块设备,/mnt/cdrom
就是将要安装到的目录,即安装点。- 在用户程序中要安装一个文件系统则可以调用
mount()
系统调用。安装过程主要工作是创建安装点对象,将其挂接到根文件系统的指定安装点下,然后初始化超级块对象,从而获得文件系统基本信息和相关的操作。
文件系统的卸载
- 如果文件系统中的文件当前正在使用,该文件系统是不能被卸载的
- 查看对应的 VFS 超级块,如果该文件系统的 VFS 超级块标志为“脏”,则必须将超级块信息写回磁盘
- 之后,对应的 VFS 超级块被释放,vfsmount 数据结构将从vfsmntlist 链表中断开并被释放
- 具体的实现代码为
fs/super.c
中的sys_umount()
函数
文件IO操作
系统调用:操作系统提供给用户程序调用的一组“特殊”接口,用户程序可以通过这组“特殊”接口来获得操作系统内核提供的服务
Q:为什么用户程序不能直接访问系统内核提供的服务?
A:为了更好地保护内核空间,将程序的运行空间分为内核空间和用户空间(也就是常称的内核态和用户态),它们分别运行在不同的级别上,在逻辑上是相互隔离的。因此,用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,它们只能在用户空间操作用户数据,调用用户空间的函数。
进行系统调用时,程序运行空间从用户空间进入内核空间,处理完后再返回到用户空间
系统调用并不是直接与程序员进行交互的,它仅仅是一个通过软中断机制向内核提交请求,以获取内核服务的接口。
在实际使用中程序员调用的通常是用户编程接口——API
Linux中的系统调用包含在Linux的libc库中,通过标准的C函数调用方法可以调用
系统命令相对API更高了一层,它实际上是一个可执行程序,它的内部调用了用户编程接口(API)来实现相应的功能
文件描述符:
Q:内核如何区分和引用特定的文件?
A:通过文件描述符。文件描述符是一个非负的整数,它是一个索引值,并指向在内核中每个进程打开文件的记录表。当打开一个现存文件或创建一个新文件时,内核就向进程返回一个文件描述符;当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数。
一个进程启动时,通常会打开3个文件:
- 标准输入 描述符为 0
- 标准输出 描述符为 1
- 标准出错处理 描述符为 2
文件描述符和PCB
文件IO函数
open()
用于打开或创建文件,可以指定文件的属性及用户的权限等各种参数creat()
打开一个文件,如果文件不存在,则创建它close()
用于关闭一个被打开的文件。当一个进程终止时,所有被它打开的文件都由内核自动关闭,很多程序都使用这一功能而不显示地关闭一个文件read()
用于将从指定的文件描述符中读出的数据放到缓存区中,并返回实际读入的字节数。若返回0,则表示没有数据可读,即已达到文件尾。读操作从文件的当前指针位置开始write()
用于向打开的文件写数据,写操作从文件的当前指针位置开始。对磁盘文件进行写操作,若磁盘已满或超出该文件的长度,则write()函数返回失败
open函数
查看详细信息 man 2 open
头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
函数原型
int open(const char* pathname, int flags);
int open(const char* pathname, int flags, mode_t mode);
函数说明
- 参数
pathname
指向欲打开的文件路径字符串。 - 下列是参数
flags
常用的标识:O_RDONLY
以只读方式打开文件O_WRONLY
以只写方式打开文件O_RDWR
以可读写方式打开文件。上述三种标识是互斥的,也就是不可同时使用,但可与下列的标识利用OR(|)运算符组合。O_CREAT
若欲打开的文件不存在则自动建立该文件。O_TRUNC
若文件存在并且以可写的方式打开时,此标识会令文件长度清为0,而原来存于该文件的资料也会消失。O_APPEND
当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。O_NONBLOCK
以不可阻断的方式打开文件,也就是无论有无数据读取或等待,都会立即返回进程之中。O_EXCL
: 如果同时指定O_CREAT
,而该文件又是存在的,报错;也可以测试一个文件是否存在,不存在则创建。
mode
:文件权限,如0664
- 返回值:文件打开成功返回文件的描述符,失败返回
-1
creat函数
man 2 creat
头文件
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
函数原型:int creat(const char * pathname, mode_t mode)
- 参数
pathname
指向欲建立的文件路径字符串。 creat()
相当于使用下列的调用方式调用open(const char * pathname ,(O_CREAT|O_WRONLY|O_TRUNC))
creat()
会返回新的文件描述词,若有错误发生则会返回-1,并把错误代码设给errno
附加说明
creat
函数的一个不足之处是它以只写方式打开所创建的文件
close函数
man 2 close
头文件:#include<unistd.h>
函数原型:int close(int fd);
函数说明
- 当使用完文件后若已不再需要则可使用close()关闭该文件,close()会让数据写回磁盘,并释放该文件所占用的资源。
- 参数
fd
为先前由open()或creat()所返回的文件描述符; - 返回值:若文件顺利关闭则返回0,发生错误时返回-1。
- 附加说明:虽然在进程结束时,系统会自动关闭已打开的文件,但仍建议自行关闭文件,并检查返回值。
read函数
man 2 read
头文件:#include<unistd.h>
函数原型:ssize_t read(int fd, void * buf, size_t count);
函数说明
read()
会把参数fd
所指的文件传送count
个字节到buf
指针所指的内存中。- 若参数
count
为0,则read()
不会有作用并返回0。 - 返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或是无可读取的数据,此外文件读写位置会随读取到的字节移动;
- 返回值
ssize_t
(有符号整型):成功返回读取的字节数,出错返回-1
;若返回0表示文件读完;
read
函数实际读到的字节数少于要求读的字节数时:
- 读普通文件,在读到要求字节数之前就到达文件尾
- 当从终端设备读,通常一次最多读一行;
- 当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数;
- 某些面向记录的设备,如磁带,一次最多返回一个记录;
读操作完成后,文件的当前位置将从读之前的位置加上实际读的字节数
当有错误发生时则返回-1,错误代码存入errno
中,而文件读写位置则无法预期
write函数
man 2 write
头文件:#include<unistd.h>
函数原型:ssize_t write (int fd, const void * buf, size_t count);
函数说明
write()
会把参数buf
所指的内存写入count
个字节到参数fd
所指的文件内。当然,文件读写位置也会随之移动;- 返回值:如果顺利
write()
会返回实际写入的字节数。当有错误发生时则返回-1
,错误代码存入errno
中。 write
出错的原因可能是磁盘满、没有访问权限、或写超过文件长度限制等等- 附加说明:将数据写入已打开的文件内。对于普通文件,写操作从文件当前位置开始写(除非打开文件时指定了
O_APPEND
选项)。写操作完成后,文件的当前位置将从写之前的位置加上实际写的字节数。
注意:数据无法一次性读完时:
- 第二次读
buf
中数据时,读位置指针并不会自动移动 - 按如下格式实现读位置移动:
write(fp, p1+len, (strlen(p1)-len)
,直至指针恢复
Write一次可以写的最大数据范围是8192
- 写入数据大小最好小于buff中的值
- Count参数值大于
SSIZE_MAX
,则write
调用的结果未定义 - Count参数值为0时,write调用会立即返回0这个值
- Write调用返回时,内核已经将缓冲区所提供的数据复制到内核的缓冲区,但是无法保证数据已经写出到预定的目的地
代码示例
1 |
|
ioctl函数
man 2 ioctl
设备驱动程序中对设备的I/O通道进行管理
头文件:#include<sys/ioctl.h>
函数原型:int ioctl(int fd, int cmd, ...);
函数说明
ioctl()
能对一些特殊的文件(主要是设备)进行一些底层参数的操作。许多字符设备都使用ioctl请求来完成对设备的控制;- 返回值:成动返回0。当有错误发生时则返回-1,错误代码存入
errno
中 - 附加说明:ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等
在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}
结构,每一个case对应一个命令码,做出一些相应的操作
ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径
“幻数”是一个字母,数据长度也是8,用一个特定的字母来标明设备类型
文件标准IO
为什么要设计标准I/O库?
- 直接使用API进行文件访问时,需要考虑许多细节问题,例如:read、write时,缓冲区的大小该如何确定,才能使效率最优
- read和write等底层系统调用函数进行输入输出时,在用户态和内核态之间来回切换,每次读出或写入的数据量较少,导致频繁的I/O操作,增加了系统开销
标准I/O库是ANSI C规范的一部分,函数原型在文件stdio.h
中定义,对底层I/O系统调用进行了封装,为程序员提供了带有格式转换功能的输入输出操作,并在用户空间增加了缓冲区管理
标准I/O库:
- 分离了应用程序空间和实际的物理设备
- 减少了直接读盘次数,提高性能
- 读取前查看是否已存在页缓存中,如果已经存放在了页缓存中,数据立即返回给应用程序
- 写数据前先写到页缓存中,如果用户采用的是同步写机制(synchronous writes),那么数据会立即被写回到磁盘上,应用程序会一直等到数据被写完为止;如果用户采用的是延迟写机制( deferred writes ),那么应用程序就完全不需要等到数据全部被写回到磁盘,数据只要被写到页缓存中去就可以了
fopen函数
man 3 fopen
fopen函数功能:打开一个指定文件
函数原型
FILE *fopen(const char *restrict pathname, const char *restrict type);
参数
pathname
:要打开的文件名type
:指定文件的读、写方式
type | 说明 |
---|---|
r或rb | 为读而打开 |
w或wb | 使文件长度为0,或为写而创建 |
a或ab | 追加文件内容,或为写而创建 |
r+或r+b或rb+ | 为读写而打开 |
w+或w+b或wb+ | 使文件长度为0,或为读写而打开或创建 |
a+或a+b或ab+ | 在文件尾读写而打开或创建 |
setbuf函数
man 3 setbuf
定义流 stream 应如何缓冲
函数原型
void setbuf(FILE *steam, char *buf);
参数
- stream — 这是指向 FILE 对象的指针,该 FILE 对象标识了一个打开的流。
- buffer — 这是分配给用户的缓冲,它的长度至少为
BUFSIZ
字节,BUFSIZ
是一个宏常量,表示数组的长度。
1 |
|
C 库函数 – setbuf() | 菜鸟教程 (runoob.com)
setvbuf函数
man 3 setvbuf
函数原型
void setvbuf(FILE *steam, char *buf, int mode, size_t size);
参数
- stream — 这是指向 FILE 对象的指针,该 FILE 对象标识了一个打开的流。
- buffer — 这是分配给用户的缓冲。如果设置为 NULL,该函数会自动分配一个指定大小的缓冲。
- mode — 这指定了文件缓冲的模式:
模式 | 描述 |
---|---|
_IOFBF | 全缓冲:对于输出,数据在缓冲填满时被一次性写入。对于输入,缓冲会在请求输入且缓冲为空时被填充。 |
_IOLBF | 行缓冲:对于输出,数据在遇到换行符或者在缓冲填满时被写入,具体视情况而定。对于输入,缓冲会在请求输入且缓冲为空时被填充,直到遇到下一个换行符。 |
_IONBF | 无缓冲:不使用缓冲。每个 I/O 操作都被即时写入。buffer 和 size 参数被忽略。 |
- size —这是缓冲的大小,以字节为单位。
返回值:如果成功,则该函数返回 0,否则返回非零值。
1 |
|
C 库函数 – setvbuf() | 菜鸟教程 (runoob.com)
fdopen函数
man 3 fdopen
函数功能
- 取一个现存的文件描述符,并使一个标准I/O流与该描述符相结合
头文件
#include<stdio.h>
函数原型
FILE *fdopen(int fd, const char *type);
fdopen常用于由创建管道及网络通信通道函数返回的描述符。
- 这些特殊类型的文件,不能用fopen打开
- 因此必须先调用设备专用函数以获得一个文件描述符,然后再用fdopen使一个标准I/O流与该描述符相关联
对于fdopen函数,type
参数的意义稍有区别
- 因为该描述符已被打开,所以fdopen为写而打开并不截短该文件
- 不能用于创建该文件(因为如若一个描述符引用一个文件,则该文件一定已经存在)
Type值 | 操作文件类型 | 是否新建文件 | 是否清空原文件 | 可读 | 可写 | 读写开始位置 |
---|---|---|---|---|---|---|
r | 文本文件 | NO | NO | YES | NO | 文件开头 |
r+ | 文本文件 | YES | NO | YES | YES | 文件开头 |
w | 文本文件 | YES | YES | NO | YES | 文件开头 |
w+ | 文本文件 | YES | YES | YES | YES | 文件开头 |
a | 文本文件 | NO | YES | NO | YES | 文件结尾 |
a+ | 文本文件 | NO | YES | YES | YES | 文件结尾 |
rb | 二进制文件 | NO | NO | YES | NO | 文件开头 |
r+b或rb+ | 二进制文件 | YES | NO | YES | YES | 文件开头 |
wb | 二进制文件 | YES | YES | NO | YES | 文件开头 |
w+b或wb+ | 二进制文件 | YES | YES | YES | YES | 文件开头 |
ab | 二进制文件 | NO | YES | NO | YES | 文件结尾 |
a+b或ab+ | 二进制文件 | NO | YES | YES | YES | 文件结尾 |
1 |
|
文件定位
lseek函数
man 2 lseek
lseek
函数用于改变文件的当前偏移量。
头文件
#include<unistd.h>
#include <sys/types.h>
定义函数
off_t lseek(int filedes, off_t offset, int origin);
函数说明
filedes
文件描述符offset
必须与origin一同解析origin
为SEEK_SET
, 则offset从文件的开头算起。origin
为SEEK_CUR
, 则offset从当前位置算起,既新偏移量为当前偏移量加上offsetorigin
为SEEK_END
, 则offset从文件末尾算起。
返回值
- 如果失败,返回值为-1
- 成功返回移动后的文件偏移量
- 确定文件当前偏移量:
currpos = lseek(fd, 0, SEEK_CUR);
- 获取文件大小
size = lseek(fd, 0, SEEK_END);
- 确定文件当前偏移量:
lseek常用于找到文件的开头、找到文件的末端,判定文件描述符的当前位置
lseek仅将当前的文件偏移量记录在内核中,它并不引起任何I/O操作,然后该偏移量用于下一次读、写操作。
文件偏移量可以大于文件的当前长度,但并不改变相应的i节点信息。在这种情况下的下一次写将延长该文件(文件拓展),并在文件中构成一个空洞,但文件大小并不是文件的最大偏移量。对空洞位置的读操作将返回0。
lseek实现空洞:
1 |
|
1 | yang@Ubuntu18~$ gcc lseek_test.c -o lseek_test |
pread函数
man 2 pread
在给定的偏移量读取一个文件描述
头文件
#include <unistd.h>
函数原型
ssize_t pread (int fd, void *buf, size_t count, off_t pos)
返回值
- 返回读到的字节数;出错:返回-1;到文件结尾:返回0
解决问题
- 由于lseek和read调用之间,内核可能会临时挂起进程,所以对同步问题造成了问题,调用pread相当于顺序调用了lseek 和read,这两个操作相当于一个捆绑的原子操作
其他
- 调用pread时,无法中断其定位和读操作,另外不更新文件指针
pwrite函数
man 2 pwrite
作用:在给定偏移量处读取或写入文件描述符
头文件
#include<unistd.h>
函数原型
ssize_t pwrite (int fd, const void *buf, size_t count, off_t pos )
返回值
- 返回已写的字节数;出错:返回-1
解决问题
- 由于lseek和write 调用之间,内核可能会临时挂起进程,所以对同步问题造成了问题,调用pwrite相当于顺序调用了lseek 和 write,这两个操作相当于一个捆绑的原子操作
其他
- 调用pwrite时,无法中断其定位和读操作,另外不更新文件指针
对比
pread/pwrite
与read/write
的区别
- 调用更容易使用,特别是在进行需要技巧的操作时
- 完成工作后不会改变文件指针
- 避免使用lseek时可能造成的竞争条件
- 如果有多个线程共享文件描述符,当地一个线程调用lseek之后,在它进行读取或写入操作之前,同一个程序中的另一个线程可能会改变文件的位置
文件共享
UNIX/Linux支持不同进程间共享文件。内核使用的三种表(文件描述符表、文件表、索引结点表)之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响
- 每个进程在进程表中有一个文件描述符表,每个描述符表项指向一个文件表
内核为每一个被打开的文件维护一张文件表,文件表项包含
- 文件的状态标志(读、写、同步、非阻塞)
- 文件当前位置
- 指向该文件索引节点表的指针
每个文件(或设备)都有一个索引节点,它包含了文件类型属性及文件数据
如果两个进程分别打开同一个的文件(物理文件),则它们有不同的文件表,因此每个进程有自己的文件当前位置,因此其读写操作互不影响。
也存在不同进程共享同一个文件表(父子进程),或同一进程共享同一个文件表(dup操作)。此时,两个进程对该文件的读写操作将基于同一个文件当前位置。
进程共享文件
dup和dup2
man 2 dup
头文件:unistd.h
函数原型
int dup( int oldfd );
int dup2( int oldfd, int targetfd );
函数说明:复制一个文件的描述符,dup2函数跟dup函数相似,但dup2函数允许调用者规定一个有效描述符和目标描述符的
返回值:dup2函数成功返回时,目标描述符(dup2函数的第二个参数)将变成源描述符(dup2函数的第一个参数)的复制品
1 | // dup.c |
1 | // dup2.c |
线程共享文件
线程的定义:有时称轻量级进程,是进程中的一个执行线路或线索,是一个相对独立的、可独立调度和指派的执行单元
线程的创建:应用程序可以通过一个统一的clone()
系统调用接口,用不同的参数指定创建轻量进程还是普通进程
clone()
调用do_fork()
创建线程, do_fork()
参数为:(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND)
CLONE_VM
:do_fork()
需要调用copy_mm()
来设置task_struct
中的mm
和active_mm项,这两个mm_struct
数据与进程所关联的内存空间相对应。如果do_fork()
时指定了CLONE_VM
开关,copy_mm()
将把新的task_struct
中的mm
和active_mm
设置成与current
的相同,同时提高该mm_struct
的使用者数目(mm_struct::mm_users)
。也就是说,轻量级进程与父进程共享内存地址空间CLONE_FS
:task_struct
中利用fs(struct fs_struct *)
记录了进程所在文件系统的根目录和当前目录信息,do_fork()
时调用copy_fs()
复制了这个结构;而对于轻量级进程则仅增加fs->count
计数,与父进程共享相同的fs_struct
。也就是说,轻量级进程没有独立的文件系统相关的信息,进程中任何一个线程改变当前目录、根目录等信息都将直接影响到其他线程CLONE_FILES
:一个进程可能打开了一些文件,在进程结构task_struct
中利用files(struct files_struct *)
来保存进程打开的文件结构(struct file)
信息,do_fork()
中调用了copy_files()
来处理这个进程属性;轻量级进程与父进程是共享该结构的,copy_files()
时仅增加files->count
计数。这一共享使得任何线程都能访问进程所维护的打开文件,对它们的操作会直接反映到进程中的其他线程CLONE_SIGHAND
:每一个Linux进程都可以自行定义对信号的处理方式,在task_struct
中的sig(struct signal_struct)
中使用一个struct k_sigaction
结构的数组来保存这个配置信息,do_fork()
中的copy_sighand()
负责复制该信息;轻量级进程不进行复制,而仅仅增加signal_struct::count
计数,与父进程共享该结构。也就是说,子进程与父进程的信号处理方式完全相同,而且可以相互更改
总结:线程间所有文件结构都为共享资源,不但“文件表项”(file对象)是共享的,就连“文件描述符表”(files_struct结构)也是共享的。线程的创建仅仅增加的是files
和fs
的引用计数,“文件打开计数”(file对象的引用计数)并没有增加,所以任何一个线程对打开的文件执行close
操作,文件都将关闭(文件打开计数为1的情况)。但是如果线程不进行打开文件的关闭,则文件直到进程结束时才会关闭,这就是使用多线程实现tcp服务器时,服务线程必须要显示调用close的原因,否则永远不会发送FIN终止链接(因为主线程一直处于监听不会结束)。
进程间文件描述符的传递:
- 传递描述符的函数的参数是fd,fd是打开文件指针在数组中的下标
- 将一个文件描述符传递给另一个进程后,文件的“访问计数”会增加
- 进程间传递文件描述符可以看做跨进程的dup调用,也就是同一个file对象在不同进程间的映射
- 对于网络接口返回的描述符 ,只能采取传递文件描述符的方法。
- UNIX系统中两个方法:BSD
sendmsg
,recvmsg
方法;SYSVioctl
方法 - 进程间传递文件描述符时,发送进程和接收进程共享同一文件表项
- 进程间文件描述符的传递,只是通过内核将接收文件的一个新file指针指向和发送进程的同一个file对象,并使这个file对象的引用计数增加
文件属性
文件类型
Linux系统中常见的文件类型:
- 普通文件
-
:包含了某种形式的数据 - 目录文件
d
:包含了其他文件的名字以及指向与这些文件有关信息的指针 - 字符特殊文件
c
:提供对设备不带缓冲的访问 - FIFO文件
p
:用于进程间的通信,命名管道 - 套接字文件
s
:用于网络通信 - 符号链接
l
:使文件指向另一个文件
文件权限
Linux系统通过进程的有效用户ID和有效用户组ID来决定进程对系统资源的访问权限
与一个进程相关联的用户ID和用户组ID有如下几种:
通常情况下,有效用户ID等于实际用户ID,有效组ID等于实际组ID;
可执行文件的权限中有一个特殊标志,定义为“当执行此文件时,将进程的有效用户ID设置为文件的所有者”,与此类似,组ID也有类似的情况。
这两个标志位称为:“设置用户ID” 和 “ 设置组ID”, 这 两 位 都 包 含 在 stat
信 息 中 的 st_mode
中 , 可 用S_ISUID
,S_ISGID
测试。
文件:
- 对于一个文件的读权限决定了我们是否能够打开该文件进行读操作
- 对一个文件的写权限决定了我们是否能够打开该文件进行写操作
- 为了在
open
函数中对一个文件指定O_TRUNC
标志,必须对该文件具有写权限 - 执行某个可执行文件,都必须对该文件具有执行权限
目录:
- 目录文件的执行权限也表示可以进入该目录
- 通过文件名打开一个任意类型的文件时,对该文件路径名中包含的每一个目录都应具有执行权限
- 为了在一个目录中创建一个新文件,必须对该目录具有写权限和执行权限
- 为了删除一个文件,必须对包含该文件的目录具有写权限和执行权限,对该文件本身则不需要有读、写权限
文件权限检查
进程访问文件时,内核就进行文件权限检查。这种检查涉及到文件的所有者、文件的所有者所在组、进程有效用户、进程的有效组及进程的附加组。两个所有者是文件的性质,而有效用户与有效组是进程的性质
当进程对某个文件进行操作时,内核按顺序执行下列4步来检查文件权限
- 若进程的有效用户为root(ID等于0),则允许任何操作;
- 若进程的有效用户等于文件的所有者(ID相同)(即该进程拥有文件),按照文件所有者具有的权限判定操作是否合法
- 若进程的有效组或进程的附加组之一等于文件所有者所在组,按照文件所有者所在组具有的权限判定操作是否合法
- 按照其他用户具有权限判定操作是否合法
一般情况下:
- 若进程有效用户拥有此文件,则按用户权限批准或拒绝该进程对文件的操作
- 若进程有效用户并不拥有该文件,但进程有效用户属于某个适当的组,则按组权限批准或拒绝该进程对文件的操作
- 若进程有效用户并不拥有该文件,也不属于某个适当的组,则按照其他其他用户权限批准或拒绝该进程对文件的操作
新文件和新目录的所有权:
新文件的所有者设置为进程的有效用户
新文件所有者所在的组,POSIX允许选择下列之一:
- 新文件所有者所在的组可以是进程的有效组
- 新文件所有者所在的组可以是它所在目录的组
新文件所有者所在的组取决于它所在目录的设置组ID位是否设置,若设置,则为目录组,否则则为进程有效组
BSD总是用目录组作为新文件所有者所在组
文件的时间
每个文件有三个时间字段
对文件进行一次读操作,它的访问时间就会改变
修改时间是文件内容最后一次被修改的时间
状态时间是该文件索引节点最后一次被修改的时间,影响索引节点的操作:
- 更改文件访问权限
- 更改文件用户ID
- 更改文件链接数
这三个时间在后文的stat结构体中也有展示
相关struct
文件和目录相关结构体
DIR结构体
mkdir
函数的返回类型为DIR*
,closedir
和readdir
函数的传入参数类型为DIR*
1 | struct __dirstream |
dirent结构体
头文件所在位置:/usr/include/dirent.h
readdir
函数的返回类型为struct dirent*
1 | struct dirent |
unsigned char d_type; // 文件类型
可以和下面的宏(/usr/include/dirent.h
)进行比较来判断文件的类型,如if(d_type == DT_DIR)
1 | /* File types for `d_type'. */ |
注:编译时添加宏定义 -D_BSD_SOURCE
stat结构
stat
是linux系统用来描述文件属性的重要数据结构信息,可以使用stat
命令查看。
1 | struct stat { |
st_mode
mode_t st_mode
16位,无符号整数,其低16位定义如下:
注:下面的宏定义在/usr/include/linux/stat.h
中
1)从st_mode中获取低12bit信息
下面是一些掩码,是定义的宏。使用方法:st_mode & 掩码
(9-11bit很少用)
1 | | bit | 字段 | 权限 | 含义 | |
2)文件类型信息(12-15bit)
使用:if(S_ISLNK(st_mode))
1 |
设置用户ID位和设置组ID位:
定义:
- 包含在可执行文件的权限标记中,有一个“设置用户ID位”若该位被设置,表示:执行该文件时,进程的有效用户ID变为文件的所有者对于设置组ID位类似
- 通过命令行设置用户ID位:
chmod u+s filename
;chmod u-s filename
;chmod g+s filename
;chmod g-s filename
;
- 若文件所有者是超级用户,且设置了设置用户ID位,则执行此文件的进程拥有超级用户权限
- Passwd(1) 允许任一用户改变其口令,该程序是一个设置用户ID程序
- 分别用常量
S_ISUID
和S_ISGID
测试
粘着位
在UNIX早期版本中,有一位被称为粘住位,如果一可执行程序文件的这一位被设置了,那么在该程序第一次执行并结束时,该程序正文被保存在交换区中,这使得下次执行该程序时能较快地将其装入内存。
现今较新的UNIX系统大多数都具有虚存系统以及快速文件系统,所以不再需要使用这种技术
如果对一个目录设置了粘住位,则只有对该目录具有写许可权的用户并且满足下列条件之一,才能删除或更名该目录下文件:
- 拥有此文件
- 拥有此目录
- 是超级用户
目录/tmp
和/var/spool/uucppublic
(怎么没有?)是设置粘住位的候选者。这两个目录是任何用户都可在其中创建文件的目录,对任一用户(用户、组和其他)的许可权通常都是读、写和执行。但是用户不应能删除或更名属于其他人的文件,为此在这两个目录的文件方式中都设置了粘住位。
st_size
stat
结构的成员st_size
包含了以字节为单位的该文件的长度。此字段只对普通文件、目录文件和符号连接有意义
- 对于普通文件,其文件长度可以是0,在读这种文件时,将得到文件结束指示
- 对于目录,文件长度通常是一个数,例如16或512的整倍数
- 对于符号连接,文件长度是在文件名中的实际字节数。例如:
lrwxrwxrwx 1 root 7 Sep 25 07:14 lib -> usr/lib
- 其中,文件长度7就是路径名
usr/lib
的长度
st_dev
每一个文件系统所在的存储设备都由其主、次设备号表示。设备号所用的数据类型是基本系统数据类型dev_t
主设备号标识设备驱动程序,次设备号标识特定的子设备
通常可以使用两个宏major
和minor
来访问主、次设备号
- 早期系统用16位整型存放设备号:8位用于主设备号,8位用于次设备号
- FreeBSD 8.0和Mac OS X 10.6.8使用32位整型,其中8位表示主设备号,24位表示次设备号。
- 32位系统中,Solaris 10用32位整型表示dev_t,其中14位用于主设备号,18位用于次设备
- 64位系统中,Solaris 10用64位整型表示dev_t,主设备号和次设备号各为32位
- 在Linux 3.2.0上,dev_t是64位整型,12位用于主设备号,20位用于次设备号
系统中与每个文件名关联的st_dev
值是文件系统的设备号,该文件系统包含了这一文件名以及与其对应的索引结点
只有字符特殊文件和块特殊文件才有st_rdev
值,此值包含实际设备的设备号
passwd结构体
保存用户的信息
头文件所在/usr/include/pwd.h
,getpwuid
的返回值
1 | struct passwd{ |
group结构体
保存的用户组信息
头文件所在/usr/include/grp.h
,getgrgid
的返回值
1 | struct group |
文件操作
获取文件的stat
结构体:stat
、fstat
、lstat
、fstatat
改变文件大小:truncate
、ftruncate
获取用户的passwd
结构体:getpwuid
获取用户组的group
结构体:getgrgid
判断文件读,写,执行或是否存在:access
改变文件权限:chmod
改变文件属主属组:chown
创建硬链接:link
创建软链接:symlink
删除链接或文件:unlink
删除文件或目录:remove
文件重命名:rename
修改文件时间:utime
操纵文件描述符:fcntl
stat函数
man 2 stat
作用:读取文件属性
头文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
函数原型:
int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int fstatat(int fd, const char *restrict pathname, struct stat *restrict buf, int flag);
函数说明:
stat
和lstat
参数相同,功能类似。读取path
参数所指定文件的文件属性并将其填充到buf
参数所指向的结构体中;对于符号链接文件,lstat
返回符号链接的文件属性,stat
返回符号链接引用文件的文件属性stat
穿透(追踪)函数 — 软连接lstat
不穿透,不穿透的Linux命令有ls -l
,rm
等
fstat
与前两个函数功能类似,指定文件的方式改为通过文件描述符statat
函数为一个相对于当前打开目录(由fd参数指向)的路径名返回文件统计信息。flag
参数控制着是否跟随着一个符号链接,返回符号链接本身的信息或者符号链接所指向的实际文件信息。- 如果fd参数的值是
AT_FDCWD
,并且pathname
参数是一个相对路径名,fstatat
会计算相对于当前目录的pathname
参数,如果pathname
是一个绝对路径,fd
参数就会被忽略。这两种情况下,根据flag
的取值fstatat
的作用就跟stat
或lstat
一样
- 如果fd参数的值是
- 参数
buf
,是一个指向stat
结构的指针,这几个函数都是为了填充buf
指向的结构stat
- 成功返回
0
,失败返回-1
truncate函数
man 2 truncate
作用:改变文件大小
函数原型:
int truncate(const char* pathname, off_t length);
int ftruncate(int fd, off_t length);
函数说明:
- 用于改变文件的长度
pathname
:欲改变长度的文件的文件名fd
:欲改变长度的文件的文件描述符;length
:要设置的文件的新长度
返回值:成功返回0,出错返回-1
注意事项:
- 当文件以前的长度
>length
时,则超过length以外的数据将不复存在 - 当文件以前的长度
<length
时,在文件以前长度到length之间,将形成空洞,读该区域,将返回0
常见问题:
truncate
和ftruncate
函数并未实质性的向磁盘写入数据,只是分配了一定的空间供当前文件使用。当fd<length
时,此时如果使用十六进制编辑工具打开该文件,会发现文件末尾多了很多00,这就是执行这个函数后的效果。如果发生系统复位或者装置掉电以后,该函数所产生的作用将被文件系统忽略,也就是说它所分配的空间将不能被识别,文件的大小将会是最后一次写入操作的区域大小,而非ftruncate
分配的空间大小,也就是说,文件大小有可能会被改变
解决方案:
可以在执行完ftruncate
之后,在新空间的末尾写入一个或以上字节的数据(不为0x00),这样新空间则不为空,文件系统会把这部分空间当成这个文件的私有空间处理,而不会出现文件大小改变的错误。
getpwuid
man 3 getpwuid
作用:获取用户信息
常用函数:getpwuid
头文件:
sys/types.h
pwd.h
函数原型:struct passwd *getpwuid(uid_t uid);
函数说明:输入用户ID,返回用户属性信息(passwd结构)
getgrgid
man 3 getgrgid
作用:获取用户组信息
头文件:
sys/types.h
grp.h
函数原型:struct group *getgrgid(gid_t gid);
函数说明:输入用户组ID,返回用户组属性信息(group结构)
access函数
man 2 access
函数功能:按照前述文件权限检查的4个步骤测试存取文件是否具有相应权限
函数原型:int access(const char *pathname, int mode);
成功返回0
,失败返回-1
Mode | 说明 |
---|---|
R_OK |
测试读许可 |
W_OK |
测试写许可 |
X_OK |
测试执行许可 |
F_OK |
文件是否存在 |
1 |
|
chmod函数
man 2 chmod
作用:改变文件权限
函数原型
int chmod(const char * pathname, mode_t mode);
int fchmod( int fd, mode_t mode);
函数用途:改变指定文件的权限位。
函数说明:
chmod
要求给出的是文件或目录所在的位置,而fchmod
主要针对的是文件,要求调用是相应的文件描述符。- 修改时,进程的有效用户ID必须等于文件的所有者ID,或是root运行的此进程
- 返回值:成功0,失败-1
mode
: 必须是8进制数,如0755
等
chmod
出错信息:
字段 | 说明 |
---|---|
EACCES |
给出的文件所处路径没有访问权限 |
EFAULT |
路径指向的文件地址错误 |
EIO |
发生I/O错误 |
ELOOP |
给出的文件所在路径中符号连接过多 |
ENAMETOOLONG |
路径过长 |
ENOENT |
文件不存在 |
ENOMEM |
内核内存空间不足 |
ENOTDIR |
给出的文件所处路径中包含不是目录的部分 |
EPERM |
有效用户ID与文件拥有者不同,进程无权访问修改文件权限 |
EROFS |
文件位于只读文件系统 |
fchmod
函数出错信息
字段 | 说明 |
---|---|
EBADF |
非法的文件描述符 |
EIO |
发生I/O错误 |
EPERM |
有效用户ID与文件拥有者不同,进程无权访问修改文件权限 |
EROFS |
文件位于只读系统 |
1 |
|
chown函数
man 2 chown
作用:更改文件所有者
函数原型
int chown( const char *pathname, uid_t owner, gid_t group);
int fchown( int filedes, uid_t owner, gid_t group);
int lchown( const char *pathname, uid_t owner, gid_t group);
函数用途:更改文件的用户ID和组ID。
函数说明:
- 如果两个参数
owner
或group
中的任意一个是-1
,则不改变文件所有者或文件所属用户组 lchown
是改变符号链接本身的所有者,而不是该符号链接所指向的文件- 基于BSD的系统中,只有超级用户才能更改一个文件的所有者
- 非超级用户进程调用,则在成功返回时,该文件的设置用户ID位和设置组ID位会被清除
- 成功返回0,若出错则返回-1
link函数
man 2 link
作用:创建一个硬链接
函数原型:int link ( const char *pathname, const char *newpath);
函数用途:任何一个文件可以有多个目录项(dentry
)指向其索引节点,创建一个指向现有文件的新目录项
函数说明
- 此函数创建一个新目录项
newpath
,它指向pathname
指向的文件。如果newpath
已经存在,则返回出错 - 创建新目录项以及增加连接计数是一个原子操作。
- 大多数的系统规定,只有超级用户可以创建指向一个目录的新连接,目的是避免在文件系统中形成循环。
1 | //link.c |
symlink函数
man 2 symlink
作用:创建一个软链接
头文件:#include <unistd.h>
函数原型:int symlink(const char *target, const char *linkpath);
target
是原文件路径linkpath
是要创建的软链接- 返回值:成功返回0,失败返回-1
1 | //symlink.c |
unlink函数
man 2 unlink
函数原型:int unlink ( const char *pathname);
头文件:unistd.h
函数用途:unlink
删除目录项,并将由pathname
所引用文件的链接计数减1,清空这个文件使用的可用的系统资源
- 删除一个文件的目录项并减少它的链接数,若成功则返回0,否则返回-1,错误原因存于errno
- 如果想通过调用这个函数来成功删除文件,你就必须拥有这个文件的所属目录的写和执行权限
注:
1)如果是符号链接,删除符号链接
2)如果是硬链接,硬链接数减1,当减为0时,释放数据块和inode
3)如果文件硬链接数为0,但有进程已打开该文件,并持有文件描述符,则等该进程关闭该文件时,kernel才真正去删除该文件
- 利用该特性创建临时文件,先open或creat创建一个文件,马上unlink此文件
1 |
|
remove函数
man 3 remove
函数原型:int remove( const char *pathname);
头文件:stdio.h
函数用途:remove
删除一个目录中的一个或多个文件或目录,也可以将某个目录及其下的所有文件及子目录均删除
remove
的参数为普通文件时等价于unlink
rename函数
man 2 rename
函数原型:int rename ( const char * oldname, const char * newname);
函数用途:更名文件或目录
函数说明
- 如果
oldname
是一个文件而不是目录,那么为该文件更名 - 如果
oldname
是一个目录,那么为该目录更名 - 如果
oldname
和newname
引用同一文件,则函数不做任何更改而成功返回 - 应对包含两个文件的目录具有写和执行许可权
utime函数
man 2 utime
函数原型:int utime( const char *pathname, const struct utimbuf *times);
函数用途:设置存取和修改文件的时间
函数说明:此函数的操作以及执行它所要求的优先权取决于times
参数是否是NULL
- 如果
times
是一个NULL
,则存取时间和修改时间两者都设置为当前时间,但必须满足下面二者之一:- 进程的有效用户ID必须等于该文件的所有者
- 进程对该文件有写许可权
- 如果
times
是非空指针,则存取时间和修改时间被设置为times
所指向的时间,此时进程的有效用户ID必须等于该文件的所有者ID,或者进程必须是超级用户的进程
fcntl函数
man 2 fcntl
作用:根据文件描述符来操作文件的状态
头文件:#include <fcntl.h>
函数原型:fcntl
是可变参的,第3个参数是否需要取决于cmd
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struc flock *lock);
参数:cmd
宏定义
功能:宏后面为第三个参数类型
1)复制一个现有的文件描述符 — cmd
: F_DUPFD(int)
2)获得/设置文件描述符标记 — cmd
:F_GETFD(void)
/ F_SETFD(int)
3)获得 / 设置文件状态标记 — cmd
:F_GETFL(void)
/ F_SETFL(int)
1 | F_GETFL |
4)获得 / 设置异步I/O所有权 — cmd
:F_GETOWN
/ F_SETOWN
5)获得 / 设置记录锁 — cmd
:F_GETLK
/ F_SETLK
/ F_SETLKW
fcntl
使用示例:
1 |
|
目录操作
获取当前目录:getcwd
或 get_current_dir_name
打开目录:opendir
关闭目录:closedir
创建目录:mkdir
删除目录:rmdir
改变当前工作目录:chdir
读取目录下的文件:readdir
重置目录读取位置:rewinddir
设置目录读取位置:seekdir
获取目录读取位置:telldir
读取特定目录数据:scandir
getcwd函数
man 3 getcwd
作用:获取当前目录
头文件: unistd.h
函数声明:char *getcwd(char *buf, size_t size);
函数说明:将当前的工作目录绝对路径字符串复制到参数buf
所指的缓冲区,参数size
为buf
缓冲区大小。
返回值:成功调用返回指向buf
的指针,失败返回NULL
1 |
|
1 | yang@Ubuntu18:~/Documents/Cprogram/$ gcc getcwd_test.c -o getcwd_test |
get_current_dir_name函数
man 3 get_current_dir_name
作用:获取当前目录
文件首加宏定义:#define _GNU_SOURCE
头文件: unistd.h
函数声明:char * get_current_dir_name(void);
函数说明:调用后会返回一个字符串指针,指向当前工作目录绝对路径字符串
返回值:成功则返回字符串指针,失败则返回NULL
,错误代码存放于errno
中
1 |
|
1 | yang@Ubuntu18:~/Documents/Cprogram$ gcc get_current_dir_name_test.c -o get_current_dir_name_test |
opendir函数
man 3 opendir
作用:打开目录
头文件:
sys/types.h
dirent.h
函数声明:DIR *opendir(const char *path);
函数说明:打开参数path
指定的目录
返回值:成功则返回DIR形态的目录流,失败则返回NULL
,错误代码存放于errno
中
错误代码:
EACCESS
代表权限不足,也就是对这个目录没有执行权限。EMFILE
表示目前同时打开这个文件的进程数目已经到达了系统上限。ENFILE
代表已经达到系统可以同时打开的文件数上限。ENOTDIR
代表参数path
指向的不是一个真正的目录。EOENT
表示参数path
指向的目录不存在,或者参数path
是一个空的字符串。ENOMEM
表示核心内存不足
closedir函数
man 3 closedir
作用:关闭目录
头文件:
sys/types.h
dirent.h
函数声明:int closedir(DIR *dir);
函数说明:关闭参数dir
指定的目录
返回值:关闭成功就返回0,失败的话就返回-1,错误原因存于errno
中
错误代码:EBADF
代表参数dir
是一个无效的目录流
readdir函数
man 2 readdir
作用:读取目录文件
头文件:
sys/types.h
dirent.h
函数声明:struct dirent *readdir(DIR *dirp);
函数说明:读取目标流dir
标识的目录
返回值:每执行一次readdir
,这个函数返回指向当前读取目录项结构的指针,有错误发生或读取到目录文件尾的时候就返回NULL
错误代码:EBADF
代表参数dir
是一个无效的目录流
代码示例
1 |
|
获取一个目录下面普通文件个数
1 |
|
mkdir函数
man 2 mkdir
作用:创建目录
头文件:sys/stat.h
函数声明:int mkdir(const char *pathname, mode_t mode);
函数说明:以mode
方式创建一个以参数pathname
命名的目录,mode
定义新创建目录的权限
返回值:若目录创建成功,则返回0;否则返回-1,并将错误记录到全局变量errno
中
注意事项:
- 创建
777
权限的目录,需要先执行umask(0)
,然后再调用mkdir
函数 - 目录已存在会返回-1
- 不能创建多个目录
1 |
|
rmdir函数
man 2 rmdir
作用:删除空目录
头文件:unistd.h
函数声明:int rmdir(const char *pathname);
函数说明:删除一个目录,该目录必须是空的
返回值:若目录删除成功,则返回0;否则返回-1,并将错误记录到全局变量errno
中
注意事项:pathname
不能超过255,目录名不能以.
开头(.
开头的是隐藏文件)。目录没有被其他进程占用。
1 |
|
chdir函数
man 2 chdir
作用:改变当前工作目录
头文件:unistd.h
函数声明:int chdir(const char *path);
函数说明:chdir
函数用于改变当前工作目录。调用参数是指向目录的指针,调用进程需要有搜索整个目录的权限
返回值:成功,则返回0;否则返回-1,并将错误记录到全局变量errno
中
错误信息:
EFAULT
: path指向了非法地址ENAMETOOLNG
: 路径过长ENOENT
: 文件不存在ENOMEM
: 内核内存不足ENOTDIR
: 给出路径不是目录EACCES
: 无访问路径中某个目录的权限ELOOP
:解析路径中太多的符号链接EIO
: 发生I/O错误
函数说明:
- 每个进程都具有一个当前工作目录。在解析相对目录引用时,该目录是搜索路径的开始之处
- 如果调用进程更改了目录,则它只对该进程有效,而不能影响调用它的那个进程
- 在退出程序时,shell还会返回开始时的那个工作目录
- 内核解析参数中的路径名,并确保这个路径名有效。内核定位该目录的索引节点,并检查它的文件类型和权限位,确保目标文件是目录以及进程的所有者可以访问该目录
- 内核用新目标目录的路径名和索引节点替换u区中当前目录路径名和索引节点号
1 |
|
rewinddir函数
man 3 rewinddir
作用:重置目录读取位置
头文件:
sys/types.h
dirent.h
函数声明:void rewinddir(DIR *dir);
函数说明:设置参数dir
指向的目录流目前的读取位置为原来开头的读取位置
返回值:无返回值,函数执行失败后会将错误记录到全局变量errno
中
错误信息:EBADF
:表示dir
指向的目录流无效
1 |
|
seekdir函数
man 3 seekdir
作用:设置目录的读取位置
头文件: dirent.h
函数声明:void seekdir(DIR * dir, off_t offset)
函数说明:设置参数dir指向的目录流目前的读取位置为offset
,在调用readdir()
时便从此新位置开始读取。参数offset
代表距离目录文件开头的偏移量。
返回值:无返回值,函数执行失败后会将错误记录到全局变量errno
中
错误信息:EBADF
:表示dir指向的目录流无效
1 |
|
telldir函数
man 3 telldir
作用:获取当前目录读取位置
头文件: dirent.h
函数声明:off_t telldir(DIR * dir);
函数说明:取得目录流dir
的读取位置。
返回值:返回参数dir
目录流目前的读取位置. 此返回值代表距离目录文件开头的偏移量,有错误发生时返回-1.
错误信息:EBADF
:表示dir指向的目录流无效
1 |
|
scandir函数
man 3 scandir
作用:读取特定目录数据
头文件: dirent.h
函数声明:
1 | int scandir(const char *dir, |
函数说明:scandir()
会扫描参数dir
指定的目录文件,经由参数filter
指定的函数来挑选目录结构至参数namelist
数组中,最后再调用参数compar
指定的函数来排序namelist
数组中的目录数据。每次从目录文件中读取一个目录结构后便将此结构传给参数filter
所指的函数,filter
函数若不想要将此目录结构复制到namelist
数组就返回0,若filter
为空指针则代表选择所有的目录结构。scandir()
会调用qsort()
来排序数据,参数compar
则为qsort()
的参数,若是要排列目录名称字母则可使用alphasort()
。
返回值:成功则返回复制到namelist
数组中的数据结构数目,有错误发生则返回-1.
错误信息:ENOMEM
核心内存不足
补充:
int alphasort(const void **a, const void **b);
int versionsort(const void **a, const void **b);
1 |
|
补充
errno
errno
是一个全局变量,是一个int
值,是记录系统的最后一次错误代码。定义在头文件 errno.h
中,位置/usr/include/errno.h
任何标准C库函数都能对其进行修改(Linux系统函数更可以)
错误宏定义位置:
第 1 - 34 个错误定义:
/usr/include/asm-generic/errno-base.h
第 35 - 133 个错误定义:
/usr/include/asm-generic/errno.h
每个errno值对应着以字符串表示的错误类型,当调用”某些”函数出错时,该函数会重新设置 errno
的值
输出errno
对应的错误信息:
1)使用 strerror
函数,头文件string.h
- 原型:
char *strerror(int errnum);
- 根据传入的
errno
返回对应的字符串
2)使用 perror
函数,头文件stdio.h
- 原型:
void perror(const char *s);
- 用来将上一个函数发生错误的原因输出到标准设备(stderr)
- 参数 s 所指的字符串会先打印出,后面再加上错误原因字符串
- 此错误原因依照全局变量
errno
的值来决定要输出的字符串。
umask
用open
函数创建文件或mkdir
函数创建目录,在设置权限时,如果设置0777
,创建的文件或目录却不是rwxrwxrwx
的权限
原因:Linux
有一个文件创建掩码umask
,umask
按位取反得到mask
,mask
和0777
按位与得到文件或目录的权限
1 | /* myopen.c */ |
得到的权限是755
umask
查看文件创建掩码
解决方法:将文件创建掩码
1)在Linux终端输入umask 0
,将文件创建掩码临时设为0
2)使用mode_t umask(mode_t mask);
函数,在代码中创建文件之前,添加umask(0);
链接穿透
backspace
C语言程序从终端输入Backspace
键显示^H
在程序中添加system("stty erase ^H");