1、获得进程ID
每个进程都有一个唯一的正数进程ID(PID)。
getpid函数返回调用进程的PID。
getppid函数返回它的父进程的PID。
#include<sys/types.h>
#include<unistd.h>
pid_t getpid(void);
pid_t getppid(void);
//返回调用者或其父进程的PID
返回一个pid_t类型的值,在Linux系统上被types.h定义为int。
2、创建和终止进程
(1)进程状态
-
运行。进程要么在CPU上执行,要么在等待被执行且最终会被内核调度。
-
停止。进程的执行被挂起(suspended ),且不会被调度。当收到SIGSTOP 、SIGT-STP、SIGTTIN 或者SIGTTOU 信号时,进程就停止,并且保持停止直到它收到一个SIGCONT 信号,在这个时刻,进程再次开始运行。(信号是一种软件中断的形式,将在8.5节中详细描述。)
-
终止。进程永远地停止了。进程会因为三种原因终止:1)收到一个信号,该信号的默认行为是终止进程,2)从主程序返回,3)调用exit函数。
(2)终止进程
exit函数以status退出状态来终止进程(也可以主程序中return status来实现)
#include<stdlib.h>
void exit(int status);
(3)创建进程
调用fork函数可以创建一个子进程。新创建的子进程几乎但不完全与父进程相同,子进程得到与父进程用户级虚拟地址空间相同但是独立的一份副本,包括代码段和数据段、堆、共享库和用户栈以及父进程任何打开的文件描述符。他们的区别在于他们的PID不同。
#include<unistd.h>
#include<sys/types.h>
pid_t fork(void);
返回:子进程返回0,父进程返回子进程的PID,如果出错,则为-1。
fork函数调用一次,返回两次:一次位于调用进程(父进程)中,一次位于新创建的进程(子进程)中。父进程中,fork函数返回子进程的PID,子进程中,fork函数返回0。可作为辨别父进程和子进程的依据。
范例:
#incldue<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int x = 1;
pid = fork();
if (pid == 0)
{
printf("Child : x=%d\n", ++x);
exit(0);
}
printf("parent : x=%d", --x);
exit(0);
}
注意:子进程与父进程并发执行,拥有相同但是独立的地址空间,且共享文件。
3、回收子进程
(1)init进程
在Linux中,init进程又内核创造,是内核启动的第一个用户级进程,是所有进程的父进程,PID为1,不会终止
(2)僵死进程
终止但未被回收的进程。
(3)回收子进程概念
当一个进程由于某种原因终止时,内核并不是立即把他从系统中清除。进程保持在一种已终止的状态,直到被父进程回收。当父进程回收已终止的子进程时,内核将子进程的退出状态传给父进程,然后抛弃已终止的进程。如果一个父进程终止了,内核会安排init进程作为它的父进程。
(4)函数调用
可以通过调用waitpid或wait函数来等待子进程终止或者停止
#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid, int *statusp, int options);
pid_t wait(int *statusp);
默认情况下(当options=0时),waitpid挂起调用进程的执行,直到他的等待集合中的一个子进程终止。
判定等待集合的成员是由参上pid来确定的:
- 如果pid > 0,那么等待集合就是单独的一个子进程,它的进程ID等于pid
- 如果pid = -1,那么等待集合就是由父进程所有的子进程组成的。
修改默认行为:
可以将options设置为常量WNOHANG、WUNTRACED和WCONTINUED的各种组合来修改默认行为:
- WNOHANG:如果等待集合中的任何子进程都还没有终止,那么就立刻返回。
- WUNTRACED:挂起调用进程的执行,直到等待结合中的进程变成已终止或者被停止。返回的PID为导致返回的已终止或已停止的子进程的PID
- WCONTINUED:挂起调用进程的执行,直到等待集合中一个正在运行的进程终止或等待集合中一个进程收到SIGCONT信号重新开始执行。
可以通过或运算符“|”把这些选项组合起来。
检查已回收子进程的退出状态:
如果statusp参数非空,waitpid会在status上放关于导致返回的子进程状态信息,status是指向statusp的值。
-
WIFEXITED (status ):如果子进程通过调用exit或者一个返回(return )正常终止,就返回真。
-
WEXITSTA (status ):返回一个正常终止的子进程的退出状态。只有在WIFEXITED ()返回为真时,才会定义这个状态。
-
WIFSIGNALED (status ):如果子进程是因为一个未被捕获的信号终止的,那么就返回真。
-
WTERMSIG (status ):返回导致子进程终止的信号的编号。只有在WIFSIG -NALED ()返回为真时,才定义这个状态。
-
WIFSTOPPED (status ):如果引起返回的子进程当前是停止的,那么就返回真。
-
WSTOPSIG (status ):返回引起子进程停止的信号的编号。只有在WIFSTOPPED ()返回为真时,才定义这个状态。
-
WIFCONTINUED (status ):如果子进程收到SIGCONT 信号重新启动,则返回真。
错误条件
- 如果调用进程没有子进程,waitpid返回-1,并设置error为ECHILD。
- 如果waitpid函数被一个信号中断,返回-1,设置error为EINTR。
wait函数
调用wait(&status)等价于调用waitpid(-1,&status,0)
4、进程休眠
sleep函数将一个进程挂起一段指定时间。如果请求时间到了,返回0,否则返回剩下要休眠的秒数。
usleep函数将一个进程挂起一段指定时间,单位为微秒。
pause函数让调用进程休眠,直到收到一个信号。
#incldue<unistd.h>
unsigned int sleep(unsigned int secs);
void usleep(unsigned int usecs);
int pause(void);
5、加载并运行程序
execve函数在当前进程的上下文中加载并运行一个新程序。
#include<unistd.h>
int execve(const char *filename, const char *argv[], const char *envp[]);
//如果成功,则不返回,如果错误,则返回-1
execve加载可执行目标文件filename,并带参数列表argv和环境变量列表envp。只有当出现错误时,execve才会返回到调用程序。与fork()不同,evecve调用一次且不返回。
环境数组中的每个指针指向形如"key=value"的键值对,环境数组以null结尾。
Linux提供的环境数组操作函数:
#include<stdlib.h>
char *getenv(const char *key);
//若存在则返回指向key的指针,若无匹配则返回NULL
getenv函数在环境数组中搜索字符串"key=value",如果找到了,它就返回一个指向value的指针,否则它就返回NULL。
#include<stdlib.h>
int setenv(const char * key, const char *newvalue, int overwrite);
//若成果返回0,否则返回-1
void unsetenv(const * key);
如果环境数组包含一个形如"key=value"的字符串,调用unsetenv会删除该环境变量,调用setenv会在overwrite非零时用newvalue替代oldvalue,如果key不存在,会把“key=value“添加到数组中。
6、信号量
Linux信号:
序号 | 名称 | 默认行为 | 相应事件 |
---|---|---|---|
1 | SIGHUP | 终止 | 终止控制终端或进程 |
2 | SIGINT | 终止 | 键盘产生的中断(Ctrl-C) |
3 | SIGQUIT | 终止 | 键盘产生的退出 |
4 | SIGILL | 终止 | 非法指令 |
5 | SIGTRAP | 终止并转储内存 | 跟踪陷阱 |
6 | SIGABRT/SIGIOT | 终止并转储内存 | 来自abort函数的终止信号 |
7 | SIGBUS/SIGEMT | 终止 | 总线异常/EMT指令 |
8 | SIGFPE | 终止并转储内存 | 浮点运算溢出 |
9 | SIGKILL | 终止 | 强制进程终止 |
10 | SIGUSR1 | 终止 | 用户定义的信号1 |
11 | SIGSEGV | 终止并转储内存 | 无效的内存故障(段故障) |
12 | SIGUSR2 | 终止 | 用户定义的信号2 |
13 | SIGPIPE | 终止 | 向一个没有读取的管道中写入数据 |
14 | SIGALRM | 终止 | 时钟中断(闹钟) |
15 | SIGTERM | 终止 | 软件终止信号 |
16 | SIGSTKFLT | 终止 | 协处理器栈错误 |
17 | SIGCHLD | 忽略 | 子进程退出或中断 |
18 | SIGCONT | 忽略 | 如进程停止状态则开始运行 |
19 | SIGSTOP | 停止直到下一个SIGCONT | 停止进程运行 |
20 | SIGSTP | 停止直到下一个SIGCONT | 键盘产生的停止 |
21 | SIGTTIN | 停止直到下一个SIGCONT | 后台进程请求输入 |
22 | SIGTTOU | 停止直到下一个SIGCONT | 后台进程请求输出 |
23 | SIGURG | 忽略 | 套接字上的紧急情况 |
24 | SIGXCPU | 终止 | CPU时间超出限制 |
25 | SIGXFSZ | 终止 | 文件大小超出限制 |
26 | SIGVTALRM | 终止 | 虚拟定时器期满 |
27 | SIGPROF | 终止 | 剖析定时器期满 |
28 | SIGWINCH | 忽略 | 窗口大小变化 |
29 | SIGIO/SIGPOLL | 终止 | 在某个描述符上可执行I/O操作 |
30 | SIGPWR | 终止 | 电源故障 |
发送信号:
- 内核检测到一个系统事件
- 进程调用了kill函数
方式:
-
可以通过/bin/kill程序向另外的进程发送任意的信号
-
可以从键盘发送信号:在键盘上输入Ctrl+C会导致内核发送一个SIGINT信号到前台进程组的每个进程。输入Ctrl+C会导致内核发送一个SIGTSTP信号到前台进程组的每个进程。
-
进程可以通过调用kill函数发送信号
#include<sys/types.h> #include<signal.h> int kill(pid_t pid, int sig);
如果pid > 0 那么kill函数发送信号号码sig给进程pid。如果pid等于0,那么kill发送信号sig给调用进程所在进程组中的每个进程,包括自己。如果pid小于0,kill发送信号sig给进程组|pid|中的每一个进程。
-
进程可以调用alarm函数向自己发送SIGALRM信号
#include<unistd.h> unsigned int alarm(unsigned int secs);
alarm函数安排内核在secs秒后发送一个SIGALRM信号给调用进程,如果secs为0,那么不会调度安排新的闹钟。
接收信号:
- 目的进程被内核强迫以某种方式对信号的发送作出反应时,它就接收了信号。进程可以忽略这个信号,终止或者通过执行一个信号处理程序捕获这个信号。
- 一个发出而没有被接收的信号叫待处理信号,任何时刻,一种类型至多只会有一个待处理信号。一个待处理信号最多只能被接收一次。
默认行为:
- 进程终止
- 进程终止并转储
- 进程停止知直到被SIGCONT信号重启
- 进程忽略该信号
进程可以通过signal函数修改和信号相关联的默认行为。(SIGSTOP和SIGKILL的默认行为不能修改)
#include<signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signal函数可以通过下列三种方法之一来改变和信号signum相关连的行为:
- 如果handler是SIG_IGN,那么忽略类型为signum的信号。
- 如果handler是SIG_DFL,那么类型为signum的信号行为恢复默认。
- 其他情况均为用户定义的函数的地址,这个函数被称为信号处理程序,只要进程接收到一个类型为signum的信号,就会调用这个程序。通过把处理程序的地址传递到signal函数从而改变默认行为,这叫设置信号处理程序。调用信号处理程序成为捕获信号。执行信号处理程序被称为处理信号。
7、非本地转跳
定义:将控制直接从一个函数转移到另一个当前正在执行的函数,而不需要经过正常的调用-返回序列。
Linux中:非本地转跳是通过setjmp和longjmp函数来实现的。
#include<setjmp.h>
int setjmp(jmp_buf env);
int sigsetjmp(sigjmp_buf env, int savesigs);
//setjmp返回0
setjmp函数在env缓冲区中保存当前调用环境,以供后面的longjmp使用,并返回0。调用环境包括程序计数器、栈指针和通用目的寄存器。
#include<setjmp.h>
int longjmp(jmp_buf env, int retval);
int siglongjmp(sigjmp_buf env,int retval);
//从不返回
longjmp函数从env缓冲区中恢复调用函数,然后触发一个从最近一次初始化env的setjmp调用的返回,并带有非零的返回值retval。
非本地转跳的一个重要应用是允许一个深层嵌套的函数调用中立即返回,通常是检测到某种错误情况引起的。如果在一个深层嵌套的函数调用中发现一个错误情况,可以使用非本地转跳直接返回一个普通的本地化的错误处理程序,而不是费力的解开掉用栈。(类似面向对象语言的异常处理机制)
8、Linux操作进程的工具
- STRACE:打印一个正在运行的程序和它的子进程调用的每个系统调用的轨迹。用-static编译能得到一个不带大量与共享库相关的输出的轨迹。
- PS:列出当前系统中的进程(包括僵死进程)
- TOP:打印出关于当前进程资源使用的信息。
- PMAP:显示进程的内存映射。