linux编程细节1-内核-文件目录

时间:2022-11-24 04:46:01

1 linux文件系统

1.1 文件系统概述


linux编程细节1-内核-文件目录


/
根目录 root (/),一般建议在根目录底下只接目录,不要直接有档案在 / 底下。根目录是开机的时候系统第一个挂载的 partition ,所以,所有开机过程会用到/的文件, 应该都要放置在这个 partition 当中。举例来说, /etc, /bin, /dev,/lib, /sbin 这五个次目录都应该要与根目录连在一起,不可独立成为某个partition 。
/bin
/usr/bin
/usr/local/bin

usr(Unix System Resources)
这几个目录放置各命令的执行文件。
/boot
这个目录放置Linux 系统开机会用到的Kernel文件(VMLINUZ)等。
/dev
这个目录放置的是任何设备和接口设备。比较重要的档案有 /dev/null, /dev/tty[1-6], /dev/ttyS*,/dev/lp*, /dev/hd*, /dev/sd* 等。
/etc
系统主要的设定文件几乎都放置在这个目录内,例如人员的账号密码文件、 各种服务的起始文件等等。比较重要的文件有: /etc/inittab, /etc/init.d/, /etc/modprobe.conf,/etc/X11,/etc/fstab, /etc/sysconfig/ 等。
另外,其下重要的目录有:
/etc/init.d/:所有服务的预设启动 script 都是放在这里的,例如要启动或者关闭 iptables 的话:/etc/init.d/iptables start;/etc/init.d/iptables stop。
/etc/xinetd.d/:这就是所谓的 super daemon 管理的各项服务的设定文件目录。
/etc/X11:与 X Window 有关的各种设定档都在这里,尤其是 xorg.conf或 XF86Config 这两个 X Server 的设定档。
/lib
/usr/lib
/usr/local/lib

系统会使用到的函数库放置的目录。比较重要的是 /lib/modules 这个目录内会摆放 kernel 的相关模块。
/home

一般用户的主文件目录。
/mnt
/media

这是软盘与光盘预设挂载点的地方;通常软盘挂在 /mnt/floppy 下,而光盘挂在 /mnt/cdrom 下。另外,目前也规划出另一个 /media 的目录呢!与 /mnt 有点类似。
/proc

系统核心与执行程序的一些信息。例如你的网络状态的问题啦!这个目录将在启动 Linux 的时候自动的被挂上,而且该目录底下不会占去硬盘空间!因为里面都是内存内的数据。
/sbin
/usr/sbin
/usr/local/sbin

放置一些系统管理员才会动用到的执行指令,例如: fdisk, mke2fs, fsck,mkswap, mount 等等。与 /bin 不太一样的地方,这几个目录是给 root 等系统管理用的。
/tmp

这是让一般使用者暂时存放文件的地方。

/usr

这个目录中含有相当多的系统信息。这个目录有点像是 Windows 底下的“C:\Program Files\”目录和“C:\Windows\”。
/opt
这是给主机额外安装软件所摆放的目录。不过,以前的 Linux 系统中,我们还是习惯放置在/usr/local 目录下呢!

/var

该目录针对常态性变动文件,如缓存(Cache)、登陆文件(log file)等。



1.2 目录/proc

/proc 文件系统是一种内核和内核模块用来向进程 (process) 发送信息的机制 (所以叫做 /proc)。这个伪文件系统让你可以和内核内部数据结构进行交互,获取有关进程的有用信息。与其他文件系统不同,/proc 存在于内存之中而不是硬盘上。
对 /proc 进行一次 'ls -l' 可以看到大部分文件都是 0 字节大的;/proc 文件系统和其他常规的文件系统一样把自己注册到虚拟文件系统层 (VFS) 了。然而,直到当 VFS 调用它,请求文件、目录的 i-node 的时候,/proc 文件系统才根据内核中的信息建立相应的文件和目录(感觉上像是用户读取的时候才立即采集信息生成该文件)。/proc文件反应了实时的内存,所以proc里的文件,实际上都是映射到内核空间中的具体数据上的,每次读取都是那一时刻的瞬时值。注意/proc不要理解为常见的文件(不是读出内存再写入/proc),/proc就是内存本身,只不过是系统将内存抽象为了一个目录。


proc 文件系统可以被用于收集有用的关于系统和运行中的内核的信息。下面是一些重要的文件:
/proc/cpuinfo - CPU 的信息 (型号, 家族, 缓存大小等)
/proc/meminfo - 物理内存、交换空间等的信息
/proc/mounts - 已加载的文件系统的列表
/proc/devices - 可用设备的列表
/proc/filesystems - 被支持的文件系统
/proc/modules - 已加载的模块
/proc/version - 内核版本
/proc/cmdline - 系统启动时输入的内核命令行参数

$ ls -l /proc/32558
total 0
-r--r--r-- 1 root root 0 Dec 25 22:59 cmdline
-r--r--r-- 1 root root 0 Dec 25 22:59 cpu
lrwxrwxrwx 1 root root 0 Dec 25 22:59 cwd -> /proc/
-r-------- 1 root root 0 Dec 25 22:59 environ
lrwxrwxrwx 1 root root 0 Dec 25 22:59 exe -> /usr/bin/mozilla*
dr-x------ 2 root root 0 Dec 25 22:59 fd/
-r--r--r-- 1 root root 0 Dec 25 22:59 maps
-rw------- 1 root root 0 Dec 25 22:59 mem
-r--r--r-- 1 root root 0 Dec 25 22:59 mounts
lrwxrwxrwx 1 root root 0 Dec 25 22:59 root -> //
-r--r--r-- 1 root root 0 Dec 25 22:59 stat
-r--r--r-- 1 root root 0 Dec 25 22:59 statm
-r--r--r-- 1 root root 0 Dec 25 22:59 status

cmdline:该(虚拟)文件包含了启动该进程的整个命令行。它未曾格式化可以使用:perl -ple 's,\00, ,g' cmdline查看。
cwd:该符号链接指向该进程的当前工作目录。
environ:该文件包含为该进程定义的所有环境变量,格式为 变量=值。未曾格式化可以使用:perl -pl -e 's,\00,\n,g' environ查看。
exe:这是指向该进程所执行的可执行程序文件的符号链接。
fd:在此子目录中包含了该进程当前打开的所有描述符。参见下文。
maps:当您打印该命名管道内容时(例如使用 cat),您将看到该进程地址空间中当前映射到文件中的那部分。自左至右各列为:与该映射关联的地址空间,与该映射关联的权限,该映射起始位置的偏移量(从文件开始处算起),保存该映射文件设备的主、从序号(十六进制格式),该文件的 inode 序号,以及该文件的文件名。当设备号为 0 且没有 inode 号或文件名时,它就是一个匿名映射。请参阅 mmap(2)。
root:该符号链接指向该进程使用的根目录。通常为 /,请参阅 chroot(2)。
status:该文件包含该进程的众多信息:可执行文件名、当前状态、PID 和 PPID、实际及有效的 UID 和 GID、内存使用情况、以及其他。请注意,stat 和 statm 现已过时。它们包含的信息现存于 status。
cpu:仅在运行 SMP 内核时出现,里面是按 CPU 划分的进程时间。

Q:这里面的很多命令都是从内存文件/proc/下面去读的,比如说mpstat读的是/proc/stat,free命令读的是/proc/meminfo,uname读的是/proc/version,可以猜测这些命令都是从内存文件中直接读取数据,或再做一些运算。这个/proc/映射了内存情况,是如何组织的?


一个例子:查看/proc/stat文件:
$ cat /proc/stat | grep 'cpu'
cpu  4409701839 5860491 3043372756 11777957443 471600199 13606335 49392558 0
cpu0 980245201 1554799 596504303 3214215192 126029552 6603537 17697344 0
cpu1 1209283591 1411942 861982464 2749190858 113506249 255348 7220138 0
cpu2 971403569 1530154 624934033 3195318936 125767475 6491354 17450205 0
cpu3 1248769476 1363594 959951956 2619232456 106296922 256096 7024869 0
 
cpu一行指的是总的CPU信息,cpu0、cpu1、cpu2、cpu3几行指的是各个CPU核的使用信息。每行的每个参数意思为:(以第一行为例,从左到右依次列举)
l  user  从系统启动开始累计到当前时刻,用户态的CPU时间(单位:jiffies) ,不包含 nice值为负进程。1jiffies=0.01秒
l  nice  从系统启动开始累计到当前时刻,nice值为负的进程所占用的CPU时间(单位:jiffies)
l  system  从系统启动开始累计到当前时刻,核心时间(单位:jiffies)
l  idle  从系统启动开始累计到当前时刻,除硬盘IO等待时间以外其它等待时间(单位:jiffies)
l  iowait  从系统启动开始累计到当前时刻,硬盘IO等待时间(单位:jiffies),
l  irq  从系统启动开始累计到当前时刻,硬中断时间(单位:jiffies)
l  softirq  从系统启动开始累计到当前时刻,软中断时间(单位:jiffies)
l  steal  - 在虚拟环境下 CPU 花在处理其他作业系统的时间,Linux 2.6.11 开始才开始支持。
l  guest  - 在 Linux 内核控制下 CPU 为 guest 作业系统运行虚拟 CPU 的时间,Linux 2.6.24 开始才开始支持。

再进行计算,CPU使用率采集算法如下(以CPU0为例):
1. cat /proc/stat | grep 'cpu0'得到cpu0的信息
2. cpuTotal1=user+nice+system+idle+iowait+irq+softirq
3. cpuUsed1=user+nice+system+irq+softirq
4. sleep 15秒
5. 再次cat /proc/stat | grep 'cpu0'得到cpu的信息
6. cpuTotal2=user+nice+system+idle+iowait+irq+softirq
7. cpuUsed2=user+nice+system+irq+softirq
8. 得到cpu0在15秒内的平均使用率:(cpuUsed2 - cpuUsed1) * 100 / (cpuTotal2 - cpuTotal1)
CPU使用率每分钟会采集4次,上报一次。上报的是这一分钟内四次采集的最大值。

1.3 目录/dev/null

在类Unix操作系统中,设备节点并不一定要对应物理设备。没有这种对应关系的设备是伪设备。操作系统运用了它们提供的多种功能。部份经常使用到的伪设备包括: null,zero,full,loop,random,urandom

/dev/null文件是空设备,也称为位桶(bit bucket),它主要是用于“被写入”,任何写入它的输出都会被抛弃。【黑洞】

/dev/zero文件会产生连续不断的null的流(二进制的零流,而不是ASCII型的),可以使用/dev/zero来初始化文件。【白洞】

1.4 目录/dev/zero

如何用zero创造一个大文件:

$ dd if=/dev/zero of=testzero count=1024 bs=1024

$ dd if=/dev/zero of=hello.txt bs=100M count=1
1024+0 records in
1024+0 records out
1048576 bytes (1.0 MB) copied, 0.0107194 seconds, 97.8 MB/s
#创建一个大小为1M文件,该文件一个块是1024字节,一共是1024块(刚好1M),用/dev/zero文件内容填充它。输出创建到:testzero文件

$dd if=/dev/zero of=/dev/磁盘分区
#【慎用】有点象windows里面的粉碎文件工具。不过它是用\0x00填充整个分区。这样做数据是不可以恢复的了。

$cat /dev/zero>testinputzero
#【慎用】/dev/zero设备一个特效是,如果你读取的话,是一个死循环会输出无穷的\x00,这样你将创建一个用\x00填充的文件。如果你没有限制该用户磁盘配额。它将耗尽整个磁盘空间。

1.5 目录/dev/fd

xxx@xxx:~/xxx> ll /dev/fd
lrwxrwxrwx 1 root root 13 2013-08-23 22:12 /dev/fd -> /proc/self/fd
这里/proc/self/fd中的文件是访问该文件的进程的所有打开的文件描述符。

xxx@xxx:~/xxx> ll /dev/fd/
总计 0
lrwx------ 1 shellge users 64 2014-07-31 18:20 0 -> /dev/pts/0
lrwx------ 1 shellge users 64 2014-07-31 18:20 1 -> /dev/pts/0
lrwx------ 1 shellge users 64 2014-07-31 18:20 2 -> /dev/pts/0
lr-x------ 1 shellge users 64 2014-07-31 18:20 3 -> /proc/3441/fd
xxx@xxx:~/xxx> exec 6<&0
xxx@xxx:~/xxx> ll /dev/fd/
总计 0
lrwx------ 1 shellge users 64 2014-07-31 18:21 0 -> /dev/pts/0
lrwx------ 1 shellge users 64 2014-07-31 18:21 1 -> /dev/pts/0
lrwx------ 1 shellge users 64 2014-07-31 18:21 2 -> /dev/pts/0
lr-x------ 1 shellge users 64 2014-07-31 18:21 3 -> /proc/3806/fd
lrwx------ 1 shellge users 64 2014-07-31 18:21 6 -> /dev/pts/0
其中已有的文件描述符为bash本身打开的所有终端描述符,比如0号文件描述符对应的是伪终端pts10的硬件设备。shell的内建命令exec将并不启动新的shell,而是用要被执行命令替换当前的shell进程。exec 3<&0:这个命令就是将操作符3也指向标准输入。

1.6 linux为什么需要挂载?

mount和umount实际上做了什么?为什么需要挂载,将/dev/hdc挂载到/...下去,什么时候真正调用了设备驱动程序?与windows有什么区别?

挂载即把一个分区或磁盘放在一个目录里。
Windows其实也需要挂载,只不过它是把分区或磁盘挂载到A:B:C:……Z:这样开头的特定文件夹里。
Linux用目录名挂载分区或目录的好处是:一、便于操作,你可以把分区或磁盘挂载到任何你想要的目录或文件夹里。二、顾名思义,由于挂载的文件夹或目录可以自己指定,所以就可以挂载到自己创建的或相关内容的目录或文件夹里。三、挂载数量没有限制,windows的分区挂载到Z:就不能继续挂载了,或者说Windows挂载的分区数不能超过26个;而Linux则没有这个限制,特别是网络磁盘映射,你可以挂载任意多的共享文件夹到你的本地文件夹里。

一般的,当你插入一个U盘到linux系统时,首先在/dev/下会识别到一个设备,具体表现为多出来了一个文件,这个文件并不是盘符本身,将这个文件mount到任意一个路径下(文件夹下),则对应的盘符就被加载到了指定的位置,一般而言,我们将盘符挂载到/media/下或者/mnt/下,但事实上,你可以挂载到任何地方。

为什么不能直接访问linux或者unix的dev目录下面的逻辑设备文件?
在Linux中一般不能直接访问软盘、其他硬盘逻辑分区、光盘等,在Linux中它们都被视为文件(而不是我们所需要的一个路径,就是文件夹),在使用前必须使用装载命令mount将它们装载到系统的/mnt目录中,使用结束后还须卸载。相当与一个文件的打开和关闭的过程(而不是直接打开文件本身)

2 linux登录环境

2.1 Linux多用户的概念

Linux是一个真正的多用户操作系统,这表示它可以同时接受多个用户登录。
Linux还允许一个用户进行多次登录,这是因为Linux和许多版本的UNIX一样,提供了虚拟控制台的访问方式,允许用户在同一时间从控制台(系统的控制台是与系统直接相连的监视器和键盘)进行多次登录。

我的理解:linux多用户登陆是说,每个用户有一个账号,以该账号登陆(或者su - 切换到该账号)都会从/etc/passwd和/etc/shadow中读取并验证是否是合法用户,然后再分配一个shell进程,该进程初始化时按照脚本规定顺序依次读取系统环境变量和该用户环境变量。然后再该环境下运行其他程序。


2.2 Linux用户的环境变量读取顺序

每个用户都有自己专属的运行环境,这个环境是由一组变量所定义,这些变量称之为环境变量。用户可以修改环境变量以满足自己的要求。

2.2.1 login shell登陆模式:

1.1.1 login shell含义:用户通过终端登录凭借用户名和密码登录控制台的动作是login shell,也就是说最终会调用login命令的操作都可称之为login shell。

1.1.2 login shell读取文件的顺序是:
  /etc/profile
~/.bash_profile或~/.bash_login或~/.profile
/etc/profile 是必须要执行的,然后后面3个,按照顺序谁存在就执行谁,然后后面的就不会再执行。

其逻辑可用代码表示为:

execute /etc/profile
IF ~/.bash_profile exists THEN
execute ~/.bash_profile
ELSE
IF ~/.bash_login exist THEN
execute ~/.bash_login
ELSE
IF ~/.profile exist THEN
execute ~/.profile
END IF
END IF
END IF

退出交互控制台,执行的文件是:

IF ~/.bash_logout exists THEN
execute ~/.bash_logout
END IF

2.2.2 non-login shell登陆模式:

1.2.1 non-login shell含义:用户在图形界面启动一个terminal,或者执行/bin/bash,/usr/bin/bash都属于non-login shell。

1.2.2 non-login shell读取文件的顺序是:

对于~/.bashrc,是在non login shell 启动时执行,也就意味着在图形界面每开启一次terminal,就会读取一次该文件

IF ~/.bashrc exists THEN
execute ~/.bashrc
END IF


2.2.3 设置环境变量常见命令

1.echo 显示某个环境变量值 echo $PATH
2.export 设置一个新的环境变量 export HELLO="hello" (可以无引号)
3.env 显示所有环境变量
4.set 显示本地定义的shell变量
5.unset 清除环境变量 unset HELLO
6.readonly 设置只读环境变量 readonly HELLO

2.2.4 C语言设置环境变量

对于C程序的用户来说,可以使用下列三个函数来设置或访问一个环境变量:
getenv()访问一个环境变量。输入参数是需要访问的变量名字,返回值是一个字符串。如果所访问的环境变量不存在,则会返回NULL
setenv()在程序里面设置某个环境变量的函数
unsetenv()清除某个特定的环境变量的函数

另外,还有一个全局的指针变量environ,它指向的是包含所有的环境变量的一个列表。下面的程序可以打印出当前运行环境里面的所有环境变量:

#include <stdio.h>
extern char**environ;
int main ()
{
char**var;
for (var =environ;*var !=NULL;++var)
printf ("%s \n ",*var);
return 0;
}

3 linux权限分析

3.1 基础概念


linux编程细节1-内核-文件目录


3.1.1 文件相关概念


ls -al *.gz

linux编程细节1-内核-文件目录


3.1.1.1 文件类型

1,普通文件( regular file ): 第一个属性为 [ - ]
    a,纯文字文件(ascii)
    b,二进制文件(binary) 
2,目录 (directory): 第一个属性为 [ d ]
3,连结文件 (link): 第一个属性为 [ l ]
4,设备文件 (device):与系统周边相关的一些文件,通常都集中在 /dev 这个目录之下: 
    a,块 (block) 设备文件 : 第一个属性为 [ b ],就是一些储存数据,以提供系统存取的接口设备,简单的说就是硬盘啦!例如你的一号硬盘的代码是 /dev/hda1 等等的文件
    b,字符 (character) 设备文件 : 第一个属性为 [ c ],亦即是一些串行端口的接口设备,例如键盘、鼠标等

3.1.1.2 文件权限

每一个【文件/目录】都有三种权限设定(属主User,属组Group,其他人Others),而每一个子类又有([ r ]代表可读(read)、[ w ]代表可写(write)、[ x ]代表可执行(excute))三种设置。即(属主(rwx),属组(rwx),其他人(rwx))。

文件权限
1. 读(r):可以读取文件的内容。 
2. 写(w):可以编辑,修改文件内容。(但不能删除文件)
3. 执行(x):文件可以被系统执行(是否可执行与文件名无关)。

目录权限
目录主要的内容是记录文件名列表。
1. 读(r):表示具有读取目录结构列表的权限,表示你可以查询该目录下的文件名数据,可以利用ls命令将该目录的内容列表显示出来。
2. 写(w):表示你具有更改目录结构列表的权限,包括以下权限:
新建文件和目录
删除已存在的文件和目录
重命名已存在的文件和目录
转移该目录下的文件和目录
3. 执行(x):表示用户可以进入该目录作为工作目录。

3.1.1.3 文件属主

chown:改变【文件/目录】属主 

chown [-R] root:root install.log

3.1.1.4 文件属组

chgrp:改变【文件/目录】属组 

chgrp [-R] root install.log


3.1.2 进程相关概念

xxx

3.1.3 用户相关概念

3.1.3.1 passwd文件

系统账号信息(一般用户+root)都记录在 /etc/passwd 这个文件内【包含UID(使用者号码)GID(群组号码)】。密码则记录在 /etc/shadow 这个文件内。所有的群组名称都纪录在 /etc/group这个文件内。

查看文件/etc/passwd :

linux编程细节1-内核-文件目录


(1) 注册名(Uname) :用于区分不同的用户。在同一系统中注册名是惟一的。
(2) 口令(passwd) :通常将passwd文件中的口令字段使用一个“x”来代替,将 /etc/shadow作为真正的口令文件。 /etc/login.defs中记录了密码强度和加密算法,现在一般都用SHA加密,很难破解。
(3) 用户标识号(UID) :UID是一个数值,是Linux系统中惟一的用户标识,用于区别不同的用户。
(4) 组标识号(GID) :这是当前用户的缺省工作组标识。
(5) 用户信息(User Info)  :包含用户相关信息(可缺省)。
(6) 用户主目录(Home Directory):用户的主目录。 root的主目录为/root;其它用户分别在/home/XXX下。
(7) 命令解释程序(Shell) :Shell是当用户登录系统时运行的程序名称,通常是一个Shell程序的全路径名,如/bin/bash。 

需要注意的是,系统管理员通常没有必要直接修改passwd文件,Linux提供一些账号管理工具帮助系统管理员来创建和维护用户账号。 基本上,你只要建立一个账号,并且将他的 UID 设定为 0 的话,那么他就具有 root 的身份了!而一般使用者的号码通常在 500 以后,至于 1~499 之间,比较多会留给系统来使用!

3.1.3.2 su和sudo的区别

------------------------------------------------------------------------------------------------------
su=  su  root  表示切换到root用户,但环境不变:/home/xxx而不是/root
su  xxx表示切换到xxx用户,但环境不变:/root而不是/home/
------------------------------------------------------------------------------------------------------
su - =   su - root 表示切换到root用户,但环境改变:/root
su - xxx 表示切换到xxx用户,但环境改变:/home/xxx而不是/root
------------------------------------------------------------------------------------------------------

sudo 执行命令的流程是当前用户切换到root(或其它指定切换到的用户),然后以root(或其它指定的切换到的用户)身份执行命令,执行完成后,直接退回到当前用户;而这些的前提是要通过sudo的配置文件/etc/sudoers来进行授权;

3.1.4 nop

xxx

3.2 权限关系

linux编程细节1-内核-文件目录


3 linux文件内核

3.1 inode节点

文件描述符(系统函数层):在linux系统中,设备也是以文件的形式存在,要对该设备进行操作就必须先打开这个文件,打开文件就会获得文件描述符,它是个很小的正整数。每个进程在PCB(Process Control Block)中保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针。文件描述符的优点:兼容POSIX标准,许多Linux和UNIX系统调用都依赖于它。文件描述符的缺点:不能移植到UNIX以外的系统上去,也不直观。

文件流(IO库函数层):C语言中使用的是文件指针而不是文件描述符做为I/O的句柄。文件指针指向进程用户区中的一个被称为FILE结构的数据结构。FILE结构包括一个缓冲区(优点就是带有IO缓存)和一个文件描述符。而文件描述符是文件描述符表的一个索引,因此从某种意义上说文件指针就是句柄的句柄(在Windows系统上,文件描述符被称作文件句柄)。FILE *中除了包含了fd信息,还包含了IO缓冲,是C标准形式,所以FILE *比fd更适合跨平台,应该多用fopen在,少用open。

二者区别所以一个进程有一个文件描述符表,每个表项用文件描述符(非负整数)对应到一个文件的具体内容。而文件指针FILE*显然更上层,FILE指针将文件描述符和缓冲区封装在一起


每个进程在Linux内核中都有一个task_struct结构体来维护进程相关的信息,称为进程描述符(Process Descriptor),而在操作系统理论中称为进程控制块 (PCB,Process Control Block)。task_struct中有一个指针(struct files_struct *files; )指向files_struct结构体,称为文件描述符表,其中每个表项包含一个指向已打开的文件的指针,如下图所示。

linux编程细节1-内核-文件目录




用户程序不能直接访问内核中的文件描述符表,而只能使用文件描述符表的索引 (即0、1、2、3这些数字),这些索引就称为文件描述符(File Descriptor),用int 型变量保存。 当调用open 打开一个文件或创建一个新文件时,内核分配一个文件描述符并返回给用户程序,该文件描述符表项中的指针指向新打开的文件。当读写文件时,用户程序把文件描述符传给read 或write ,内核根据文件描述符找到相应的表项,再通过表项中的指针找到相应的文件。

已打开的文件在内核中用file结构体表示,文件描述符表中的指针指向file结构体。在file结构体中维护File Status Flag(file 结构体的成员f_flags)当前读写位置(file 结构体 的成员f_pos )。在下图中,进程1和进程2都打开同一文件,但是对应不同的file 结构体,因此可以有不同的File Status Flag和读写位置。file结构体中比较重要的成员还有f_count,表示引用计数(Reference Count),如dup 、fork 等系统调用会导致多个文件描述符指向同一个file 结构体,例如有fd1和fd2都引用同一个file结构体,那么它的引用计数就是2, 当close(fd1) 时并不会释放file结构体,而只是把引用计数减到1,如果再close(fd2) ,引用计数 就会减到0同时释放file结构体,这才真的关闭了文件。 每个file结构体都指向一个file_operations结构体,这个结构体的成员都是函数指针,指向实现各种文件操作的内核函数。比如在用户程序中read一个文件描述符,read通过系统调用进入内核, 然后找到这个文件描述符所指向的file结构体,找到file结构体所指向的file_operations结构体,调用它的read成员所指向的内核函数(如内核代码中实现函数可能为sys_read())以完成用户请求。在用户程序中调用lseek 、read 、write 、ioctl 、open 等函数,最终都由内核调用file_operations的各成员所指向的内核函数完成用户请求。file_operations结构体中的release成员用于完成用户程序的close请 求,之所以叫release而不叫close 是因为它不一定真的关闭文件,而是减少引用计数,只有引用计数减到0才关闭文件。对于同一个文件系统上打开的常规文件来说,read 、write 等文件操作的步骤和方法应该是一样的,调用的函数应该是相同的,所以图中的三个打开文件的file结构体指向同一个file_operations结构体。如果打开一个字符设备文件,那么它的read,write 操作肯定和常规文件不一样,不是读写磁盘的数据块而是读写硬件设备,所以file 结构体应该指向不同的file_operations 结构体,其中的各种文件操作函数由该设备的驱动程序实现。

linux编程细节1-内核-文件目录

每个file 结构体都有一个指向dentry结构体的指针,“dentry”是directory entry(目录项)的缩写。 我们传给open 、stat 等函数的参数的是一个路径,如/home/akaedu/a ,需要根据路径找到文件 的inode。为了减少读盘次数,内核缓存了目录的树状结构,称为dentry cache,其中每个节点是一个dentry结构体,只要沿着路径各部分的dentry搜索即可,从根目录/找到home 目录,然后找到akaedu目录,然后找到文件a。dentry cache只保存最近访问过的目录项,如果要找的目录项 在cache中没有,就要从磁盘读到内存中。 
每个dentry结构体都有一个指针指向inode结构体。inode结构体保存着从磁盘inode读上来的信 息。在上图的例子中,有两个dentry,分别表示/home/akaedu/a 和/home/akaedu/b ,它们都指向同 一个inode,说明这两个文件互为硬链接。inode 结构体中保存着从磁盘分区的inode读上来信息, 例如所有者、文件大小、文件类型和权限位等。每个inode 结构体都有一个指向inode_operations结 构体的指针,后者也是一组函数指针指向一些完成文件目录操作的内核函数。 
和file_operations 不同,inode_operations所指向的不是针对某一个文件进行操作的函数,而是影 响文件和目录布局的函数,例如添加删除文件和目录、跟踪符号链接等等,属于同一文件系统的 各inode 结构体可以指向同一个inode_operations结构体。 inode 结构体有一个指向super_block结构体的指针。super_block结构体保存着从磁盘分区的超级块 读上来的信息,例如文件系统类型、块大小等。super_block结构体的s_root成员是一个指 向dentry的指针,表示这个文件系统的根目录被mount 到哪里,在上图的例子中这个分区被mount 到/home 目录下。 

inode结构体记录了很多关于文件的信息,比如文件长度,文件所在的设备,文件的物理位置,创建、修改和更新时间等等,特别的,它不包含文件名!目录下的所有文件名和目录名都存储在目录的数据块中,即如下图的目录块。对于常规文件,文件的数据存储在数据块中,一个文件通常占用一个inode,但往往要占用多个数据块,数据块是在分区进行文件系统格式化时所指定的“最小存储单位”,块的大小为扇区的2^n倍,一个扇区512B。

linux编程细节1-内核-文件目录







3.2 文件描述符(系统调用)

3.2.1 文件描述符012

在linux中,万物皆为文件,包括设备、套接字、管道等。因此针对进程来说,每个进程都维护着一个私有的文件描述符表。它打开一个文件会从小到大分配一个文件描述符(从3开始)。从进程的角度看,一个进程出生时,默认打开了3个“文件”:
一个是标准输入(读写权限并且设置为只读,分配标准描述符0,用于输入);
一个是标准屏幕(读写权限并且设置为只写,分配标准描述符1,用于输出);
一个是标准屏幕(读写权限并且设置为只写,分配标准描述符2,用于错误输出)。

文件描述符值 POSIX标准 默认文件(设备)
0(====标准输入) STDIN_FILENO 键盘设备
1(====标准输出) STDOUT_FILENO 屏幕设备
2(====标准错误输出) STDERR_FILENO 屏幕设备

3.2.2 一个进程打开多个文件

linux编程细节1-内核-文件目录
文件表包括:
  文件状态标志:包含读,写,添写,同步和非阻塞等各种文件打开/当前状态。
  当前文件偏移量:记录文件指针位置
   V节点指针:文件类型和对此文件进行各种操作的函数的指针,这些信息都是在打开文件时候由磁盘读入内存的。
 

3.2.3 不同进程打开同一文件

linux编程细节1-内核-文件目录



3.2.4 调用dup函数

调用dup函数复制文件描述符,共享文件表项。

linux编程细节1-内核-文件目录

3.2.5 调用fork函数

调用fork函数,父子进程之间对打开文件的共享。

linux编程细节1-内核-文件目录

3.2.6 匿名管道实现

http://www.cnblogs.com/GODYCA/archive/2013/01/05/2845618.html

我们依此来理解一下管道的概念:如果两个不相关的进程打开同一个文件,一个只读方式开打,一个只写方式打开,不就相当于创建了一个管道了,进程A往里面写,进程B读,同样实现进程间通信。管道其实也一样。

匿名管道只能在父子进程间通信,原因就在于管道实现的进程间通信是在父进程fork()出子进程时,子进程会继承父进程的文件描述符表,而这个文件描述符表里记录了所有父进程打开的文件,所以子进程也继承了父进程打开的文件,所以父子进程可以通过同一个文件描述符去访问同一个文件,而管道就是建立在文件描述符上的,建立一个管道必须和两个文件描述符相关,一个可以赋予可读权限,一个可以赋予可写权限,但是这两个文件描述符其实都和同一个文件关联,所以你通过不同的文件描述符去访问这个文件时,可以获得不同的访问权限,如果在父子进程中,在父进程将fd[0]为可读,fd[1]可写,而子进程fd[0]可读,fd[1]可写,那么这个管道就将父子进程连起来,如下图所示:

linux编程细节1-内核-文件目录

3.3文件流(标准库)

在标准I/O库中,与底层文件描述符对应的对等物叫流(stream),它被实现为指向结构FILE的指针。

调用函数 文件流(FILE*类型) 缓冲机制 文件描述符 默认文件
scanf stdin 行缓冲 STDIN_FILENO(0) /dev/stdin
printf stdout 行缓冲 STDOUT_FILENO(1) /dev/stdout
printf stderr 无缓冲 STDERR_FILENO(2) /dev/stderr

3.3.1 缓冲机制分类

标准IO提供了3种缓冲机制:

缓冲区类型
全缓冲 _IOFBF
行缓冲 _IOLBF
无缓冲 _IONBF

1 全缓冲

这种情况下,在填满标准IO缓冲区后才进行实际IO操作。对于驻留在磁盘文件上的文件通常是由标准IO库实现全缓冲的。在一个流上执行第一次IO操作时,相关标准IO函数常调用malloc获得需要使用的缓冲区。
      术语flush说明标准IO缓冲区的写操作。缓冲区可由标准IO例程自动flush(例如填满一个缓冲区时),或者可以调用函数fflush,flush一个一个流。值得注意的是在UNIX环境中,flush有两种意思。在标准IO库方面,flush意味着将缓冲区中的内容写到磁盘上。在终端驱动程序方面,flush表示丢弃已存储在缓冲区中的数据。


2 行缓冲

在这种情况下,当输入和输出中遇到换行符时,标准IO库执行IO操作。这允许我们一次输出一个字符(fputc),但只有在写了一行后才进行实际的IO操作。当流操作涉及一个终端时,通常使用行缓冲。
对于行缓冲有两个限制。第一,因为标准IO库用来收集每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,那么即使还没有写一个换行符,也进行IO操作。第二,任何时候只要通过标准IO库要求从(a)一个不带缓冲的流,或者(b)一个行缓冲的流(它要求从内核的得到数据)得到输入数据,那么就会造成冲洗所有行缓冲输出流。在(b)中带了一个在括号中的说明,其理由是,所需要的数据可能是已在该缓冲区中,它并不要求在需要数据时才从内核读数据。很明显,在从不带缓冲的一个流中进行输入要求当时从内核得到数据。


3 不带缓冲

标准IO库不对字符进行缓冲存储。例如,如果用标准IO函数,fputs写15个字符到不带缓冲的一个流中,则该函数很可能用write系统调用函数将这些字符立即写到相关文件中。
标准出错流stderr通常是不带缓冲的,这就使得出错信息尽快显示出来。

4 ISO C要求下列缓冲特征

当且仅当标准输入和输出并不涉及交互式设备时,它们才是全缓冲的。
标准出错流绝不会是全缓冲的。

3.3.2 缓冲机制原理

UNIX的传统是Everything is a file,键盘、显示器、串口、磁盘等设备在/dev 目录下都有一个特殊的设备文件与之对应,这些设备文件也可以像普通文件(保存在磁盘上的文件)一样打开、读、写和关闭,使用的函数接口是相同的。用户程序调用C标准I/O库函数读写普通文件或设备,而这些库函数要通过系统调用把读写请求传给内核 ,最终由内核驱动磁盘或设备完成I/O操作。C标准库为每个打开的文件分配一个I/O缓冲区以加速读写操作,通过文件的FILE 结构体可以找到这个缓冲区,用户调用读写函数大多数时候都在I/O缓冲区中读写,只有少数时候需要把读写请求传给内核。

1 底层磁盘文件

以fgetc / fputc 为例,当用户程序第一次调用fgetc 读一个字节时,fgetc 函数可能通过系统调用进入内核读1K字节到I/O缓冲区中,然后返回I/O缓冲区中的第一个字节给用户,把读写位置指向I/O缓冲区中的第二个字符,以后用户再调fgetc ,就直接从I/O缓冲区中读取,而不需要进内核了,当用户把这1K字节都读完之后,再次调用fgetc时,fgetc函数会再次进入内核读1K字节 到I/O缓冲区中。在这个场景中用户程序、C标准库和内核之间的关系就像在“Memory Hierarchy”中 CPU、Cache和内存之间的关系一样,C标准库之所以会从内核预读一些数据放在I/O缓冲区中,是希望用户程序随后要用到这些数据,C标准库的I/O缓冲区也在用户空间,直接从用户空间读取数据比进内核读数据要快得多。另一方面,用户程序调用fputc 通常只是写到I/O缓冲区中,这样fputc函数可以很快地返回,如果I/O缓冲区写满了,fputc 就通过系统调用把I/O缓冲区中的数据传给内核,内核最终把数据写回磁盘或设备。有时候用户程序希望把I/O缓冲区中的数据立刻传给内核,让内核写回设备或磁盘,这称为Flush操作,对应的库函数是fflush,fclose函数在关闭文件 之前也会做Flush操作。

下图以fgets / fputs 示意了I/O缓冲区的作用,使用fgets / fputs 函数时在用户程序中也需要分配缓冲区(图中的buf1 和buf2 ),注意区分用户程序的缓冲区和C标准库的I/O缓冲区。

linux编程细节1-内核-文件目录

注意:虽然write 系统调用位于C标准库I/O缓冲区的底 层,被称为Unbuffered I/O函数,但在write 的底层也可以分配一个内核I/O缓冲区,所以write 也不一定是直接写到文件的,也 可能写到内核I/O缓冲区中,可以使用fsync函数同步至磁盘文件,至于究竟写到了文件中还是内核缓冲区中对于进程来说是没有差别 的,如果进程A和进程B打开同一文件,进程A写到内核I/O缓冲区中的数据从进程B也能读到,因为内核空间是进程共享的, 而c标准库的I/O缓冲区则不具有这一特性,因为进程的用户空间是完全独立的.

2 底层终端缓冲

以输入队列为例,从键盘输入的字符经线路规程过滤后进入输入队列,用户程序以先进先出的顺序 从队列中读取字符,一般情况下,当输入队列满的时候再输入字符会丢失,同时系统会响铃警报。 终端可以配置成回显(Echo)模式,在这种模式下,输入队列中的每个字符既送给用户程序也送给 输出队列,因此我们在命令行键入字符时,该字符不仅可以被程序读取,我们也可以同时在屏幕上看到该字符的回显。
       注意上述情况是用户进程(shell进程也是)调用read/write等unbuffer I/O函数的情况,当调用printf/scanf(底层实现也是read/write)等C标准I/O库函数时,当用户程序调用scanf读取键盘输入时,开始输入的字符都存到C标准库的I/O缓冲区,直到我们遇到换行符(标准输入和标准输出都是行缓冲的)时,系统调用read将缓冲区的内容读到内核的终端输入队列;当调用printf打印一个字符串时,如果语句中带换行符,则立刻将放在I/O缓冲区的字符串调用write写到内核的输出队列,打印到屏幕上,如果printf语句没带换行符,则由上面的讨论可知,程序退出时会做fflush操作.

linux编程细节1-内核-文件目录


3.3.3 nop

xxx

3.6 Linux系统为什么需要挂载

问:本来的疑惑主要是:在linux中,使用U盘,如果不挂载可以通过cd /dev/sdb1直接访问么?

答:驱动设备是无法直接 cd /dev/sdb1 的??(开机引导时加载为/dev/sdb1),打个比方,在windows里插入U盘时任务栏会提示驱动器安装成功,就相当于linux里的mount命令一样,只不过windows里是自动挂载的,但如果你用了linux的图形界面,你会发现和windows里一样可以直接打开光盘等块设备。

3.7 如何理解终端概念????

/dev/console /dev/tty /dev/pty  /dev/ttySn

http://laiyuanyuan7.blog.163.com/blog/static/1527432120124118413770/


3.8 linux和windows下的换行符

windows换行是\r\n,十六进制数值是:0D0A。
LINUX换行是\n,十六进制数值是:0A
所以unix的文本到windows会出现换行丢失(ultraedit这种软件可以正确识别); 而反过来就会出现^M的符号了
Windows等操作系统用的文本换行符和UNIX/Linux操作系统用的不同,Windows系统下输入的换行符在UNIX/Linux下不会显示为“换行”,而是显示为 ^M 这个符号(这是Linux等系统下规定的特殊标记,占一个字符大小,不是 ^ 和 M 的组合,打印不出来的)。Linux下很多文本编辑器(命令行)会在显示这个标记之后,补上一个自己的换行符,以避免内容混乱(只是用于显示,补充的换行符不会写入文件,有专门的命令将Windows换行符替换为Linux换行符)。 UNIX/Linux系统下的换行符在Windows系统的文本编辑器中会被忽略,整个文本会乱成一团。

3.9 linux下的终端设备

http://blog.chinaunix.net/uid-20543672-id-3225777.html
linux编程细节1-内核-文件目录

终端是一种字符型设备,它有多种类型,通常使用tty来简称各种类型的终端设备。tty是Teletype的缩写。在Linux系统的设备特殊文件目录/dev/下,终端特殊设备文件一般有以下几种:

3.9.1 串行端口终端(/dev/ttySn)

串行端口终端(Serial   Port   Terminal)是使用计算机串行端口连接的终端设备。计算机把每个串行端口都看作是一个字符设备。有段时间这些串行端口设备通常被称为终端设备,因为那时它的最大用途就是用来连接终端。这些串行端口所对应的设备名称是/dev/tts/0(或/dev/ttyS0)、/dev/tts/1(或   /dev/ttyS1)等,分别对应于DOS系统下的COM1、COM2等。若要向一个端口发送数据,可以在命令行上把标准输出重定向到这些特殊文件名上即可。例如,在命令行提示符下键入:echo   test   > /dev/ttyS1会把单词”test”发送到连接在ttyS1(COM2)端口的设备上。

3.9.2 伪终端,虚拟终端(/dev/pty/)

但是如果我们远程telnet到主机或使用xterm时不也需要一个终端交互么?是的,这就是虚拟终端pty(pseudo-tty)。pts(pseudo-terminal slave)是pty的实现方法,与ptmx(pseudo-terminal master)配合使用实现pty。


3.13 文件空洞

在UNIX文件操作中,文件位移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将延长该文件,并在文件中构成一个空洞(看起来文件变大了,但实际上没有占用磁盘空间,因为只是用指针将数据块链起来,并没有顺序占用磁盘块),这一点是允许的。位于文件中但没有写过的字节都被设为 0。

linux系统自己有cat,cp命令可以复制文件,但是对空洞的处理方式不同:
1 cat  file> file _copy  :复制输出的文件将使空洞全填为字符0,文件实际大小和原空洞文件大小一致,但是空洞部分化为字符0占用磁盘空间;
2 cp  file  file_copy : 复制输出的文件不填平空洞,文件实际大小和原空洞文件大小一致,空洞部分仍是空洞,不占用磁盘空间;


3.2 系统头文件vs系统库文件

http://www.cnblogs.com/qiufenghui/archive/2012/07/01/2572120.html

3.2.1 头文件 

对c语言来说,Ubuntu头文件几乎全部位于/usr/include目录及其子目录中,而对于其他编程语言的头文件会存在对于的目录下,并且会自动对头文件进行搜索,例如GNU C++的头文件位于 /usr/include/c++下。

可以使用-I标志来包含保存在子目录或非标准位置中的头文件(可能是自定义头文件,存放于其他文件夹中的,需要时可以添加进来!)      

//fred.c文件中包含的头文件也可以在/usr/openwin/include目录中查找!
gcc-I/usr/openwin/include fred.c


3.2.2 库文件

标准系统库文件一般存放于/usr/lib和/lib,C语言编译器默认搜索c语言库!
库文件命名也很有意思,基本以lib开头,随后部分指明是什么库(c代表C语言库,m代表数学库,例如libm.a代表数学库)!
库文件类型: .a代表传统的静态函数库,.so代表共享函数库
库文件添加方式:1.给出完整的库文件路径  2.-l标志告诉编译器要搜索的库文件.

gcc -o fred fred.c /usr/lib/libm.a
gcc -o fred fred.c -lm //给出完整路径方式可能更好,因为-l标志搜索的是环境变量中设置的库文件路径,可能会不全的哦!
如果标准库文件中没有含有该库文件,可以使用-L标志添加库搜索路径.
gcc -o x11fred -L/usr/openwin/lib x11fred.c -lX11 //这个例子比较全面,既有增加搜索路径,也有默认路径搜索!