1.程序和进程
程序:程序(program)是存放在磁盘文件中的可执行文件
进程:程序的执行实例被称为进程(process),进程具有独立的权限和职责。如果系统中某个进程崩溃,它不会影响到其余的进程。每个进程运行在其各自的虚拟地址空间中,进程之间可以通过由内核控制的机制相互通讯。
进程ID:每个linux进程都一定有一个唯一的数字标识符,称为进程ID(process ID),简称PID,进程ID总是一非负整数(从0开始计算)。
name$ ps -ef | more
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Mar10 ? 00:37:15 /usr/lib/syst
emd/systemd --switched-root --system --deserialize 22
root 2 0 0 Mar10 ? 00:00:01 [kthreadd]
root 5 2 0 Mar10 ? 00:01:15 [kworker/u144
:0]
root 6 2 0 Mar10 ? 00:00:53 [ksoftirqd/0]
root 7 2 0 Mar10 ? 00:00:17 [migration/0]
root 8 2 0 Mar10 ? 00:00:00 [rcu_bh]
root 9 2 0 Mar10 ? 00:41:44 [rcu_sched]
--More--
2.内核中的进程结构
task_struct ==>结构体 ==>进程表项,记录了当前进程中的信息。
上图中的files_struct是指文件描述符表,实际上就是一个结构体指针
可以自己在linux系统下查看内核的头文件就能看到task_struct结构体,参考下面的方法
name$cd /usr/src
name$ls
linux-headers-2.6.38-8 linux-headers-2.6.38-8-generic
name$cd linux-headers-2.6.38-8/include/linux
name$vim sched.h
3.C程序启动过程
3.1 内核启动特殊例程
3.2 启动例程
- 在进程的main函数执行之前内核会启动
- 该例程防止在/lib/libc.so.***中(函数动态库)
vname$ ls /lib/libc* /lib/libc-2.17.so /lib/libc.a /lib/libcap.so.2 /lib/libcap.so.2.22 --More--
- 编译器在编译时会将启动例程编译进可执行文件之中
3.3 启动例程的作用
- 搜集命令行的参数传递给main函数中的argc和argv
- 搜集环境信息构建环境表并传递给main函数
- 登记进程的终止函数
4.进程终止方式
4.1 正常终止
- 从main函数返回(return 0)
- 调用exit(标准c库函数)(exit(0))
- 调用_exit或_Exit(系统调用,由内核提供)
- 最后一个线程从启动例程返回(一个进程只有一个主线程)
- 最后一个线程调用pthread_exit
4.2 异常终止
- 调用abort
- 接受到一个信号并终止(最常用)
- 最后一个线程对取消请求做处理响应
4.3 进程返回
- 通常程序运行成功返回0,否则返回非0
- 在shell中可以查看进程返回值(echo $?)
4.4 atexit函数
- 每个启动的进程都默认登记了一个标准的终止函数
#include<stdlib.h> int atexit(void (*function)(void)); //返回:若成功则为0,若出错则为-1 //功能:向内核登记终止函数
- 终止函数在进程终止时释放进程所占用的一些资源
- 登记的多个终止函数执行顺序是以栈的方式执行,先登记的后执行。
- 终止函数并非在所有情况下都会被调用(系统调用的_exit不会调用终止函数,也不会刷新缓存·)
name$ cd process/ name$ mkdir bin include obj src name$ ls bin include obj src name$ vim src/process_term.c #include<unistd.h> #include<string.h> #include<stdio.h> #include<stdlib.h> #include<fcntl.h> //define the stop function for process void term_fun1(void) { printf("First term function\n"); } void term_fun2(void) { printf("Second term function\n"); } void term_fun3(void) { printf("Third trem function\n"); } int main(int argc, char *argv[]) { if(argc < 3){ fprintf(stderr, "usage: %s file [exit|_exit|return]\n", argv[0]); exit(1); } //Register termination function with kernel atexit(term_fun1); atexit(term_fun2); atexit(term_fun3); FILE *fp = fopen(argv[1], "W"); fprintf(fp, "hello iotek"); //全缓存 if(!strcmp(argv[2], "exit")){ exit(0); //标准c的库函数 }else if(!strcmp(argv[2], "_exit")){ _exit(0); //系统调用 }else if(!strcmp(argv[2], "return")){ return 0; }else{ fprintf(stderr, "usage: %s file [exit|_exit|return]\n", argv[0]); } exit(0); } name$ gcc -o bin/process_term src/process_term.c name$ bin/process_term iotek.txt return Third term function Second term function First term function name$ more iotek.txt hello iotek name$ rm -rf iotek.txt name$ bin/process_term iotek.txt exit Third term function Second term function First term function name$ more iotek.txt hello iotek name$ rm -rf iotek.txt name$ bin/process_term iotek.txt _exit name$ ls bin include iotek.txt obj src name$ more iotek.txt name$
4.5 进程终止方式区别
5.进程的启动和退出流程
6.进程查看和进程状态
6.1 用ps指令查看进程信息
- 通常可以查看到:进程的ID、进程的用户ID、进程状态和进程的command,例如ps -aux;ps -ef等。
USER:进程的属主;
PID:进程的ID
PPID:进程的父进程
%CPU:进程占用的CPU百分比
%MEM:占用的内存百分比
NI:进程的NICE值,数值DA,表示较少占用CPU时间
VSZ:进程虚拟大小
RSS:驻留中页的数量
TTY:终端ID
STAT:进程的状态
WCHAN:正在等待的进程资源
START:启动进程的时间
TIME:进程消耗CPU的时间
CMMAND:命令的名称和参数
name$ ps
PID TTY TIME CMD
226411 pts/20 00:00:00 ps
831852 pts/20 00:00:00 bash
name$ echo $SHELL # ~/process
/bin/bash
name$ ps -ef | more
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Mar10 ? 00:37:30 /usr/lib/systemd/
systemd --switched-root --system --deserialize 22
root 2 0 0 Mar10 ? 00:00:01 [kthreadd]
root 5 2 0 Mar10 ? 00:01:15 [kworker/u144:0]
root 6 2 0 Mar10 ? 00:00:53 [ksoftirqd/0]
root 7 2 0 Mar10 ? 00:00:17 [migration/0]
root 8 2 0 Mar10 ? 00:00:00 [rcu_bh]
root 9 2 0 Mar10 ? 00:41:59 [rcu_sched]
root 10 2 0 Mar10 ? 00:00:00 [lru-add-drain]
--More--
UID是进程的属主,PID是进程号,PPID是父进程号
TTY:终端ID
STIME:启动进程的时间
TIME:进程消耗CPU的时间
CMD:命令的名称和参数
6.2 进程常见状态
6.2.1 运行状态
- 系统当前进程
- 就绪状态进程
- ps命令的STAT列为值R
6.2.2 等待状态
- 等待事件发生
- 等待系统资源
- ps命令的STAT列为值S(可中断的等待)或D(不可中断的等待),一般为S
6.2.3 停止状态
- ps命令的STAT列为值T
6.2.4 僵尸状态
- 进程终止或结束
- 在进程表项中仍有记录
- ps命令的STAT列为值Z
6.3 进程状态切换
7.进程调度
7.1 进程调度步骤
7.1.1 第一步:处理内核中的工作
7.1.2 第二步:处理当前进程
7.1.3 第三步:选择进程
- 实时进程
- 普通进程
7.1.4 第四步:进程交换
7.2 task_struct中的调度信息:
7.2.1 策略
- 轮流策略
- 先进先出策略
7.2.2 优先权
- Jiffies变量
7.2.3 实时优先权
- 实时进程之间(动态优先级)
7.2.4 计数器
7.3 进程状态变化关系(并发)
7.4进程标识
#include<unistd.h>
#include<sys/types.h>
pid_t getpid(void) //获取当前进程ID
uid_t getuid(void) //获取当前进程的实际用户ID
uid_t geteuid(void) //获得当前进程的有效用户ID(很多情况下实际用户就是有效用户,除非切换用户去启动特定权限的进程)
gid_t getgid(void) //获得当前进程的用户组ID
pid_t getppid(void) //获得当前进程的父进程ID
pid_t getpgrp(void) //获得当前进程所在的进程组ID
pid_t getpgid(pid_t pid) //获得进程ID为pid的进程所在的进程组ID
name$ vim src/process_id.c
#include<unistd.h>
#include<stdio.h>
int main(void)
{
printf("pid: %d\n", getpid());
printf("ppid: %d\n", getppid());
printf("uid: %d\n", getuid());
printf("euid: %d\n", geteuid());
printf("user gid: %d\n", getgid());
printf("gid: %d\n", getpgrp());
printf("pgid: %d\n", getpgid(getpid()));
printf("ppgid: %d\n", getpgid(getppid()));
return 0;
}
name$ gcc -o bin/process_id src/process_id.c
name$ bin/process_id
pid: 665757
ppid: 665019
uid: 7588469
euid: 7588469
user gid: 64000
gid: 665757
pgid: 665757
ppgid: 665019
name$ ps
PID TTY TIME CMD
665019 pts/60 00:00:00 bash
668244 pts/60 00:00:00 ps
name$ id
uid=7588469(name) gid=64000(user) groups=64000(user),25991(ugpdupccom),26220(ugpdupcgw),26223(ugpdupcinf),10004555(ugpdupcup)
//当前euid和uid是一样的,都是7588469,如何改成不一样的呢?
name$ sudo chown root.root bin/process_id
[sudo] password for user: //输入超级用户密码
name$ ls -l bin/process_id
-rwxr-xr-x 1 root root 7408 Apr 13 13:41 bin/process_id
name$ sudo chmod u+s bin/process_id
name$ ls -l bin/process_id
-rwsr-xr-x 1 root root 7408 Apr 13 13:41 bin/process_id
//euid有效用户id变成0了,实际用户id还是7588469
name$ bin/process_id
pid: 665757
ppid: 665019
uid: 7588469
euid: 0
user gid: 64000
gid: 665757
pgid: 665757
ppgid: 665019
name$ id root
uid=0(root) gid=0(root) groups=0(root)
8.进程创建与继承
8.1 进程创建
#include<sys/types.h>
#include<unistd.h>
pid_t fork(void);
//返回:子进程中为0,父进程中为子进程ID,出错为-1
pid_t vfork(void);
//返回:子进程中为0,父进程中为子进程ID,出错为-1
- fork创建的新进程被称为子进程,该函数被调用一次,但返回两次。两次返回的区别是:在子进程中的返回值为0,而在父进程中的返回值则是新子进程的进程ID。
- 创建子进程,父子进程哪个先运行根据系统调度且复制父进程的内存空间。
- vfork创建子进程,但子进程先运行且不重复父进程的内存空间。
例一:单次运行父进程和子进程
name$ vim src/process_fork.c
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
printf("pid: %d\n", getpid());
pid_t pid;
pid =fork(); //create child process
//After fork, there are two processes, namely the p
arent process and the child process
if (pid < 0) {
perror("fork error");
}else if (pid > 0) {
//parent process
printf("I am parent process pid is %d, ppid is %d, fork return is %d\n",
getpid(), getppid(), pid);
}else {
//child process
printf("I am child process pid is %d, ppid is %d, fork return is %d\n",
getpid(), getppid(), pid);
}
printf("pid: %d\n", getpid());
sleep(1);
exit(0);
}
name$ gcc -o bin/process_fork src/process_fork
name$ bin/process_fork
pid: 704017
I am parent process pid is 704017, ppid is 665019, fork return is 704018
pid: 704017
I am child process pid is 704018, ppid is 704017, fork return is 0
pid: 704018
name$ ps
PID TTY TIME CMD
665019 pts/60 00:00:00 bash
709057 pts/60 00:00:00 ps
例二:多次交替执行父子进程(父子进程运行顺序不一定,根绝实际系统调用情况来决定)
name$ vim src/process_fork2.c
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
int main(void)
{
printf("current pid: %d\n", getpid());
pid_t pid = fork();
if(pid < 0){
perror("fork error");
}else if(pid > 0){ //parent process
int i;
for(i = 0; i < 10; i++){
printf("This is parent process pid is: %d\n", getpid());
sleep(1);
}
}else{ //child process
int i;
for(i = 0; i < 10; i++){
printf("This is child process pid is: %d\n", getpid());
sleep(1);
}
}
return 0;
}
name$ gcc -o bin/process_fork2 src/process_fork2.c
name$ bin/process_fork2
current pid: 33140
This is parent process pid is: 33140
This is child process pid is: 33141
This is parent process pid is: 33140
This is child process pid is: 33141
This is parent process pid is: 33140
This is child process pid is: 33141
This is parent process pid is: 33140
This is child process pid is: 33141
This is parent process pid is: 33140
This is child process pid is: 33141
This is parent process pid is: 33140
This is child process pid is: 33141
This is parent process pid is: 33140
This is child process pid is: 33141
This is parent process pid is: 33140
This is child process pid is: 33141
This is parent process pid is: 33140
This is child process pid is: 33141
This is parent process pid is: 33140
This is child process pid is: 33141
8.2 继承
父进程fork出子进程,子进程会继承父进程的一些信息
8.2.1 子进程的继承属性
- 用户信息和权限、目录信息、信号信息、环境、共享存储段、资源限制、堆、栈和数据段、共享代码段。(父子进程的物理内存空间是不一样的)
注:P1代表父进程,P2代表子进程。
例一:(全局变量和静态变量存放在数据段,局部变量存放在栈中)
name$ vim src/process_fork.c
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
int g_v = 30;
int main(void)
{
int a_v = 30;
static int s_v = 30;
printf("pid: %d\n", getpid());
pid_t pid;
pid =fork(); //create child process
//After fork, there are two processes, namely the parent process and the child process
if (pid < 0) {
perror("fork error");
}else if (pid > 0) {
//parent process
g_v = 40; a_v = 40; s_v = 40;
printf("I am parent process pid is %d, ppid is %d, fork return is %d\n",
getpid(), getppid(), pid);
printf("g_v: %p, a_v:%p, s_v: %p\n", &g_v, &a_v, &s_v);
}else {
//child process
g_v = 50; a_v = 50; s_v = 50;
printf("I am child process pid is %d, ppid is %d, fork return is %d\n",
getpid(), getppid(), pid);
printf("g_v: %p, a_v:%p, s_v: %p\n", &g_v, &a_v, &s_v);
}
printf("pid: %d, g_v:%d, a_v:%d, s_v:%d\n", getpid(), g_v, a_v, s_v);
sleep(1);
exit(0);
}
user$ gcc -o bin/process_fork src/process_fork.c
user$ bin/process_fork
pid: 68503
I am parent process pid is 68503, ppid is 61863, fork return is 68504
g_v: 0x601064, a_v:0x7ffee4891708, s_v: 0x601068
pid: 68503, g_v:40, a_v:40, s_v:40
I am child process pid is 68504, ppid is 68503, fork return is 0
g_v: 0x601064, a_v:0x7ffee4891708, s_v: 0x601068
pid: 68504, g_v:50, a_v:50, s_v:50
缓存数据在堆里
name$ vim src/process_fork.c
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
int g_v = 30;
int main(void)
{
int a_v = 30;
static int s_v = 30;
printf("pid: %d\n", getpid());
FILE *fp = fopen("s.txt", "W");
int fd = open("s_fd.txt",
O_WRONLY|O_CREAT|O_TRUNC,
S_IRWXU|S_IRWXG);
char *s = "hello iotek";
ssize_t size = strlen(s) * sizeof(char);
//父进程调用
//标准I/O函数(带缓存-->全缓存)
fprintf(fp, "s:%s, pid:%d", s, getpid()); //写入缓存
//内核提供的I/O系统调用(不带缓存)
write(fd, s, size); //直接写入文件
pid_t pid;
pid =fork(); //create child process
//After fork, there are two processes, namely the parent process and the child process
if (pid < 0) {
perror("fork error");
}else if (pid > 0) {
//parent process
g_v = 40; a_v = 40; s_v = 40;
printf("I am parent process pid is %d, ppid is %d, fork return is %d\n",
getpid(), getppid(), pid);
printf("g_v: %p, a_v:%p, s_v: %p\n", &g_v, &a_v, &s_v);
}else {
//child process
g_v = 50; a_v = 50; s_v = 50;
printf("I am child process pid is %d, ppid is %d, fork return is %d\n",
getpid(), getppid(), pid);
printf("g_v: %p, a_v:%p, s_v: %p\n", &g_v, &a_v, &s_v);
}
//printf("pid: %d, g_v:%d, a_v:%d, s_v:%d\n", getpid(), g_v, a_v, s_v);
//父子进程都要执行,写入缓存
fprintf(fp, "pid: %d, g_v:%d, a_v:%d, s_v:%d\n", getpid(), g_v, a_v, s_v);
sleep(1);
fclose(fp);
close(fd);
exit(0);
}
name$ gcc -o bin/process_fork src/process_fork.c
name$ bin/process_fork
pid: 68503
I am parent process pid is 68503, ppid is 61863, fork return is 68504
g_v: 0x601064, a_v:0x7ffee4891708, s_v: 0x601068
I am child process pid is 68504, ppid is 68503, fork return is 0
g_v: 0x601064, a_v:0x7ffee4891708, s_v: 0x601068
name$ more s_fd.txt
hello iotek
name$ more s.txt
s:hello iotek, pid: 68503 pid: 68503, g_v:40, a_v:40, s_v:40
s:hello iotek, pid: 68503 pid: 68504, g_v:50, a_v:50, s_v:50
8.2.2 子进程特有属性
- 进程ID、锁信息、运行时间、未决信号
8.2.3 操作文件时的内核结构变化
- 子进程只继承父进程的文件描述表,不继承但共享文件表项和i-node。
- 父进程创建一个子进程后,文件表项中的引用计数器加1变成2,当父进程作close操作后,计数器减1,子进程还是可以使用文件表项,只有计数器为0时才会释放文件表项。
name$ vim src/process_append.c
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char *argv[])
{
if(argc < 2){
fprintf(stderr, "usage: %s file\n",argv[0]);
exit(1);
}
int fd = open(argv[1], O_WRONLY);
if(fd < 2){
perror("open error");
exit(1);
}
pid_t pid = fork();
if(pid < 0){
perror("fork error");
exit(1);
}else if(pid > 0){//parent process
//父进程将文件偏移量调整到文件尾部
if(lseek(fd, 0L, SEEK_END) < 0){
perror("lseek error");
exit(1);
}
}else{//child process
//子进程从文件尾部追加内容
char *str = "\nhello child";
ssize_t size = strlen(str) * sizeof(char);
sleep(3);
//此处的fd是从父进程中复制过来的,但是和父进程中的fd都是指向同一个文件
if(write(fd, str, size) != size){
perror("write error");
exit(1);
}
}
printf("pid: %d finish\n", getpid());
sleep(1);
//父子进程都要去关闭文件描述符表
close(fd);
return 0;
}
name$ gcc -o bin/process_append src/process_append.c
name$ more s_fd.txt
hello
name$ bin/process_append s_fd.txt
pid: 642694 finish
name$ pid: 642695 finish
name$ more s_fd.txt
hello
hello child
9.进程链、进程扇
- 进程链:父进程只fork一个子进程,而子进程继续创建子进程,子进程变父进程后不能再创建子进程。简言之:创建进程后,判断父进程退出循环,子进程继续循环
- 进程扇:与进程链反过来,父进程创建多个子进程,而子进程不再创建子进程。简言之:判断子进程退出循环,父进程继续循环fork
例一:进程链
name$ vim src/process_link.c
//进程链
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char *argv[])
{
int counter = 0;
if(argc < 2)
counter = 2;
else
counter = atoi(argv[1]);
int i = 1;
pid_t pid;
for(; i < counter; i++){
pid = fork();
if(pid < 0){
perror("fork error");
exit(1);
}else if(pid > 0) break;
//如果是父进程,退出,子进程会继续创建子进程
}
printf("pid: %d, ppid: %d\n", getpid(), getppid());
sleep(2);
return 0;
}
name$ gcc -o bin/process_link src/process_link.c
name$ bin/process_link
pid: 665967, ppid: 316581
pid: 665968, ppid: 665967
name$ ps
PID TTY TIME CMD
316581 pts/1 00:00:00 bash
665993 pts/1 00:00:00 ps
name$ bin/process_link 5
pid: 668736, ppid: 316581
pid: 668737, ppid: 668736
pid: 668738, ppid: 668737
pid: 668739, ppid: 668738
pid: 668740, ppid: 668739
例二:进程扇
name$ cp src/process_link.c src/process_swing.c
name$ vim src/process_swing.c
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char *argv[])
{
int counter = 0;
if(argc < 2)
counter = 2;
else
counter = atoi(argv[1]);
int i = 1;
pid_t pid;
for(; i < counter; i++){
pid = fork();
if(pid < 0){
perror("fork error");
exit(1);
}else if(pid == 0) break;
//这里做修改,如果是子进程,退出循环,父进程继续fork
}
printf("pid: %d, ppid: %d\n", getpid(), getppid());
sleep(2);
return 0;
}
name$ gcc -o bin/process_swing src/process_swing.c
name$ bin/process_swing 5
pid: 671925, ppid: 671924
pid: 671926, ppid: 671924
pid: 671924, ppid: 316581
pid: 671927, ppid: 671924
pid: 671928, ppid: 671924
name$ ps
PID TTY TIME CMD
316581 pts/1 00:00:00 bash
671937 pts/1 00:00:00 ps
10.几种特殊进程
10.1 僵尸进程
10.1.1 概念:
- 子进程结束但是没有完全释放内存(在内核中的task_struck没有释放),该进程就成为僵尸进程。<defunct>
- 当僵尸进程的父进程结束后就会被init进程领养,最终被回收。
name$ vim src/process_zombie.c
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
int main(void)
{
pid_t pid;
pid = fork();
if(pid < 0){
perror("fork error");
exit(1);
}else if(pid == 0){ //child process
printf("pid: %d, ppid: %d\n", getpid(), ge
tppid());
exit(0); //子进程结束成为僵尸进程
}
while(1){ //父进程继续循环
sleep(1);
}
return 0;
}
name$ gcc -o bin/process_zombie src/process_zombie.c
name$ bin/process_zombie //目前父进程被挂住了,执行ctrl + C可以中断等待,结束父进程。
pid: 128065, ppid: 128064
//打开另一个窗口查看进程状态
name$ ps -aux | grep process_zombie
name 128064 0.0 0.0 4216 360 pts/37 S+ 16:48 0:00 bin/process_zombie
name 128065 0.0 0.0 0 0 pts/37 Z+ 16:48 0:00 [process_zombie]<defunct>
name 130951 0.0 0.0 112812 948 pts/39 S+ 16:50 0:00 grep --color= autoprocess_zombie
10.1.2 如何避免僵尸进程
- 让僵尸进程的父进程来回收,父进程每隔一段时间来查询子进程是否结束并挥手,调用wait()或者waitpid(),通知内核释放僵尸进程。
- 采用信号SIGCHILD通知处理,并在信号处理程序中调用wait函数。
- 让僵尸进程成为孤儿进程,由init进程回收。
10.2 守护进程
- 守护进程是生长周期长的一种进程。他们常常在系统引导装入是启动,在系统关闭时终止。
- 所有守护进程都以超级用户(用户ID为0)的优先权运行
- 守护进程没有控制终端
- 守护进程的父进程都是init进程
10.3 孤儿进程
- 父进程结束,子进程就会成为孤儿进程,会由1号进程(init进程)领养。
name$ vim src/process_orphen.c
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
pid_t pid;
pid = fork();
if(pid < 0){
perror("fork error");
exit(1);
}else if(pid > 0){//parent process
printf("%d deaded\n", getpid());
exit(0);
}else{ //child process
sleep(4);
printf("pid: %d, ppid: %d\n", getpid(),get
ppid());
}
return 0;
}
name$ gcc -o bin/process_orphen src/process_orphen.c
name$ bin/process_orphen
158033 deaded
name$ pid: 158034, ppid: 1