Linux:进程(二)
目录
一、cwd的理解
二、fork的理解
1.代码共享
2.各司其职
3.fork的返回值
三、进程状态
1.进程排队
2.进程状态
运行状态
一、cwd的理解
cwd(current working directory)。译为当前工作目录。
在C语言中,使用fopen函数打开文件时,第一个参数为当前目录下的某个文件的文件名,那么如何理解“当前目录”。
我们已经知道在/proc目录下,以文件的形式存放着每一个进程。
需要注意的是,只有当一个可执行程序被执行后,对应的进程文件才会在/proc目录下出现。
[euto@VM-4-13-centos 24915]$ ./myprocess
i am a process! id: 3800,fid: 2963
i am a process! id: 3800,fid: 2963
i am a process! id: 3800,fid: 2963
i am a process! id: 3800,fid: 2963
···········
同时在/proc目录下,出现一个进程目录3800。
[euto@VM-4-13-centos 24915]$ ls /proc
1 16 264 36 560 acpi kallsyms scsi
10 170 265 3647 562 buddyinfo kcore self
1005 17110 269 37 563 bus keys slabinfo
1071 18 27 3721 568 cgroups key-users softirqs
1074 1825 275 376 575 cmdline kmsg stat
1077 19 28 38 6 consoles kpagecount swaps
11 196 28150 3800 637 cpuinfo kpageflags sys
1100 2 285 4 638 crypto loadavg sysrq-trigger
1196 20 28704 400 639 devices locks sysvipc
12 21 28720 403 640 diskstats mdstat timer_list
1206 22 28721 407 641 dma meminfo timer_stats
1207 23 29 4452 65 driver misc tty
1218 23737 290 46 671 execdomains modules uptime
1219 24 291 48 7 fb mounts version
13 25 2959 49 8 filesystems mtrr vmallocinfo
1347 259 2962 50 9 fs net vmstat
1354 25995 2963 51 9648 interrupts pagetypeinfo xpmem
1373 26 30772 516 9684 iomem partitions zoneinfo
14 262 3123 5375 9802 ioports sched_debug
14075 263 35 5421 9803 irq schedstat
我们可以查看进程3800的目录。
[euto@VM-4-13-centos 24915]$ ll /proc/3800
total 0
dr-xr-xr-x 2 euto euto 0 Sep 18 10:20 attr
-rw-r--r-- 1 euto euto 0 Sep 18 10:20 autogroup
-r-------- 1 euto euto 0 Sep 18 10:20 auxv
-r--r--r-- 1 euto euto 0 Sep 18 10:20 cgroup
--w------- 1 euto euto 0 Sep 18 10:20 clear_refs
-r--r--r-- 1 euto euto 0 Sep 18 10:17 cmdline
-rw-r--r-- 1 euto euto 0 Sep 18 10:20 comm
-rw-r--r-- 1 euto euto 0 Sep 18 10:20 coredump_filter
-r--r--r-- 1 euto euto 0 Sep 18 10:20 cpuset
lrwxrwxrwx 1 euto euto 0 Sep 18 10:17 cwd -> /home/euto/linux/24915
-r-------- 1 euto euto 0 Sep 18 10:17 environ
lrwxrwxrwx 1 euto euto 0 Sep 18 10:17 exe -> /home/euto/linux/24915/myprocess
dr-x------ 2 euto euto 0 Sep 18 10:17 fd
dr-x------ 2 euto euto 0 Sep 18 10:20 fdinfo
-rw-r--r-- 1 euto euto 0 Sep 18 10:20 gid_map
-r-------- 1 euto euto 0 Sep 18 10:20 io
·······················
需要注意两个信息,exe所指向的就是该进程对应的可执行程序所在目录。
这里还有一个信息就是cwd,不难总结出来,cwd和进程是深深绑定的,换句话说,我们平时说讲的“当前目录”,其实就是和进程绑定的cwd。因此,在C语言中使用fopen打开当前目录下某个文件的操作,其实就是可执行程序运行起来后,对应的进程中cwd所指目录下的某个文件。
下面对这个结论作验证,通过执行指令chdir来演示,chdir的功能用来改变当前的工作目录。
CHDIR(2) Linux Programmer's Manual CHDIR(2)
NAME
chdir, fchdir - change working directory
SYNOPSIS
#include <unistd.h>
int chdir(const char *path);
int fchdir(int fd);
······
修改源文件为以下内容。
[euto@VM-4-13-centos 24915]$ cat myprocess.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
printf("当前进程pid:%d\n",getpid());
printf("修改目录前\n");
sleep(30);
printf("修改目录后\n");
chdir("/home/euto/");
sleep(30);
FILE* fp = fopen("test.txt","w");
fclose(fp);
return 0;
}
演示结果应该为:修改目录前,cwd为当前文件所在的目录24915。修改目录后,cwd更改为/home/euto,执行fopen打开文件test.txt时,由于test.txt文件并不存在,所以新建一个文件test.txt,新建文件所在的目录是/home/euto。
[euto@VM-4-13-centos 24915]$ ./myprocess
当前进程pid:16846
修改目录前
[euto@VM-4-13-centos 24915]$ ll /proc/16846
total 0
dr-xr-xr-x 2 euto euto 0 Sep 18 10:43 attr
-rw-r--r-- 1 euto euto 0 Sep 18 10:43 autogroup
-r-------- 1 euto euto 0 Sep 18 10:43 auxv
-r--r--r-- 1 euto euto 0 Sep 18 10:43 cgroup
--w------- 1 euto euto 0 Sep 18 10:43 clear_refs
-r--r--r-- 1 euto euto 0 Sep 18 10:43 cmdline
-rw-r--r-- 1 euto euto 0 Sep 18 10:43 comm
-rw-r--r-- 1 euto euto 0 Sep 18 10:43 coredump_filter
-r--r--r-- 1 euto euto 0 Sep 18 10:43 cpuset
lrwxrwxrwx 1 euto euto 0 Sep 18 10:43 cwd -> /home/euto/linux/24915
-r-------- 1 euto euto 0 Sep 18 10:43 environ
lrwxrwxrwx 1 euto euto 0 Sep 18 10:43 exe -> /home/euto/linux/24915/myprocess
dr-x------ 2 euto euto 0 Sep 18 10:43 fd
dr-x------ 2 euto euto 0 Sep 18 10:43 fdinfo
·······
程序执行chdir修改目录后。
[euto@VM-4-13-centos 24915]$ ./myprocess
当前进程pid:16846
修改目录前
修改目录后
[euto@VM-4-13-centos 24915]$ ll /proc/16846
total 0
dr-xr-xr-x 2 euto euto 0 Sep 18 10:43 attr
-rw-r--r-- 1 euto euto 0 Sep 18 10:43 autogroup
-r-------- 1 euto euto 0 Sep 18 10:43 auxv
-r--r--r-- 1 euto euto 0 Sep 18 10:43 cgroup
--w------- 1 euto euto 0 Sep 18 10:43 clear_refs
-r--r--r-- 1 euto euto 0 Sep 18 10:43 cmdline
-rw-r--r-- 1 euto euto 0 Sep 18 10:43 comm
-rw-r--r-- 1 euto euto 0 Sep 18 10:43 coredump_filter
-r--r--r-- 1 euto euto 0 Sep 18 10:43 cpuset
lrwxrwxrwx 1 euto euto 0 Sep 18 10:43 cwd -> /home/euto
-r-------- 1 euto euto 0 Sep 18 10:43 environ
lrwxrwxrwx 1 euto euto 0 Sep 18 10:43 exe -> /home/euto/linux/24915/myprocess
dr-x------ 2 euto euto 0 Sep 18 10:43 fd
········
查看/home/euto目录,发现test.txt文件新建在了这个目录,和预期符合。
[euto@VM-4-13-centos 24915]$ ls /home/euto/
linux test.txt
二、fork的理解
运行man fork查看fork的相关介绍。
FORK(2) Linux Programmer's Manual FORK(2)
NAME
fork - create a child process
SYNOPSIS
#include <unistd.h>
pid_t fork(void);
fork这个函数用来创建一个子进程。
父进程和子进程的关系:一个父进程可以有多个子进程,而一个子进程的父进程是唯一的。
fork的返回值介绍。
RETURN VALUE
On success, the PID of the child process is returned in the parent,
and 0 is returned in the child. On failure, -1 is returned in the parent,
no child process is created, and errno is set appropriately.
子进程创建失败,返回负数。
子进程创建成功,给父进程返回子进程的pid,给子进程返回0。(pid都是大于0的整数)
1.代码共享
- 结论:使用fork创建子进程后,父进程和子进程共享代码。
解释:
在目录24918下编辑源文件myprocess.c的内容。
[euto@VM-4-13-centos 24918]$ cat myprocess.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
printf("fork之前:i am a process ,pid:%d,ppid:%d",getpid(),getppid());
fork();
printf("fork之后:i am a process ,pid:%d,ppid:%d",getpid(),getppid());
return 0;
}
运行结果显示。
[euto@VM-4-13-centos 24918]$ ./myprocess
fork之前:i am a process ,pid:16709,ppid:6682
fork之后:i am a process ,pid:16709,ppid:6682//父进程
fork之后:i am a process ,pid:16710,ppid:16709//子进程
第二句printf被打印了两次的原因是,父进程和子进程分别进行了打印。在执行fork这个函数后,子进程就已经在内存中存在了,子进程和父进程指向同一个可执行程序,在fork函数后需要执行的代码被父子进程共享。
其中,一条打印结果的ppid是另一条打印结果的pid,说明这是父子进程。
关于子进程的PCB,大部分内容和父进程相同。
关于共享的可执行程序,代码和数据都只有一份。
2.各司其职
- 结论:一般而言,父进程和子进程完成的功能是不同的,源文件的代码只有一份,但是通过条件判断语句让父进程和子进程可以实现不同的功能。
解释:编辑源文件myprocess.c的内容如下。
int main()
{
printf("fork之前i am a process,pid:%d ,ppid:%d\n",getpid(),getppid());
sleep(5);
printf("开始创建子进程\n");
pid_t id = fork();
if(id < 0){
return 1;
}
else if(id == 0)
{
//子进程
while(1)
{
printf("fork之后,我是子进程,pid:%d,ppid:%d,id:%d\n",getpid(),getppid(),id);
sleep(1);
}
}
else{
//父进程
while(1)
{
printf("fork之后,我是父进程,pid:%d,ppid:%d,id:%d\n",getpid(),getppid(),id);
sleep(1);
}
}
return 0;
}
运行结果如下。
[euto@VM-4-13-centos 24918]$ ./myprocess
fork之前i am a process,pid:32557 ,ppid:30035
开始创建子进程
fork之后,我是父进程,pid:32557,ppid:30035,id:32570
fork之后,我是子进程,pid:32570,ppid:32557,id:0
fork之后,我是父进程,pid:32557,ppid:30035,id:32570
fork之后,我是子进程,pid:32570,ppid:32557,id:0
fork之后,我是父进程,pid:32557,ppid:30035,id:32570
fork之后,我是子进程,pid:32570,ppid:32557,id:0
fork之后,我是父进程,pid:32557,ppid:30035,id:32570
fork之后,我是子进程,pid:32570,ppid:32557,id:0
······
程序运行成功后,父进程和子进程执行各自的代码,但是源文件的代码在内存中只有一份,数据也是只有一份。
如果子进程在执行的过程中修改某一个变量,实际上并不是修改变量本来的内存空间,而是在修改之前,完成一个“写时拷贝”的操作,拷贝一份新的空间用来给子进程操作,父进程也是如此。
3.fork的返回值
- 为什么fork返回时,给父进程返回子进程的pid,给子进程返回0?
如果作为子进程,只需要调用getppid即可获取到父进程,因为父进程是唯一的;如果作为父进程,是没有办法直接获取到子进程的pid的,因为子进程不唯一,可以有多个。
- fork给两个进程返回了不同的值,众所周知,函数不可能返回两个值,因此,可以判断,fork函数返回了两次,那么fork是如何返回两次的?
我们假设这是fork函数的函数体。
在return语句之前,fork处理创建子进程相关的操作,且执行return这一行代码之前,子进程就已经被创建出来并活跃在内存中,return语句的本质就写入到不同的进程,可以看作是特殊的返回,执行两次return分别返回到不同的进程中。
- 源代码中的id是一个变量,为什么一个变量可以有两个值?
操作系统在设计进程的时候,就保证了进程之间是彼此独立的,互相不影响。且利用了虚拟内存等其他技术,使得在Linux下,用一个变量表示了不同的内存空间。
编写一次创建多个进程的源程序,代码如下。
const int num = 10;
void worker()
{
int cnt = 10;
while(cnt)
{
printf("child %d is running,cnt:%d\n",getpid(),cnt);
--cnt;
sleep(1);
}
}
int main()
{
for(int i =0;i < num; ++i)
{
pid_t id = fork();
if(id<0)
{
break;
}
if(id == 0)
{
worker();
exit(0);
}
printf("父进程成功创建子进程,id:%d\n",id);
sleep(1);
}
sleep(10);
return 0;
}
三、进程状态
1.进程排队
- 为什么存在进程排队这样的现象?
进程出现了排队,一定是在等待某个资源,毕竟硬件资源有限。
- 进程不是一直在运行的。进程在CPU上,也不是一直在运行的。
比如,当程序执行到scanf这一行的时候,程序在等待键盘的输入,即在等待硬件资源(也可能是软件资源),因此进程不是一直在运行的。
在CPU上,由于时间片的设计,CPU也不是一直在运行同一个进程。
- 进程=PCB + 可执行程序,所谓的进程排队是可执行程序在排队还是PCB在排队?
排队一定是利用了队列这种数据结构,那么单位类型是可执行程序的话消耗将会巨大无比,因此涉及排队,都是利用队列对PCB作排队。
- 不难发现,一个task_struct,既要利用链表这种数据结构,又要利用队列这种数据结构,那么底层是怎么实现的呢,是存储两份数据吗?
并不是下面这样的实现。
而是在task_struct内部封装链表结点或者队列结点,因此一个task_struct可以被链入到多个数据结构中。
那么,在已知listnode地址的情况下,如何操作task_struct其他数据呢,这就和偏移量有关。
2.进程状态
进程的三种状态:运行,阻塞,挂起。
- 状态是什么
本质上,就是task_struct中的一个整型变量。
struct task_struct
{
int status;
····
}
用整型变量表示不同的状态就类似define定义。
#define new 1
#define ready 2
#define running 3
#define block 4
·····
- 设计状态这一个属性的意义。
通过进程的状态来决定下一步进程要做的动作。
运行状态
一个CPU对应一个运行队列。当进程在运行队列中排队时,即为运行状态。