目录
一. PCB 的概念
1. 为什么需要PCB
2. PCB的属性
二. task struct
1. task struct 介绍
2. 查看进程指令
3. PID
4. PPID
父进程是什么?
为什么要有父进程?
5. fork 创建子进程
1) fork 后的现象
为什么会打印两次?
2) 的返回值
getpid petppid
3) fork 如何办到?
三. 总结
在我们的操作系统中,如果我们想要运行一个程序,那么我们显然是需要将程序加载到内存中的,所以我们可以先不怎么准确的理解一下进程是什么——加载到内存中的程序。
更为官方的概念是这样的,进程 PCB(Process Control Block,进程控制块)是操作系统中用于描述和管理进程的数据结构。每个正在运行的进程都有一个对应的进程 PCB。
那么我们首先谈论一下为什么需要进程
我们前面说了,我们的进程就是一个程序加载到内存,那么我们的系统中显然是不可能只有一个进程的,因为我们需要运行很多软件(进程),所以我们是需要对这么多的进程需要进行管理的,而我们的操作系统如何管理? 先描述再组织。
那么我们如何描述一个进程呢?通过用数据结构来描述进程的特定的属性来描述进程,所以我们用来描述进程的数据结构就叫做PCB,而我们的操作系统也就是通过管理PCB来管理我们的进程。
但是我们上面一直是在说PCB,那么PCB就等于进程吗? 这里说一下,其实PCB并不等于进程,我们说了PCB只是系统给我们创建的一个用来描述进程的控制块,但是我们不仅仅需要用来描述,我们还是需要我们自己程序的代码和数据,所以我们的进程应该是: 进程 = PCB + 代码和数据
根据上面说的,我们的PCB就是用来描述我们的进程的一个控制块,而PCB的作用就是方便操作系统对我们的进程进行管理。
我们现在知道,PCB就是系统用来描述进程的一个数据结构,那么既然是一个数据结构里面当然是有各种关于进程的属性的,我们下面看一下关于进程的各种属性。
这上面只是笼统的介绍一下,后面会详细说的~
前面说的是大多数操作系统的一个概念,但是如果我们想要学习操作系统,那么我们必须要学习的是一款具体的操作系统,我们这里说的就是 linux
我们这里说的 task struct 就是 PCB ,而PCB是所有操作系统里面进程的一个叫法,而我们的
task struct 是一款具体的操作系统(linux)里面的PCB的一个叫法。
task struct 里面的属性和PCB里面的都是差不多的,但是肯定是有差别的。
下面我们就先简单的看一下 linux 里面的进程
指令:ls /proc(查看所有进程)
指令:ps axj (查看所有进程)
我们现在写一个死循环,然后我们执行该程序,在执行的时候我们的该程序会被加载到内存,然后被执行,我们使用命令查看该进程
测试代码:
#include
#include
int main()
{
while(1)
{
printf("I am a process...\n");
sleep(2);
}
return 0;
}
其实这样看的管感并不太好,我们还可以使用 grep 命令来查看
[lxy@hecs-165234 linux2]$ ps axj | head -1 && ps axj | grep myprocess
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
18563 18611 18611 18563 pts/1 18611 S+ 1000 0:00 ./myprocess
18169 18859 18858 18169 pts/0 18858 D+ 1000 0:00 grep --color=auto myprocess
前面的那个是为了查看第一行
我们看到,我们查到的内容里面有几个属性,我们这里先值说一个,PID(进程的标识符),也就是在系统中表示唯一的一个进程
既然我们现在知道了PID,那么我们看到 ls /proc查看到的里面的那些数字,其实就是PID
我们可以通过 ls /proc 查看一下我们的 myproc 进程,然后我们关掉我们的程序后在查看一下
[lxy@hecs-165234 linux2]$ ls /proc | grep 18611
18611
[lxy@hecs-165234 linux2]$ ls /proc | grep 18611
[lxy@hecs-165234 linux2]$
我们看到我们第一次查到了,然后关掉程序后就查看不到了
我们继续启动程序,然后我们查看该程序
[lxy@hecs-165234 linux2]$ ps axj | grep myprocess
18563 18940 18940 18563 pts/1 18940 S+ 1000 0:00 ./myprocess
18169 18942 18941 18169 pts/0 18941 R+ 1000 0:00 grep --color=auto myprocess
[lxy@hecs-165234 linux2]$ ls /proc/18940 -dl
dr-xr-xr-x 9 lxy lxy 0 Jul 12 19:04 /proc/18940
[lxy@hecs-165234 linux2]$
我们看到我们的 18940 是一个文件,那么我们打开看一下里面有一些什么内容
lxy@hecs-165234 linux2]$ cd /proc/18940
[lxy@hecs-165234 18940]$ ll
total 0
dr-xr-xr-x 2 lxy lxy 0 Jul 12 19:06 attr
-rw-r--r-- 1 lxy lxy 0 Jul 12 19:06 autogroup
-r-------- 1 lxy lxy 0 Jul 12 19:06 auxv
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 cgroup
--w------- 1 lxy lxy 0 Jul 12 19:06 clear_refs
-r--r--r-- 1 lxy lxy 0 Jul 12 19:04 cmdline
-rw-r--r-- 1 lxy lxy 0 Jul 12 19:06 comm
-rw-r--r-- 1 lxy lxy 0 Jul 12 19:06 coredump_filter
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 cpuset
lrwxrwxrwx 1 lxy lxy 0 Jul 12 19:06 cwd -> /home/lxy/108/linux2
-r-------- 1 lxy lxy 0 Jul 12 19:06 environ
lrwxrwxrwx 1 lxy lxy 0 Jul 12 19:06 exe -> /home/lxy/108/linux2/myprocess
dr-x------ 2 lxy lxy 0 Jul 12 19:04 fd
dr-x------ 2 lxy lxy 0 Jul 12 19:06 fdinfo
-rw-r--r-- 1 lxy lxy 0 Jul 12 19:06 gid_map
-r-------- 1 lxy lxy 0 Jul 12 19:06 io
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 limits
-rw-r--r-- 1 lxy lxy 0 Jul 12 19:06 loginuid
dr-x------ 2 lxy lxy 0 Jul 12 19:06 map_files
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 maps
-rw------- 1 lxy lxy 0 Jul 12 19:06 mem
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 mountinfo
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 mounts
-r-------- 1 lxy lxy 0 Jul 12 19:06 mountstats
dr-xr-xr-x 5 lxy lxy 0 Jul 12 19:06 net
dr-x--x--x 2 lxy lxy 0 Jul 12 19:06 ns
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 numa_maps
-rw-r--r-- 1 lxy lxy 0 Jul 12 19:06 oom_adj
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 oom_score
-rw-r--r-- 1 lxy lxy 0 Jul 12 19:06 oom_score_adj
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 pagemap
-r-------- 1 lxy lxy 0 Jul 12 19:06 patch_state
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 personality
-rw-r--r-- 1 lxy lxy 0 Jul 12 19:06 projid_map
lrwxrwxrwx 1 lxy lxy 0 Jul 12 19:06 root -> /
-rw-r--r-- 1 lxy lxy 0 Jul 12 19:06 sched
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 schedstat
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 sessionid
-rw-r--r-- 1 lxy lxy 0 Jul 12 19:06 setgroups
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 smaps
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 stack
-r--r--r-- 1 lxy lxy 0 Jul 12 19:04 stat
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 statm
-r--r--r-- 1 lxy lxy 0 Jul 12 19:04 status
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 syscall
dr-xr-xr-x 3 lxy lxy 0 Jul 12 19:06 task
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 timers
-rw-r--r-- 1 lxy lxy 0 Jul 12 19:06 uid_map
-r--r--r-- 1 lxy lxy 0 Jul 12 19:06 wchan
这里面我们介绍两个字段
在操作系统中,每一个进程都是由自己独立的编号的,而在PCB中进程的编号就是 PID, PID 可以表示某一个操作系统中的特定的一个进程。
现在我们自己编写一个程序,我们的程序之打印 hello world,下面查看该进程的 PID
#include
#include
int main()
{
while(1)
{
printf("hello world\n");
sleep(1);
}
return 0;
}
查看进程的前面已经说过了,下面直接查看一下。
[lxy@hecs-165234 linux3]$ ps axj | head -1 && ps axj | grep proc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
6468 6552 6552 6468 pts/1 6552 S+ 1000 0:00 ./proc
这里使用 head -1 来显示第一行的信息,第一行可以看到 PID 而该进程的 PID 就是 6552
这里先介绍 PPID 是什么,PPID 就是 该进程的父进程。
父进程就是创建该进程的进程,就叫做父进程,而在 linux 中,进程的创建时有两种方法的:
所以系统中的进程都是由别的进程创建的,或者是自己手动创建的,而当用户 ./ 执行一个程序的时候其实也是由父进程的,而这个父进程就是 bash 这个之前说过,bash 在 这里就是命令行解释器,当 bash 察觉到我们是要启动一个进程的时候,就是 bash 帮助用户创建进程,而这里的进程的父进程也就是 bash。
下面查看一下刚才的 6468 的进程,这里可以看到就是 bash
[lxy@hecs-165234 linux3]$ ps axj | head -1 && ps axj | grep 6468
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
6467 6468 6468 6468 pts/1 6468 Ss+ 1000 0:00 -bash
这个我们在下一期说。
前面说了, fork 可以创建子进程,那么怎么创建呢?
fork 是一个系统接口,所以如果当我们不明白的时候我们可以使用 man 进程查看该函数的信息。
[lxy@hecs-165234 linux3]$ man 2 fork
NAME
fork - create a child process
SYNOPSIS
#include
pid_t fork(void);
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,如果失败的话,就返回 -1.
下面看一下使用。
#include
#include
int main()
{
printf("begin....\n");
fork();
printf("end....\n");
sleep(1);
return 0;
}
运行....
[lxy@hecs-165234 linux3]$ ./proc
begin....
end....
end....
这里是运行后的结果,看到 end 打印的两次。
第一次解释:
因为 fork 后就会创建一个进程,然后这时候就会有两个执行流执行,所以说 end 被打印了两次
这里继续编写一段代码。
#include
#include
#include
int main()
{
pid_t id = fork();
if(id == 0)
{
// 子进程
while(1)
{
printf("I am a child... \n");
sleep(1);
}
}
else if(id > 0)
{
// 父进程
while(1)
{
printf("I am a father... \n");
sleep(1);
}
}
else{
//创建失败
}
return 0;
}
然后我们运行看一下结果。
[lxy@hecs-165234 linux3]$ ./proc
I am a father...
I am a child...
I am a father...
I am a child...
I am a father...
I am a child...
结果就是我们该程序不仅在打印 father 还在打印 child ,我们该进程不仅进了 if 还进了 else if,所以为什么会有这个现象呢?
下面在看一下查看到的进程。
[lxy@hecs-165234 linux3]$ ps axj | head -1 && ps axj | grep proc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
6468 7387 7387 6468 pts/1 7387 S+ 1000 0:00 ./proc
7387 7388 7387 6468 pts/1 7387 S+ 1000 0:00 ./proc
这里看到确实是这样,第一个是父进程,第二个是子进程,子进程的 PPID 就是父进程的 PID
在下面开始之前先介绍两个函数。
NAME
getpid, getppid - get process identification
SYNOPSIS
#include
#include
pid_t getpid(void);
pid_t getppid(void);
DESCRIPTION
getpid() returns the process ID of the calling process. (This is often used
by routines that generate unique temporary filenames.)
这就是两个函数的介绍,我这里在简单介绍一下,这两个函数一个是返回PID 另一个是返回 PPID 其中,谁调用该函数就返回谁的 PID 和 PPID。
这里为什么会有上面的结果:
概念铺设:进程之间是互相独立的。进程是有自己的代码和数据的,所以进程之间是互不干扰的。