进程控制
进程:一个运行的程序,内核调度的最小单位,资源分配的最小单位
1.进程标识
类pid_t 类型,----> 0 内核调用进程
----> 1 init 进程
2.获取进程号
getpid(2)获取子进程号
getppid(2)获取子进程的父进程号
头文件
#include <sys/types.h>
#include <unistd.h>
函数声明
pid_t getpid(void);
pid_t getppid(void);
返回值
//These functions are always successful.
进程状态
D uninterruptible sleep (usually IO)
不间断睡眠(通常是IO)
R running or runnable (on run queue)
正在运行或可运行(在运行队列中)
S interruptible sleep (waiting for an event to complete)
可中断睡眠(等待事件完成)
T stopped by job control signal
由作业控制信号停止
t stopped by debugger during the tracing
在跟踪期间由调试器停止
W paging (not valid since the 2.6.xx kernel)
分页(自2.6.xx内核版本后无效)
X dead (should never be seen)
已经死亡的(不会被看到)
Z defunct ("zombie") process, terminated but not reaped by its parent
僵尸进程,已经结束但未被收尸
进程优先级
< high-priority (not nice to other users)
高优先级(对其他用户不好)
N low-priority (nice to other users)
低优先级(友好)
L has pages locked into memory (for real-time and custom IO)
将页面锁定到内存中(用于实时和自定义IO)
s is a session leader
会话领导者
l is multi-threaded (using CLONE_THREAD, like NPTL pthreads do)
多线程
+ is in the foreground process group
位于前台进程组
3.fork( )
创建一个子进程
用途
网络模型,shell
头文件
#include <unistd.h>
函数声明
pid_t fork(void);
复制当前进程作为父进程,(缓存区,虚拟地址空间,进程表项.,文件表项...) ,但是有自己独立的文件表项,不共享 ,除了ID号不会复制, “相当于两个一模一样的程序”
返回值
//返回进程号,子进程为0,否则为父进程,
//错误返回-1,设置errno
用法示例
/*
* 使用fork创建子进程,getpid获取进程号,理解父子进程
*/
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t pid;
pid = fork();
if(pid == -1)
{
perror("fork()");
return 1;
}
printf("[%d] hello world\n", getpid());
if(pid == 0)
{
printf("[子进程%d] [父进程%d]the child is rinning\n", getpid(),getppid());
}
else
printf("[%d] the parent is running\n",getpid());
return 0;
}
4.终端命令ps
man ps 查看详细信息
ps aux --->查看进程状态 各项含义
USER PID STAT TTY
所属用户,进程号,进程状态,运行终端
ps axj --->查看父进程 各项含义
PPID PID PGID PSID
父进程号, 进程号 进程组
ps exf --->打印进程间关系
5.收尸函数
可为任意子进程收尸
1. wait(2)
只能父进程为子进程收尸,子进程结束后需要收尸,可以为任意子进程收尸
头文件
#include <sys/types.h>
#include <sys/wait.h>
函数声明
pid_t wait(int *status);
参数含义
如果不关心子进程状态,可传NULL;
如果需要获取子进程状态,传整型地址,查看返回的状态需要使用宏定义函数 WEXITSTATUS(status)
用法示例
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
pid_t pid;
int status;
pid = fork();
if(pid == -1)
{
perror("fork()");
return 1;
}
printf("[%d] hello world\n", getpid());
if(pid == 0) //child 如果是子进程
{
sleep(1);
printf("[子进程:%d] [父进程:%d]the child is running\n",\
getpid(),getppid());
return 100;
}
else
{
wait(&status);
printf("the child dead with %d\n",WEXITSTATUS(status));
printf("[%d] the parent is running\n",getpid());
}
return 0;
}
2. waitpid(2)
为任意进程收尸,进程结束后需要收尸
头文件
#include <sys/types.h>
#include <sys/wait.h>
函数声明
pid_t waitpid(pid_t pid, int *status, int options);
参数含义
参考man手册
The value of pid can be:
< -1 meaning wait for any child process whose process group
ID is equal to the absolute value of pid.
等待其进程组的任何子进程ID等于pid绝对值
-1 meaning wait for any child process.
等待任意子进程
0 meaning wait for any child process whose process group
ID is equal to that of the calling process.
等待其进程组的任何子进程ID等于调用进程的ID
> 0 meaning wait for the child whose process ID is equal to
the value of pid.
等待进程ID等于子进程pid的绝对值
The value of options is an OR of zero or more of the following constants:
WNOHANG return immediately if no child has exited.
WUNTRACED also return if a child has stopped (but not traced
via ptrace(2)). Status for traced children which
have stopped is provided even if this option is not
specified.
WCONTINUED (since Linux 2.6.10)
also return if a stopped child has been resumed by
delivery of SIGCONT.
6.进程终止
理解下图
C程序如何终止.pngreturn调用exit( ),exit 调用终止处理程序和标准I/O清理程序,然后调用 _exit( )或 Exit( )
exit( ) 直接调用 _exit( )或 _Exit( )
_exit( ) 或 _EXIT( )直接调用内核
用法示例
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
static int test(void)
{
sleep(1);
printf("[子进程:%d] [父进程:%d]the child is running\n",\
getpid(),getppid());
//return 100;
//exit(100);
_exit(100);
}
int main(int argc, char *argv[])
{
pid_t pid;
int status;
pid = fork();
if(pid == -1)
{
perror("fork()");
return 1;
}
printf("[%d] hello world\n", getpid());
if(pid == 0) //child 如果是子进程
{
test();
}
else
{
wait(&status);
printf("the child dead with %d\n",WEXITSTATUS(status));
printf("[%d] the parent is running\n",getpid());
}
return 0;
}
7.终止处理
atexit(3) 进程终止处理函数
#include <stdlib.h>
int atexit(void (*function)(void));
用法示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
// 终止处理程序
static void atexit_handler1(void)
{
printf("%s was called\n", __FUNCTION__);
}
static void atexit_handler2(void)
{
printf("%s was called\n", __FUNCTION__);
}
static void atexit_handler3(void)
{
printf("%s was called\n", __FUNCTION__);
}
static void exit_handler4(int s, void *arg)
{
printf("%s was called\n", __FUNCTION__);
printf("%d\n", s);
printf("arg:%s\n", (char *)arg);
}
static int test(void)
{
sleep(1);
printf("[%d][%d]the child is running", getpid(), getppid());
// return 100;
//exit(100);
_exit(100);
}
int main(void)
{
pid_t pid;
int status;
// 注册终止处理程序
atexit(atexit_handler1);
atexit(atexit_handler2);
atexit(atexit_handler3);
on_exit(exit_handler4, "handler4");
printf("good morning");
fflush(NULL);
pid = fork();
if (pid == -1)
{
perror("fork()");
return 1;
}
printf("[%d]hello world\n", getpid());
if (pid == 0)
{
// child
test();
}
else
{
// 父进程为子进程收尸
wait(&status);
printf("the child dead with %d\n",WEXITSTATUS(status));
printf("[%d]the parent is running\n", getpid());
}
return 0;
}
on_exit(3)
用法同atexit(3),但是on_exit(3)可传参
8.孤儿进程,僵尸进程
孤儿进程: 父进程已经终止,子进程还未结束,此时子进程就称为孤儿进程; 在ubuntu系统下,孤儿进程由一 个1号进程的子进程管理,这个子进程成为孤儿院;有的系统由1号进程直接管理孤儿进程。
僵尸进程: 子进程已经终止,还未被收尸,此时的状态就是僵尸进程
示例
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
pid = fork();
if (pid == -1)
{
perror("fork()");
exit(EXIT_FAILURE);
}
if (pid == 0)
{
// child
printf("child....\n");
sleep(100); //父进程结束,子进程还在,则为孤儿进程
exit(EXIT_SUCCESS);
}
//sleep(100); //父进程没有为孤儿进程收尸,为僵尸进程
//wait(NULL);
return 0;
}
9. vfork(2)
用法和fork(2)一样,但是有区别;
区别:
fork(2)会复制父进程的页表,对于同一个变量,子进程改变它,在父进程中不会改变,这是因为变量的地址看似没变,实则子进程的变量地址发生了改变。
vfork(2)不会复制父进程表项,对于同一变量,子进程改变它,在父进程中也会改变,因为他俩公用一个变量地址。即地址空间共享
对于vfork(2)创建的子进程,父进程不用给子进程收尸,父进程会阻塞等待子进程终止。
现在很少使用,只需掌握 fork 和 vfork 的区别
示例
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(void)
{
int var = 100;
pid_t pid;
//pid = fork();
pid = vfork();
if (pid == -1)
{
perror("fork()");
exit(1);
}
if (pid == 0)
{
// child
var = 200;
sleep(2);
printf("[%d]var:%d, &var:%p\n", getpid(), var, &var);
exit(0);
}
//wait(NULL);
printf("[%d]var:%d, &var:%p\n", getpid(), var, &var);
exit(0);
}
10. exec族函数
新的进程映像替换了调用的进程映像
(1). execl( )
头文件
#include <unistd.h>
函数声明
int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
完全替换一个进程
参数含义
(可执行文件路径; 文件名; 可选参数即argv; NULL)
返回值
成功不会返回
失败返回
用法示例
/*
* 将当前程序的子进程完全替换为 “ls” 命令对应的进程
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
pid = fork();
// if error
if (pid == 0)
{
execl("/bin/ls", "ls", "-l", NULL);
}
wait(NULL);
printf("hello world\n");
exit(0);
}
execlp(3) ;
execv(3) ;
exexvp(3):
11.更改进程组
setpgid(2) 更改进程组ID
#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);
getpgrp() 获取进程组ID
getsid() 获取会话ID
geptid() 获取当前进程ID
getppid() 获取父进程ID