一、进程的概念与理解
1.1 概念
进程是程序的一个执行实例,即正在执行的程序。
1.2 理解
我们编写代码运行后会在磁盘中会形成一个可执行程序,当我们运行这个可执行程序时,这个程序此时就会被操作系统的调度器加载到内存中;操作系统会对进程进行管理(进程的加载、调度、切换、释放……),而我们可能同时启动多个进程(一面听歌一面看PPT),那么操作系统是如何管理进程的呢?
很简单,先描述,后组织,即我们的操作系统对进程的属性进行管理,创建了内核级的数据结构PCB(Linux中的PCB是task_struct)来管理进程。因此实际上进程=内核数据结构(task_struct)+代码和数据。接下来这些task_struct彼此相连构成了struct task_struct* list,而后操作系统就可以对多个进程基于时间片进行轮转调度、切换等等。在这一过程中呈现了动态的特征,因此我们有了如上的结论:进程是运行起来的程序。
1.3 源码中对task_struct的描述
下面是来自Linux0.11版本的源码中对task_struct的描述:
struct task_struct {
/* these are hardcoded - don't touch */long state; /* -1 unrunnable, 0 runnable, >0 stopped */long counter;long priority;long signal;struct sigaction sigaction[32];long blocked; /* bitmap of masked signals */
/* various fields */int exit_code;unsigned long start_code,end_code,end_data,brk,start_stack;long pid,father,pgrp,session,leader;unsigned short uid,euid,suid;unsigned short gid,egid,sgid;long alarm;long utime,stime,cutime,cstime,start_time;unsigned short used_math;
/* file system info */int tty; /* -1 if no tty, so it must be signed */unsigned short umask;struct m_inode * pwd;struct m_inode * root;struct m_inode * executable;unsigned long close_on_exec;struct file * filp[NR_OPEN];
/* ldt for this task 0 - zero 1 - cs 2 - ds&ss */struct desc_struct ldt[3];
/* tss for this task */struct tss_struct tss;
};
二、进程的基本操作
查看进程信息的指令:ps axj
2.1 查看PID
PID是用以区分进程唯一性的编号,我们可以使用系统接口getpid
来获取当前进程的pid。
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{ while(1) { printf("I am a process,My Pid is %d\n",getpid()); sleep(1); } return 0;
}
终止该进程的方式:1. Crtl+C
可以结束进程 2.发送信号 kill -9 5150
也可以杀死该进程
仅仅通过ps axj显示的进程信息是有些少的,进程的信息被保存在/proc中,以自己的PID为文件名,因此我们也可以通过查看/proc/PID来查看进程的信息
[caryon@VM-24-10-centos ~]$ ll /proc/9139
total 0
dr-xr-xr-x 2 caryon caryon 0 Oct 9 15:47 attr
-rw-r--r-- 1 caryon caryon 0 Oct 9 15:47 autogroup
-r-------- 1 caryon caryon 0 Oct 9 15:47 auxv
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 cgroup
--w------- 1 caryon caryon 0 Oct 9 15:47 clear_refs
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 cmdline
-rw-r--r-- 1 caryon caryon 0 Oct 9 15:47 comm
-rw-r--r-- 1 caryon caryon 0 Oct 9 15:47 coredump_filter
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 cpuset
lrwxrwxrwx 1 caryon caryon 0 Oct 9 15:47 cwd -> /home/caryon/linux/lesson11
-r-------- 1 caryon caryon 0 Oct 9 15:47 environ
lrwxrwxrwx 1 caryon caryon 0 Oct 9 15:47 exe -> /home/caryon/linux/lesson11/code
dr-x------ 2 caryon caryon 0 Oct 9 15:47 fd
dr-x------ 2 caryon caryon 0 Oct 9 15:47 fdinfo
-rw-r--r-- 1 caryon caryon 0 Oct 9 15:47 gid_map
-r-------- 1 caryon caryon 0 Oct 9 15:47 io
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 limits
-rw-r--r-- 1 caryon caryon 0 Oct 9 15:47 loginuid
dr-x------ 2 caryon caryon 0 Oct 9 15:47 map_files
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 maps
-rw------- 1 caryon caryon 0 Oct 9 15:47 mem
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 mountinfo
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 mounts
-r-------- 1 caryon caryon 0 Oct 9 15:47 mountstats
dr-xr-xr-x 5 caryon caryon 0 Oct 9 15:47 net
dr-x--x--x 2 caryon caryon 0 Oct 9 15:47 ns
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 numa_maps
-rw-r--r-- 1 caryon caryon 0 Oct 9 15:47 oom_adj
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 oom_score
-rw-r--r-- 1 caryon caryon 0 Oct 9 15:47 oom_score_adj
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 pagemap
-r-------- 1 caryon caryon 0 Oct 9 15:47 patch_state
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 personality
-rw-r--r-- 1 caryon caryon 0 Oct 9 15:47 projid_map
lrwxrwxrwx 1 caryon caryon 0 Oct 9 15:47 root -> /
-rw-r--r-- 1 caryon caryon 0 Oct 9 15:47 sched
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 schedstat
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 sessionid
-rw-r--r-- 1 caryon caryon 0 Oct 9 15:47 setgroups
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 smaps
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 stack
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 stat
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 statm
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 status
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 syscall
dr-xr-xr-x 3 caryon caryon 0 Oct 9 15:47 task
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 timers
-rw-r--r-- 1 caryon caryon 0 Oct 9 15:47 uid_map
-r--r--r-- 1 caryon caryon 0 Oct 9 15:47 wchan
认识cwd和exe
• exe
我们可以尝试一下将进程启动然后删掉可执行程序,我们发现进程依然是可以运行的,但是一旦退出了进程就不能再运行了,但是进程结束后我们发现exe报红了,这就是由于exe存储了我们的形成该进程的原文件。• cwd
我们之前总是听到这么一句话“当我们打开一个不存在的文件时会在当前目录下创建这个文件”,为什么会在当前目录下新建这个文件?又是怎么新建的呢?
当一个进程在实际启动的时候,该进程会用cwd记录当前工作目录,新建文件就是把cwd拿过来在后面加上/文件名,这也就意味着我们如果能改变进程启动的cwd即可对新建文件的创建位置进行控制,而系统给我们提供了这样的接口chdir()#include<stdio.h> #include<unistd.h> int main() {chdir("/home/caryon");FILE* fp=fopen("log.txt","w");if(fp==NULL){}return 0; }
/proc是系统为我们提供的一个访问进程信息的接口,因此实际上的ps就是对/proc进行相关的文本分析,而/proc也并不是磁盘级文件,因此频繁的创建于删除并不影响效率。
2.2 查看PPID
在Linux中,OS启动之后,新建的任何进程都是由自己的父进程创建的,我们可以使用系统调用接口getppid()获取当前进程的ppid。
#include<stdio.h>
#include<unistd.h>
int main()
{ while(1) { printf("PID:%d,PPID:%d\n",getpid(),getppid()); sleep(1); } return 0;
}
我们反复执行了几次代码,发现该进程的PPID居然没有变,它的父进程究竟是谁啊?
[caryon@VM-24-10-centos ~]$ ps ajx|head -1;ps ajx|grep 31222PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
31222 28570 28570 31222 pts/0 28570 S+ 1001 0:00 ./code
31220 31222 31222 31222 pts/0 28570 Ss 1001 0:00 -bash7482 31749 31748 7482 pts/1 31748 S+ 1001 0:00 grep --color=auto 31222
原来是bash(Linux下的命令行解释器)。命令行中,执行命令/程序本质上就是bash创建子进程,由子进程执行我们的代码。
• 创建子进程
系统接口提供了fork()来创建子进程,如果创建成功则给父进程返回子进程的PID,给子进程返回0,失败则给父进程返回-1,子进程创建失败。#include<stdio.h> #include<unistd.h>int gal=0;int main() {printf("我的PID是%d,我的PPID是%d\n",getpid(),getppid());pid_t id=fork();if(id==0){while(1){printf("我是子进程,我的PID是%d,我的PPID是%d,gal=%d\n",getpid(),getppid(),gal);sleep(1);gal++;}}else{while(1){printf("我是父进程,我的PID是%d,我的PPID是%d,gal=%d\n",getpid(),getppid(),gal);sleep(1);}}return 0; }
[caryon@VM-24-10-centos lesson11]$ ./code 我的PID是13111,我的PPID是31222 我是父进程,我的PID是13111,我的PPID是31222,gal=0 我是子进程,我的PID是13112,我的PPID是13111,gal=0 我是父进程,我的PID是13111,我的PPID是31222,gal=0 我是子进程,我的PID是13112,我的PPID是13111,gal=1 我是父进程,我的PID是13111,我的PPID是31222,gal=0 我是子进程,我的PID是13112,我的PPID是13111,gal=2 我是父进程,我的PID是13111,我的PPID是31222,gal=0 我是子进程,我的PID是13112,我的PPID是13111,gal=3 我是父进程,我的PID是13111,我的PPID是31222,gal=0 我是子进程,我的PID是13112,我的PPID是13111,gal=4
对于同一个父进程可以创建多个子进程,对于每一个子进程只能有一个父进程,故进程也是树形结构。
• 理解fork函数
fork()函数一旦调用之后,后面就会有两个进程了,这两个进程各自执行各自的代码,只不过fork()给父进程返回的是子进程的PID,给子进程则返回0。对于父子进程,它们的关系是代码共享,数据私有一份。
为什么代码是共享的呢?
我们知道进程=内核数据结构(task_struct)+代码和数据。fork之后产生子进程,子进程的内核数据结构拷贝自父进程,但是没有代码和数据怎么办呢?系统就会让子进程的内核数据结构指向父进程的代码和数据。
为什么数据是私有一份的呢?
上面我们不是说子进程的内核数据结构指向父进程的代码和数据吗?这是因为进程具有很强的独立性,多个进程运行时互不干扰(即使是父子)。代码是只读的,可以共享;但是数据不一定时只读的,当数据被修改时两个进程必须要进行分割,也就是各自要私有一份。
#include<stdio.h> #include<unistd.h>int gal=0; int main() {pid_t id=fork();if(id==0){while(1){printf("I am a subprocess,my pid is %d,my ppid is %d,gal = %d\n",getpid(),getppid(),gal++);sleep(1);}}else{while(1){printf("I am a parentprocess,my pid is %d,my ppid is %d,gal = %d\n",getpid(),getppid(),gal);sleep(1);}}return 0; }
上面的这段代码我们可以看到全局变量gal在两个进程中的数值是不相同的,也就是进程间实现了数据私有一份的。
这时我们也能理解了fork的返回值了。因为id也是变量,返回的本质就是向指定变量进行写入,因此两个进程的id是不相同的。