接着上一篇博客,整理一下有关Linux进程的知识。
什么是进程?
1)执行中的程序实例叫做进程
2)Linux是多任务系统,可以运行多个进程并发执行
3)进程和程序的关系
- 进程由程序创建
- 一个程序可以产生多个进程
4)进程由内核管理调度
5)每个进程有一个进程号PID
6)进程的层次结构,除了第一个进程外每个进程都有一个父进程
- shell进程的父进程是init进程
- 子进程的属性继承自父进程
ps
1)命令功能:显示进程属性
2)命令格式: ps[option]
3)常用选项:
- -e或-A 显示包含用户和系统进程的所有进程
- -a 显示所有用户进程
- -ax 显示系统进程
- -f 显示PPID等详细进程信息
- -l 显示进程状态等相关信息
- -u 显示CPU和内存占用情况
守护进程
1)没有和终端相关联的进程
2)不能读和写终端
3)守护进程处于睡眠状态
4)在接受输入信号时被唤醒
进程的创建
1)一个进程只能由另一个进程来创建
2)进程创建机制包含三个部分:
- Fork:创建已有进程的副本来创建新进程,新进程是原进程的子进程
- Exec:运行一个程序,用新程序的代码和数据来覆盖自己的映像
- Wait:父进程等待子进程执行程序,并在子进程退出后记录其退出状态
3)例子:在shell中执行cat file命令
- shell进程fork自己的副本,生成新的shell进程
- 新的shell进程用cat的可执行映像覆盖自己,运行cat命令
- 原shell进程等待cat终止,然后获取子进程的退出状态
子进程继承的属性
1)fork创建进程时,子进程会继承父进程的大部分属性
- 创建进程的用户UID和组GID
- 运行该进程的当前目录
- 父进程中打开的所有文件描述符
- 环境变量值
2)继承的这些属性是由子进程维护的副本,不影响父进程
3)用户定义的变量不会由子进程继承
- 可使用export命令将变量导出至所有该进程创建的子进程中
- 子进程对变量的修改在父进程中不可见
进程状态
1)进程在任意时刻都处于一个特定状态
- 可运行状态 R:进程创建之后,实际运行之前
- 运行状态 O:进程实际运行时
- 睡眠状态 S:在等待输入信号时
- 挂起状态 T:用户按下Ctrl+z挂起进程时
- 僵尸状态 Z:父进程不再等待该进程的退出状态时
2)进程状态可用ps –l命令查看
- 列表中的第二列
- 僵尸进程在最后一列显示<defunct>
作业控制 1
1)作业是一组进程,如管道组合ls | head
2)作业后台运行
在命令行末尾加上&:command &
- 父进程不会等待子进程的退出,shell会显示该命令的PGID和PID
- 作业的标准输出和错误输出到终端
- 最后一个后台进程ID被存储在$!变量中
nohup命令:
- 格式:nohup command &
- 作业的标准输出和错误输出到nohup.out
- 在用户注销之后后台程序依然运行
作业控制 2
1)每个进程都属于一个进程组(作业),组中的每个进程有相同的PGID
2)发送给进程组的信号会发给组中每一个进程
3)作业控制命令
- %PGID 作业ID
- %str 以str开头的作业名
- %?str 包含str的作业名
作业控制命令 | 功能 |
fg | 将作业移到前台运行 |
bg | 将作业移到后台运行 |
[Ctril+z] | 挂起当前的前台作业 |
jobs | 列出活动作业 |
kill | 杀死作业 |
进程的虚拟地址空间
1)一个进程在运行一个C程序时,会在内存中开辟一块虚拟地址空间供程序访问
2)虚拟地址空间的构成
- 程序文本:包含要执行的指令
- 数据:程序使用的全局、静态变量
- 栈:函数的参数和局部变量以及要返回的地址
- 堆:动态内存分配(malloc, calloc)
- 命令行参数和环境变量存储在栈的底部
- 动态共享库的使用位于栈和堆之间
在C语言中实现进程控制
1)C语言提供了一系列的系统调用和库函数来实现对进程的控制
2)头文件:#include<unistd.h>
3)查看进程属性的系统调用
- int getpid();
- int getppid();
- int getuid();
- int getgid();
- char *getenv(const char *name);
- int setenv(const char *name, const char *val, int overwrite);
fork函数
1)功能:复制当前进程以创建新进程
2)声明:pid_t fork(void);
3)参数:无
4)返回值:创建的子进程的PID
5)复制当前进程信息,生成子进程
6)fork在父进程和子进程中都有返回值
- 父进程中:子进程的PID
- 子进程中:0
7)在fork返回后,两个进程都在fork语句之后继续执行
exec函数族
1)运行程序,用新程序的地址空间覆盖进程地址空间
2)库函数execl、execv、execlp、execvp
- int execl(const char *path, const char *arg0,…, NULL);
- int execv(const char *path, char *const argv[ ]);
- path参数给出程序的绝对或相对路径
- 参数列表arg0,…表示命令行中的每一个单词,最后以NULL结束
- int execlp(const char *file, const char *arg0,…, NULL);
- int execvp(const char *file, char *const argv[ ]);
- file参数给出程序文件名称,在环境变量PATH指定的路径中寻找
3)一个成功的exec函数族中的库函数的执行不会返回
僵尸进程
1)子进程终止,但父进程还没有接收其退出状态时,该子进程处于僵尸状态。
2)僵尸形成的机制:
- 每一个进程退出时,内核释放其占用的资源,但是仍然保留其进程号和退出状态
- 父进程回收子进程的退出状态,在此之前子进程处于僵尸状态
- 若父进程没有回收子进程状态,则僵尸进程一直存在
- 若父进程退出,init进程会接管其所有子进程,回收他们的退出状态
3)僵尸进程的危害:会占用有限的进程号资源
4)避免僵尸进程:通过wait或waitpid函数
wait函数
1)等待子进程死亡,并收集子进程退出状态
2)声明:pid_t wait(int *stat)
3)参数:保存子进程退出状态的变量地址
4)返回值:死亡或挂起子进程的PID
6)调用进程在执行wait时一直等待,直到其子进程终止
7)调用进程在wait返回后继续执行
进程间通信
Linux系统中进程间的通信方式主要有信号、管道、消息队列和共享内存这4种。
信号
1)一种进程间通信的简单形式
2)信号可以是同步的,也可以是异步的
3)信号来源:
- 键盘输入:影响前台作业,[Ctrl+c]生成SIGINT信号,[Ctrl+z]生成SIGTSTP信号
- 硬件异常:算术异常SIGFPE,非法指令SIGILL,内存访问违规SIGSEGV
- C程序:生成信号的函数,如alarm生成SIGALRM
- 其他:SIGCHILD通知父进程子进程死亡,SIGTIN阻止后台作业读取终端
4)信号的生命周期
- 生成→[延迟]→送达→处理
常用信号
1)信号符号名称:以SIG开头
2)
信号编号 | 信号符号名称 | 功能 |
2 | SIGINT | 中断当前终端,Ctrl+c |
3 | SIGQUIT | 退出当前终端,并生成一个核心文件 |
9 | SIGKILL | 杀死进程,不能被忽略或捕获 |
15 | SIGTERM | 终止进程,可能被忽略(kill命令默认参数) |
20 | SIGTSTP | 挂起当前前台进程,Ctrl+z |
17 | SIGCHLD | 内核通知父进程子进程终止 |
3)使用kill命令向进程发送控制信号:kill –s 信号 进程ID
- 默认信号为SIGTERM
- 信号选项可不带SIG前缀
- 可使用kill –l查看所有信号
信号处理
1)信号处理机制:
- 信号发送给进程时,内核在进程表中设置该信号的掩码字段
- 进程检查进程表中的该字段,然后检查相应的信号处置表
- 根据信号处置表对信号进行处理
- 如果处理方式是捕获信号,则挂起自身,调用信号处理器
- 当处理器返回时,进程恢复执行
2)信号处理相关的系统调用
- sigaction: 指定信号处理器
- alarm: 设定一个计时器,经过一定时间后生成SIGALRM信号
- pause: 阻塞程序执行,直到接收到信号为止
- kill: 向进程发送一个信号
sigaction系统调用
1)功能:指定信号处理器
2)头文件:#include<signal.h>
3)声明:int sigaction (int sig, const struct sigaction * restrict act, struct sigaction * restrict oact)
4)用法:指定该系统调用,由act结构来处理接收到的sig信号
oact存储当前处理方式
5)sigaction结构
- sa_handler设置信号处置方式
- 函数名,SIG_IGN,SIG_DFL
- sa_mask指定阻塞信号
- sa_flags指定可选标志
struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*)(int, siginfo_t *, void *) sa_sigaction;
}
kill系统调用
1)功能:向指定进程发送指定信号
2)格式:int kill (pid_t pid, int sig);
- pid 指定进程PID
- sig 指定信号
管道
管道分为无名管道和命名管道
无名管道通信
1)pipe系统调用
2)格式:int pipe (int fd[2])
3)参数:
- 2个整数组成的数组,分别表示管道的输入端fd[0]和输出端fd[1]
- 可以使用write向fd[1]写入数据
- 可以使用read从fd[0]读取数据
- 写入fd[1]中的内容都可以由fd[0]读取
命名管道通信
1)无名管道只能用于同源进程间(父子进程间)通信
2)命名管道FIFO是一种特殊的管道,存在文件系统中
- mkfifo库函数:#include <sys/types.h, sys/stat.h>
- int mkfifo(const char *fifoname, mode_t mode);
3)参数:fifoname 管道文件名
- mode 文件读写权限,三位8进制数,前加0
4)可以用open/close 打开/关闭FIFO管道,用read/write进行读/写
5)删除命名管道:unlink(fifoname) 命令
6)需要在两个通信的进程中分别打开相同的命名管道
命名管道的阻塞问题
1)在同一个进程中命名管道是单向的
2)不能以O_RDWR方式打开命名管道
3)open的非阻塞选项
- open(fifoname,O_RDONLY|O_NONBLOCK)
- open(fifoname,O_WRONLY|O_NONBLOCK)
4)以阻塞方式打开的管道,只有在管道另一端有操作时才会继续执行读/写操作
5)以非阻塞方式打开的管道,会立即执行读/写操作(有可能失败)
消息队列
1)从一个进程向另一个进程发送一个数据块
2)每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构
3)使用:通过四个系统调用:
- msgget 创建消息队列
- msgsnd 向消息队列中发送消息
- msgrcv 从消息队列中接收消息
- msgctl 控制消息队列
消息队列的使用
1)msgget:创建和访问一个消息队列
- int msgget(key_t, key, int msgflg);
- key 消息队列名称;msgflg 消息队列权限
- 成功时返回消息队列标识符,失败时返回-1
2)msgsnd:发送消息到消息队列
- int msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
- msgid 消息队列标识符;msg_ptr 指向待发消息的指针
- msg_sz 消息的长度;msgflg 队列是否满的标志
- 成功时返回0;失败时返回-1
3)消息类型:结构体类型
struct my_msg {
long int msg_type;
char text[size]
}
4)msgrcv: 接收消息
- int msgrcv(int msgid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
- msgid 消息队列标识符;msg_ptr 指向收到消息的指针;msg_sz 消息长度;
- msgtype 消息类型,=0接收第一个可用消息;>0接收相同类型消息;<0接收类型小于等于其绝对值的消息
- msgflg 队列是否为空的标志
- 成功时返回接收的字符数,失败时返回-1
5)msgctl: 控制消息队列
- int msgctl(int msgid, int command, struct msgid_ds *buf);
- msgid 消息队列标识符;
- command 控制命令
- buf 指向消息队列模式和访问权限的结构
共享内存
1)共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式
2)进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址
3)如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程
4)注意:共享内存并未提供同步保护机制
5)使用:
- shmget 创建共享内存
- shmat 连接共享内存以开启访问
- shmdt 分离共享内存
- shmctl 共享内存控制
共享内存的使用
1)shmget: 创建共享内存
- int shmget(key_t key, size_t size, int shmflg);
- key 命名共享内存;size 共享内存容量;shmflg 共享内存权限
- 成功时返回共享内存标识,失败时返回-1
2)shmat: 连接共享内存
- void *shmat(int shm_id, const void *shm_addr, int shmflg);
- shm_id 共享内存标识符;shm_addr 连接位置地址,通常留空
- shmflg 标志位,通常为0
- 成功时返回指向共享内存的指针,失败时返回-1
3)shmdt: 分离共享内存
- int shmdt(const void *shmaddr);
- shmaddr 是shmat返回的共享内存地址
- 成功时返回0,失败时返回-1
4)shmctl: 控制共享内存
- int shmctl(int shm_id, int command, struct shmid_ds *buf);
- shm_id 共享内存标识符;
- command 控制命令
- buf 指向共享内存模式和访问权限的结构
- 成功时返回0,失败时返回-1