进程学习总结(一)
概述
在计算机中,进程是正在执行的计算机程序的实例。它包含程序代码及其当前活动(即程序的状态)。一个进程可能由多个并行执行指令的执行线程组成。
C程序的组成
C程序⼀直由下列⼏部分组成:
正⽂段
这是由C P U执⾏的机器指令部分。通常,正⽂段是可共享的,所以即使是经常执⾏的程序(如⽂本编辑程序、 C编译程序、 s h e l l等)在存储器中也只需有⼀个副本,另外,正⽂段常常是只读的,以防⽌程序由于意外事故⽽修改其⾃身的指令。
初始化数据段
初始化数据段通常将此段称为数据段,它包含了程序中需赋初值的变量。全局变量就是初始化数据段。
例如:int global = 88;
⾮初始化数据段
⾮初始化数据段通常称为b s s段(block started bysymbol(由符号开始的块))。全局变量的声明就是非初始化数据段。
例如:long sum[1000] ;
此变量存放在⾮初始化数据段中。
栈
⾃动变量以及每次函数调⽤时所需保存的信息都存放在此段中。每次函数调⽤时,其返回地址、以及调⽤者的环境信息(例如某些机器寄存器)都存放在栈中。然后,新被调⽤的函数在栈上为其⾃动和临时变量分配存储空间。
例如:C函数递归调⽤的返回的临时变量就是保存在栈里。
堆
动态存储分配的变量就是保存在堆上。
测试程序
初始程序
#include <stdio.h>
int main(){
return 0;
}
size a.out
[root@bogon ~]# size a.out
text data bss dec hex filename
1127 540 4 1671 687 a.out
其中dec = text + data + bss,单位为字节,10进制。hex为dec 的16进制。下同。
测试程序1
#include <stdio.h>
int a;
int a1;
int b1;
int b2;
int c1;
int main(){
return 0;
}
[root@localhost ~]# size a.out
text data bss dec hex filename
1127 540 28 1695 69f a.out
bss段增加了24个字节,bss段以8个字节作为对齐字节。例如上面测试程序,增加一个变量bss段增加8个字节,再增加一个变量,bss段字节数不变,再增加一个字节则bss段再增加8个字节,以此类推。
测试程序2
#include <stdio.h>
int d1=1;
int d2=2;
int d3=4;
int d4=5;
int d5=6;
int main(){
return 0;
}
[root@localhost ~]# size a.out
text data bss dec hex filename
1127 560 8 1695 69f a.out
data 段增加了20个字节,bss段增加了一个字节。在测试中发现,初始化的全局变量为单数时,bss字节数增加4个字节,当初始化的全局变量为双数时,bss段字节数在单数时的字节数减4个字节。
进程创建的相关函数
fork函数
#include <unistd.h>
pid_tfork(void);
⼀个现存进程调⽤fork函数是UNIX内核创建⼀个新进程的唯⼀⽅法。
该函数被调⽤⼀次,但返回两次。两次返回的区别是⼦进程的返回值是 0,⽽⽗进程的返回值则是新⼦进程的进程 ID。
⼦进程和⽗进程继续执⾏ fork之后的指令。
⼦进程是⽗进程的复制品。例如,⼦进程获得⽗进程数据空间、堆和栈的复制品。
父子进程继承的数据
⽗进程的性质由⼦进程继承:
• 实际⽤户I D、实际组I D、有效⽤户I D、有效组I D。
• 添加组I D。
• 进程组I D。
• 对话期I D。
• 控制终端。
• 设置-⽤户- I D标志和设置-组- I D标志。
• 当前⼯作⽬录。
• 根⽬录。
• ⽂件⽅式创建屏蔽字。
• 信号屏蔽和排列。
• 对任⼀打开⽂件描述符的在执⾏时关闭标志。
• 环境。
• 连接的共享存储段。
• 资源限制。
• 打开的文件描述符。
⽗、⼦进程之间的区别是:
• fork的返回值。
• 进程I D。
• 不同的⽗进程I D。
• ⼦进程的t m s _ u t i m e, t m s _ s t i m e , t m s _ c u t i m e以及t m s _ us t i m e设置为0。
• ⽗进程设置的锁,⼦进程不继承。
• ⼦进程的未决告警被清除。
• ⼦进程的未决信号集设置为空集。
测试程序1
#include<unistd.h>
#include<stdio.h>
int globvar = 6; /* external variable in initializeddata */
char buf[] = "a write to stdout\n";
int
main(void)
{
int var; /* automatic variable on the stack*/
pid_t pid;
var = 88;
if (write(STDOUT_FILENO, buf,sizeof(buf)-1) != sizeof(buf)-1)
return -1;
printf("before fork\n"); /* we don't flush stdout */
if ((pid = fork()) < 0) {
return -2;
} else if (pid == 0) { /* child */
globvar++; /* modifyvariables */
var++;
} else {
sleep(2); /* parent */
}
printf("pid = %ld, glob = %d, var= %d\n", (long)getpid(), globvar, var);
return 0;
}
运行结果:
[root@localhost proc]# ./a.out
a write to stdout
before fork
pid = 6084, glob = 7, var = 89
pid = 6083, glob = 6, var = 88
[root@localhost proc]# ./a.out > a.log
[root@localhost proc]# vi a.log
[root@localhost proc]# tail -f a.log
a write to stdout
before fork
pid = 6100, glob = 7, var = 89
before fork
pid = 6099, glob = 6, var = 88
程序说明:
write函数是不带缓存的。因为在fork之前调⽤write,所以其数据写到标准输出⼀次。
标准 I / O库是带缓存的。如果标准输出连到终端设备,则它是⾏缓存的,否则它是全缓存的。当以交互⽅式运⾏该程序时,只得到printf输出的⾏⼀次,其原因是标准输出缓存由新⾏符刷新。
但是当将标准输出重新定向到⼀个⽂件时,却得到 print f输出⾏两次。其原因是,在 fork之前调⽤了printf⼀次,但当调⽤fork时,该⾏数据仍在缓存中,然后在⽗进程数据空间复制到⼦进程中时,该缓存数据也被复制到⼦进程中。于是那时⽗、⼦进程各⾃有了带该⾏内容的缓存。
在 exit之前的第⼆个printf将其数据添加到现存的缓存中。当每个进程终⽌时,其缓存中的内容被写到相应⽂件中。
参考资料
[1].《Unix高级编程》
[2]. https://en.wikipedia.org/wiki/Process_(computing)