-
Notifications
You must be signed in to change notification settings - Fork 47
/
Copy pathlinux_进程_文件的关系
168 lines (135 loc) · 10.4 KB
/
linux_进程_文件的关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
-----------------------------------------------------------
Linux进程与文件的关系
-----------------------------------------------------------
Linux进程(线程)、文件描述符、文件的关系:
具体到内核里面,就是 task_struct、files_struct、file 和 inode 的关系了。
问:我们在Linux用户态开发时,打开一个文件之后会返回一个文件描述符,而且每个进程打开文件的数量是有限的, 这是什么原因?
答:
需要深入到Linux内核了。
在Linux操作系统,每个用户态的进程,在内核态都有一个对应的内核进程(线程),这个在内核中通过 task_struct 结构体标识,内核通过它,实现对进程的调度。
---插入内容---
对于Linux 0.11内核来讲,系统最多可有64个进程同时存在。除了第一个进程是"手工"建立以外,其余的都是晋城市用系统调用fork创建的新进程,
被创建的进程称为子进程,创建者被称为父进程。内核程序使用进程标识号(PID)来标识每个进程。
进程由【可执行的指令代码】,【数据】,【堆栈区】组成。
进程中的代码和数据部分,分别对应一个可执行文件中的代码段、数据段。
每个进程只能执行自己的代码和访问自己的数据及堆栈区。
进程之间的通信,需要通过系统调用来进行。
对于只有一个CPU的系统,在某一时刻只能有一个进程在运行。内核通过调度程序,分时调度执行各个进程。
Linux系统中,一个进程可以在内核态(Kernel Mode)或用户态(User Mode)下执行,因此,Linux内核堆栈和用户堆栈是分开的。
用户堆栈,用于进程在用户态下临时保存调用函数的参数、局部变量、等数据。
内核堆栈,则含有内核程序执行函数调用时的信息。
---插入内容---
而在内核中,对于文件的访问,则是通过 file 和 inode 结构体实现的。其中包含这访问文件的关键信息(例如访问偏移)和方法(例如读写文件操作)。
fd=open(“/home/zhf/zhf/c_prj/itworld123.com”,O_RDWR);
-----------------------------------------------------------
进程与文件的关系:
-----------------------------------------------------------
如下图是典型的进程与文件的关系图,图中进程打开了两个不同的文件。
在进程结构体(task_struct)中有一个files_struct成员,其中保存这一个数组,这个数组的偏移量就是文件描述符,而其中的成员则是file结构体的指针。
这样,通过用户态的整型的文件描述符fd, 可以很方便的找到 管理文件的结构体(file), 进而实现对文件的操作。
为了方便,对files_struct结构体进行了简化,实际上该结构要复杂很多。
+-----------------------------------+
| task_struct |
| |
| |
+--+ +files: file_struct * |
| | |fs: fs_struct * |
| | |
| +-----------------------------------+ +----------------+
+--------------+ | |
| | |
+-----------v------------------+ | +------------v---------------+
| files_struct | | | file |
| +fdx: file * +--------+ | +f_inode: inode +--+
| +fdy: file * | | +f_mapping: address_space | |
| +fdz: file * +--------+ | +f_path: path | |
| | | | +method(type): type | |
+------------------------------+ | +----------------------------+ |
| +--------------------------+ |
| | inode <----+
| | +i_mode: umode_t |
| | +i_op: inode_operations |
| | |
| +--------------------------+
|
+-----------------+
|
+-------------v--------------+
| file |
| +f_inode: inode +--+
| +f_mapping: address_space | |
| +f_path: path | |
| +method(type): type | |
+----------------------------+ |
+--------------------------+ |
| inode | |
| +i_mode: umode_t <----+
| +i_op: inode_operations |
| |
+--------------------------+
问:进程(task_struct)与文件结构体(file)的关系清楚了,那么文件结构体又是怎么来的,它跟inode的关系是什么样的呢?
答:如上图所示,每一个file,都有一个对应的inode的结构体。
两种其实都对应着一个磁盘上的具体文件,但又有差异。
file其实对应这一个打开文件的实例,而inode一一对应一个磁盘文件。
也就是说一个磁盘文件,对应的file在内存中可能有多份,而inode则只会有一份。后续我们会详细解释具体实现。
-----------------------------------------------------------
父/子进程与文件的关系:
-----------------------------------------------------------
我们知道在Linux中进程都存在一些父子关系,而且子进程会继承父进程的很多内容。
那么如果我们fork出一个子进程,此时子进程会继承父进程文件相关的内容。
如果我们使用的是fork系统调用,此时子进程会创建一个新的files_struct实例,并将父进程的内容迁移过来,
这里说迁移,而不是拷贝,其原因是并不是原封不懂的内存拷贝,而是会做一些处理。
比如父进程中对file结构体的指向,在子进程中也会指向,且文件描述符一致,同时会增加file结构体实例的引用计数,确保使用关系的正确性:
+-----------------------------------+
| task_struct |
| |
| +files: file_struct * +-----+-----------------+
| +fs: fs_struct * | | | +---------------+
| | | +-------------v--------------+ | +------------v-------------+
+-----------------------------------+ | | file | | | inode |
| | +f_inode: inode +---+ | +i_mode: umode_t |
| | +f_mapping: address_space | | +i_op: inode_operations |
| | +f_path: path | | |
| | +method(type): type | +--------------------------+
| +----------------------------+
+-----------------------------------+ |
| task_struct | |
| | |
| +files: file_struct * +----+
| +fs: fs_struct * |
| |
+-----------------------------------+
还有一种情况,Linux操作系统提供了另外一种创建子进程的方法,也就是clone方法。
通过该方法创建进程时可以指定子进程可以不继承父进程的那些内容。
如果此时传入了CLONE_FILES参数,则不会进行files_struct结构体的迁移,而只是增加一个该结构体的引用计数。
也就是父进程和子进程中的files指针,指向相同的内存区域,此时整个关系如下所示:
+---------------------------------+
| task_struct | +------------------------------+ +----------------+
| +files: files_struct * +-----+--> files_struct | | | +---------------+
+---------------------------------+ | | +fdx: file * +--+ +-------------v--------------+ | +------------v-------------+
| | |fdy: file * | | file | | | inode |
+---------------------------------+ | | +fdz: file * | | +f_inode: inode +---+ | +i_mode: umode_t |
| task_struct +-----+ | | | |f_mapping: address_space | | +i_op: inode_operations |
| +files: files_struct * | +------------------------------+ | |f_path: path | | |
+---------------------------------+ | |method(type): type | +--------------------------+
+----------------------------+
---------------------------------------------------------
file结构体, inode, 磁盘数据的关系:
---------------------------------------------------------
简单理解 struct inode 和 struct file,cdev 和 inode的关系的话:
内核中用inode结构表示具体的文件,而用file结构表示打开的文件描述符。
inode中对于编写驱动代码有用的主要成员是dev_t i_rdev,struct cdev * i_cdev。
对于代表设备文件的结点,dev_t i_rdev包含了实际的设备编号:
内核中提供了两个宏,可以提取inode中的主次编号。
unsigned int iminor(struct inode * inode);
unsigned int imajor(struct inode * inode);
当结点inode指的是一个字符设备文件时,成员struct cdev * i_cdev指针指向的就是这个字符设备。
int register_chrdev_region(dev_t first, unsigned int count, char * name);
//first是手动给定的设备编号,主、次设备号通过MKDEV宏生成。count是所请求的连续设备号的个数。
int alloc_chrdev_region(dev_t * dev, unsigned int firstminor, unsigned int count, char * name);
//这是动态分配设备号。
要注意的是,这两个函数仅仅是注册设备号!如果要和cdev关联起来,还要调用cdev_add()。
cdev 和 inode的关系:
如果cdev中有两个inode,代表有两个设备文件,
这俩inode结构当中的指针*i_cdev连接到相同的cdev,代表这两个设备文件指向同一类型设备。
而cdev下的list链表表示cdev这种类型的设备有两个。