一、进程的概念与理解
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 31222
PPID 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 -bash
7482 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是不相同的。