目录
启动进程
查看进程
方法1:/proc
方法2:查看脚本
方法3:系统调用获取进程标示符❗❗
终止进程
创建进程(主fork)
🙂查看父子进程的pid
🙂进程创建/执行/终止
🙂多次重新启动进程查看pid和ppid
🙂fork创建子进程
🙂fork的返回值
🙂why❓fork有两个返回值
进程的当前工作路径
从本篇开始陆续介绍Linux中进程的task_struct内部的属性。
启动进程
- ./ 本质上就是让系统创建进程并运行。
- myprocess.c是C语言源代码
- myprocess是可执行文件(gcc/g++)
- 可执行的程序的可执行文件在磁盘上
- ./运行程序:把可执行程序加载到内存上,OS创建task_struct,进入排队,等待调度。
- 启动程序:就是让系统创建进程并运行。
- 我们自己写的代码形成的可执行程序 == 系统指令(都是可执行程序) == 可执行文件。
- 在Linux中运行的大部分指令操作,本质都是运行程序。
- 在Linux中启动进程一般make(shell命令行)
- 在windows启动进程双击(图形化界面)
【Makefile】
1 myprocess:myprocess.c
2 gcc -o $@ $^ -g
3 .PHONY:clean
4 clean:
5 rm -f myprocess
【myprocess.c】
1 #include<stdio.h>
2 #include<unistd.h>//sleep函数包含的头文件
3 int main()
4 {
5 //让这个进程持续运行不停止
6 while(1)
7 {
8 printf("I am a process!\n");
9 sleep(1);
10 }
11 return 0;
12 }
./myprocess
I am a process!
I am a process!
I am a process!
I am a process!
//之后myprocess已经是一个进程
【在/usr/bin/find目录下查看的可执行程序】
file /usr/bin/find
查看进程
怎么查看进程呢?父进程,子进程?
- 进程的信息可以通过 /proc 系统文件夹查看
- 系统调用获取进程的标识符
- 查看脚本
方法1:/proc
如:要获取PID为1的进程信息,你需要查看 /proc/1 这个文件夹。
/pro就是实时的把内存的进程信息挂接到文件当中,以文件的方式可以查看进程信息。
/pro就是/process
每启动一个进程,在/pro目录下,产生一个以该进程pid命名的文件夹(目录),该目录保存进程的属性信息,链接信息等等。
- 查看 ls /proc/1 -l
- exe 当前进程所对应的可执行程序,可执行程序.exe
- cwd 当前进程所处的工作路径(current work dir)
- 进程的PCB中会记录自己对应的可执行程序的路径
如果把可执行程序删除,为什么进程还可以运行❓
- 因为删除是磁盘上的可执行程序,内存还有一份(如果可执行程序过大也会出问题)
怎样理解进程的当前工作路径(文件操作)❓>>>下面讲
ls /proc/1
ls /proc/
方法2:查看脚本
大多数进程信息同样可以使用top和ps这些用户级工具来获取。
- ps查看当前系统的进程
- axj显示进程的详细信息(可以打乱顺序写)
- ps axj是用户级的工具,可以行输出消息
- grep也是一个可执行程序
- grep可以把数据按照 行过滤的方式输出
ps axj | grep myprocess :grep把得到的进程信息数据,按照行过滤的方式,凡是包含myprocess的关键字全部提取出来。
- head n查看前n行的信息
ps axj | head -1 查看进程信息的属性信息
- grep -v 关键字 就是除了显示除了关键字以外的其他行信息
- -v反向过滤
- 不需要查看grep的进程信息,举例很混乱。
ps axj | grep -v grep
- && 一起查看,两个命令同时进程
- | 管道
- 查阅出来的信息也可能含有grep的进程信息,因为grep也是一个进程。grep这个进程本省也携带了过滤myprocess的这个关键字。
- 周期性循环查看进程脚本
- 清晰的看到进程的创建,执行,中止,状态这一整个连贯操作。
while :; do ps axj | head -1 && ps axj | grep -v grep | grep process; sleep 1; done
ps axj | grep myprocess
ps axj | head -1
ps axj | head -1 && ps axj | grep 关键字
ps axj | head -1 && ps axj | grep -v 关键字 | grep 关键字
ps axj | head -1 && ps axj | grep -v grep | grep process
//循环查看脚本
while :; do ps axj | head -1 && ps axj | grep -v grep | grep process; sleep 1; done
方法3:系统调用获取进程标示符❗❗
- 进程id(PID)>>>>>>获得子进程的id系统调用函数getpid()
- 父进程id(PPID)>>>>>>获得子进程的id系统调用函数getppid()
- 进程的pid:每一个进程都要有自己的唯一标识符,叫做进程pid
- 进程的ppid(子进程的父进程的pid)
- pid:process id 表示进程的唯一标识符
- ppid:process parent id
- pid和ppid同理
- pid的类型是unsigned int,无符号整数被操作系统封装成pid_t的类型
- pid是一个无符号整型的变量(unsigned int)
- 每一个进程都有task_struct,每个进程间的task_struct怎么区分,用pid❗❗
- Linux中PCB就是task_struct,进程 == 任务task
- 用户想要知道一个进程的pid,是不能够直接到操作系统的内核数据结构中去查询的PCB的pid,需要使用操作系统提供的"系统调用接口"去查询
getpid() getppid()
- 使用man指令去查询getpid 和getppid:man pid
- ❓若查询不到,请安装man命令的一个man-pages安装包
- 安装查看:yum install man-pages(安装的时候只能用超级管理员root安装)
- 系统调用函数getpid查看子进程的pid
- 用getppid查看子进程的ppid == 父进程pid
- #include <sys/types.h>
- #include <unistd.h>
理解:当代码预处理,编译,汇编,链接形成可执行程序的文件的时候是在磁盘中存放,这个时候还不是进程,也没有运行一行代码。只有可执行程序加载到内存,OS创建进程启动进程调度进程的时候。进程启动起来,系统调用函数才会获得PCB的pid。操作系统OS没有PCB,操作系统内核有定期的任务。比如刷新,内存管理之类这些有PCB,操作系统OS本身不需要PCB。
yum install man-pages //记得指令提权
man pid //ppid
man 2 pid //ppid
【用系统调用函数查看进程的pid】
1 #include<stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 pid_t id=getpid();
8 //让这个进程持续运行不停止
9 while(1)
10 {
11 printf("I am a process!,pid=%d\n",id);
12 sleep(1);
13 }
14 return 0;
15 }
终止进程
- ctrl+c就是在用户层面终止进程
- kill -9 pid 可以用来直接杀掉进程
创建进程(主fork)
进程创建的代码方式
(这里讲解重点在操作,轻微原理。后面会重谈进程的创建以及其他方法)
- pid_t getpid(void);获得当前进程的pid
- pid_t getppid(void);获得当前进程的父进程的pid
- 父进程VS子进程
- 运行 man fork 认识fork
- fork有两个返回值
- 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
- 创建进程
- bash是父进程,进程创建进程
- 系统调用函数fork创建进程
🙂查看父子进程的pid
1 #include<stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 pid_t id=getpid();
8 pid_t parentid=getppid();
9 //让这个进程持续运行不停止
10 while(1)
11 {
12 printf("I am a process!,pid=%d,ppid:%d\n",id,parentid);
13 sleep(1);
14 }
15 return 0;
16 }
🙂进程创建/执行/终止
while :; do ps axj | head -1 && ps axj | grep -v grep | grep process; sleep 1; done
🙂多次重新启动进程查看pid和ppid
❓我们发现每次重新启动进程,进程的pid都要改变,但是进程的父进程pid都不改变。
❓查询父子进程属性信息:发现都有bash进程
- 进程每次启动,对应的pid都不一样是正常的
- 经过验证,我们创建的进程的父进程就是bash
- bash是父进程,是实习生,是李婆,命令行解释器。
- ❗所以进程可以创建进程。
ps axj | head -1 && ps axj | grep -v grep | grep 13185//查询父进程
ps axj | head -1 && ps axj | grep -v grep | grep 12345 //查询子进程随机一个
🙂fork创建子进程
- 创建一个进程,OS内多了进程,多了进程的PCB,内存多了进程的代码和数据。
- 用户想要创建进程,就要创建内核数据结构,用户是没有权力对操作系统内部的内核数据结构等做增删查改的。
- 所以,用户只能使用OS提供的系统调用函数来创建子进程。
fork:分支,叉子。创建子进程。
- man fork查询
- 头文件: #include <unistd.h>
- 返回值有两个类型都是pid_t
- 可以接收返回值,也可以不接收。
fork创建子进程的脑中图,理解记忆❗
- 在fork创建了子进程,是由父子进程分别都要执行后序代码,它们共享一份代码。
- 创建一个进程,本质是系统中多一个进程,那么内存也会多一份代码和数据。
- 父进程的代码和数据是从磁盘中加载过来的
- 父进程的PBC也是OS动态创建的
子进程的代码和数据,PBC呢?
- 子进程的代码和数据也可以从磁盘再加载一份到内存(下个博文进程控制会讲)
- 在fork这里默认子进程的代码和数据是继承父进程的
- 数据也是继承的吗❓
- PCB是OS动态创建的
fork()之后,父子进程代码共享,但父子是并列关系。
- 理论上,子进程是在fork之后共享了父进程的代码和数据,OS才动态创建了子进程的PCB。
- 父进程和子进程是并列的关系,它们是相互独立的,kill了父进程并不会影响子进程。(子进程会到后台)
- 子进程有自己独立的PCB,在内核数据结构上和父进程的PCB是并列关系
- 逻辑上是父子(仅仅停留在指针指向上面)
子进程的数据也是继承父进程的吗&父子写时拷贝
- 理论上,子进程只能用父进程的数据
- 但是,OS必须都做到保持进程的独立性,进程具有独立性!
- 一个进程不能影响另外一个进程
- 进程的数据:全局变量(静态区)
- 进程的代码:只读(字符串常量)的部分
- 所以子进程只会继承父进程的代码部分,而数据部分是拷贝。(因为数据需要独立分开,从而保证进程的独立性)
1 #include<stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 printf("process is running,only me!\n");//只有父进程
8 sleep(3);
9 fork();//创建子进程
10 printf("hello linux\n");
11 sleep(5);
20 return 0;
21 }
1 #include<stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 pid_t id=getpid();
8 pid_t parentid=getppid();
9 printf("process is running,id=%d,parentid=%d\n",id,parentid);//只有父进程
10 sleep(3);
11 fork();//创建子进程
12 printf("hello linux,id=%d,parentid=%d\n",id,parentid);
13 sleep(5);
22 return 0;
23 }
~
🙂fork的返回值
- 我们为什么要创建子进程呢?
当然是为了两个进程执行不一样的代码, 想让子进程和父进程运行不一样的代码。
- 怎样确保两个进程执行不一样的代码呢?
fork有两个返回值。可以提供给fork之后的两个不一样的逻辑。
- fork创建子进程成功,将子进程的pid返回给父进程,将0返回给子进程。
- fork创建子进程失败,则将-1返回给父进程,没有子进程。
❓C/C++语法层面上,if / else if /else 只能执行一个逻辑,而不可能同时执行两个逻辑。(可以改成全是if)
回答:因为历史学习,只有一个执行指令,是单线程。这里是存在多线程问题,后面谈谈。
1 #include<stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 pid_t id=getpid();
8 pid_t parentid=getppid();
9 printf("process is running,id=%d,parentid=%d\n",id,parentid);//只有父进程
10 sleep(3);
11 pid_t ret= fork();//创建子进程
12 if(ret == -1)
13 {
14 return 1;
15 }
16 else if(ret == 0)
17 {
18 printf("I am child process!,pid=%d,ppid:%d\n",id,parentid);
19 sleep(2);
20 }
21 else//ret>0
22 {
23 printf("I am parent process!,pid=%d,ppid:%d\n",id,parentid);
24 sleep(2);
25 }
return 0;
}
🙂why❓fork有两个返回值
在本篇我们先简述以下原因。这里重点掌握fork的应用即可。后序在学习了【地址空间】我们就可以深入的理解fork为什么有两个返回值了 。
❓ret的值可以是不同的值,同一个id,怎么可能既是 == 0又是 >0的呢
- 虚拟地址空间,父子写时拷贝,TODP后面讲述。
- id本身是一个变量,上面讲到父子进程的数据是独立的,所以父进程返回的数据是一个,子进程返回的数据是另外一个,所以id可以有两个值,只不过接收值的变量是同一个而已。
❓fork会有两个返回值,返回两次
- fork内部创建子进程的代码的时候,在return 返回pid之前就已经创建了子进程,且子进程已经被调度运行了。
- 所以准确来说,从fork内部从子进程创建开始fork内部到外的后面所有的代码均被子进程共享了(包括return)
- fork()是函数,是由OS提供的
- 当fork函数内部执行到return的时候,函数的核心工作已经完成了
- 所以,return 2次!
进程的当前工作路径
在我们以前C语言实现文件操作的时候,我们用写的方式打开一个文件,OS默认就是在当前路径下创建了这个文件。
- 在这个场景当中新建文件在当前路径/当前目录下 == 这个进程的当前工作目录
- 创建写入数据到log.txt,OS执行这个代码就会创建进程,创建到当前路径其实就是进程的当前工作路径。
- 更改进程的当前工作目录:chdir
- 当前工作路径不是一个C语言概念的问题,而是一个操作系统的问题OS的问题。
fopen("log.txt","w");
#include<stdio.h>
int main()
{
FILE *fp = fopen("log.txt","w");
(void)fp;//ingor warning
return 0;
}
❓能否改变进程的当前工作目录
- chdir 更改进程工作的目录
- man chdir
- 开启文件>>>>记录进程的启动路径>>默认创建文件会拼接路径>>拼接进程的当前工作目录>>>创建文件
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
chdir("/home/tangsiqi/usr"); //更改路径
FILE *fp = fopen("log.txt","w");
(void)fp;//ingor warning
fclose(fp);
return 0;
}