目录
进程基础
进程指正在运行的程序,如下图示,是资源分配的最小单位,可以通过“ps
”或“
top
”等命令查看正在运行的进程,线程是系统的最小调度单位,一个进程可以拥有多个线程,同一进程里的线程可以共享此进程的同一资源。
每个进程都有一个唯一的标识符,既进程 ID
,简称
pid
进程间的通信的几种方法?
- 管道通信:有名管道,无名管道
- 信号通信:信号的发送,信号的接受,信号的处理
- IPC 通信:共享内存,消息队列,信号灯
- Socket 通信
进程的三种基本状态以及转换
:
1.进程创建
所有的进程都是由其他进程创建(除了 pid
为
0
号的
idle
进程),
pid
号为
1
的
init
进程是系统启动后运行的第一个进程,是所有进程的父进程,init
进程会初始化一部分系统服务,创建其他进程。
创建新进程的那个进程称为父进程,新进程称为子进程,父进程和子进程拥有相同的代码段数据段,有各自独立的地址空间。采用写时拷贝技术,即创建的新进程不会立即把父进程的资源空间复制过来,只有在修改时才会复制资源,另外父进程挂起的信号和文件锁不会被子进程继承。
子进程结束后,它的父进程要回收它的资源,否则就会成为僵尸进程 。
如果父进程先结束,子进程会被 init
进程收养,称为孤儿进程。
创建进程常用函数如下定义:
|
头文件
|
#include <sys/types.h>
#include <unistd.h>
|
|
函数
|
pid_t getpid(void);
|
|
返回值
|
PID
号
|
|
功能
|
获取此进程
PID
|
|
头文件
|
#include <sys/types.h>
#include <unistd.h>
|
|
函数
|
pid_t getppid(void);
|
|
返回值
|
PID
号
|
|
功能
|
获取父进程
PID
|
|
头文件
|
#include <unistd.h>
|
|
函数
|
pid_t fork(void);
|
|
返回值
|
调用成功父进程返回子进程号,子进程返回
0
,失败返回
-1
。
|
|
功能
|
系统调用,创建一个进程
|
实验代码
在程序中,父进程中创建一个子进程。
#include <stdio.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
pid = fork();
if (pid < 0)
{
printf("fork is error \n");
return -1;
}
//父进程
if (pid > 0)
{
printf("This is parent,parent pid is %d\n", getpid());
}
//子进程
if (pid == 0)
{
printf("This is child,child pid is %d,parent pid is %d\n", getpid(), getppid());
}
return 0;
}
在 Ubuntu
上编译运行,打印进程号如下图所示:
2.exec 函数族
用 fork
函数创建子进程后,子进程往往要调用一种
exec
函数以执行另一个程序,该子进程被新的程序替换,改变地址空间,进程映像和一些属性,但是 pid
号不变。
exec
函数详解如下表所示:
|
函数
|
int execve(const char *filename, char *const argv[], char *const envp[]);
|
|
头文件
|
#include <unistd.h>
|
|
参数
filename
|
路径名,表示载入进程空间的新程序路径。
|
|
参数
argv[]
|
命令行参数,
argv[0]
为命令名
|
|
参数
envp[]
|
新程序的环境变量
|
|
返回值
|
成功时不会返回,使用时不用检查返回值,可通过
errno
检查。
|
以下函数都是根据 execve 实现:
- int execl(const char *path, const char *arg, .../* (char *) NULL */);
- int execlp(const char *file, const char *arg, .../* (char *) NULL */);
- int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
- int execv(const char *path, char *const argv[]);
- int execvp(const char *file, char *const argv[]);
- int execvpe(const char *file, char *const argv[],char *const envp[]);
实验代码:
创建子进程,子进程使用 execl
打印
hello world
。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
int i=0;
pid_t pid;
pid = fork();
if (pid < 0)
{
printf("fork is error \n");
return -1;
}
//父进程
if (pid > 0)
{
printf("This is parent,parent pid is %d\n", getpid());
}
//子进程
if (pid == 0)
{
printf("This is child,child pid is %d\n", getpid(), getppid());
//改为自己的路径
execl("/home/samba/linux/15/hello","hello",NULL);
exit(1);
}
i++;
printf("i is %d\n",i);
return 0;
}
编写 hello.c 文件,内容如下所示:
#include <stdio.h>
#include <unistd.h>
int main(void)
{
printf("hello world\n");
return 0;
}
编译 hello.c 如下图所示:

执行结果:

3.ps 和 kill 命令
ps
命令
:
ps
命令可以列出系统中当前运行的那些进程。
- 命令格式:ps [参数]
- 命令功能:用来显示当前进程的状态
- 常用参数: aux
kill
命令
:
kill
命令用来杀死进程
- 举例:kill -9(SIGKILL) PID
进程的状态
- D: 无法中断的休眠状态 (通常 IO 的进程)
- R: 正在执行中
- S: 静止状态
- T: 暂停执行
- Z: 不存在但暂时无法消除
- W: 没有足够的记忆体分页可分配
- <: 高优先序的行程
- N: 低优先序的行程
- L: 有记忆体分页分配并锁在记忆体内 (实时系统或捱 A I/O)
如下所示,使用命令“
ps aux | grep a.out
”查找到
./a.out
的进程号为
3179
,然后输入“
kill -9 3179
”结束此进程,
完成后再次输入查找命令“
ps aux | grep a.out
”,没有发现
3179
号进程。
4.孤儿进程与僵尸进程
- 孤儿进程:父进程结束以后,子进程还未结束,这个子进程就叫做孤儿进程。
- 僵尸进程:子进程结束以后,父进程还在运行,但是父进程不去释放进程控制块,这个子进程就叫做僵尸进程。
实验代码
在程序中,创建的子进程变为孤儿进程。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
int i=0;
pid_t pid;
// 创建一个子进程
pid = fork();
if (pid < 0)
{
printf("fork is error \n");
return -1;
}
//父进程 让父进程先结束,并打印进程 PID
if (pid > 0)
{
printf("pid is %d\n", getpid());
}
//子进程,让子进程等待一会,让父进程先结束,并打印子进程的父进程的 pid
if (pid == 0)
{
sleep(2);
printf("parent pid is %d\n", getppid());
}
return 0;
}
编译运行,如下图所示:
如上图所示,子进程中打印的父进程的进程号和父进程的进程号是不一样的,说明创建的子进程变成了孤儿进程,此进程被系统的 init 进程
"
领养
"
了,如下图所示:
实验代码
在程序中,父进程变为僵尸进程。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
int i=0;
pid_t pid;
// 创建一个子进程
pid = fork();
if (pid < 0)
{
printf("fork is error \n");
return -1;
}
//父进程,让子进程先结束,父进程在while循环
if (pid > 0)
{
while(1);
}
//子进程,让子进程先结束
if (pid == 0)
{
printf("This is child\n");
exit(0);
}
return 0;
}
编译运行,如下图所示:
再打开另一个终端,查看此进程如下图所示为僵尸进程。
5.wait()函数
wait()函数一般用在父进程中等待回收子进程的资源,而防止僵尸进程的产生。
|
函数
|
pid_t wait(int *status)
|
|
头文件
|
#include <sys/wait.h>
|
|
返回值
|
成功返回回收的子进程的
pid
,失败返回
-1
|
与 wait 函数的参数有关的俩个宏定义:
- WIFEXITED(status):如果子进程正常退出,则该宏定义为真
- WEXITSTATUS(status):如果子进程正常退出,则该宏定义的值为子进程的退出值。
实验代码
在程序中,使用 wait
()函数,防止僵尸进程的产生。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
pid = fork();
if(pid < 0){
printf("error\n");
}
if(pid > 0){
int status;
wait(&status);
if(WIFEXITED(status)==1){
printf("return value is %d\n",WEXITSTATUS(status));
}
while(1);
}
if(pid == 0){
sleep(2);
printf("This is child\n");
exit(6);
}
return 0;
}
编译运行,如下图所示:
6.守护进程
1.什么是守护进程?
守护进程(daemon)
是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。另一些只在需要的时候才启动,完成任务后就自动结束。
用户使守护进程独立于所有终端是因为,在守护进程从一个终端启动的情况下,这同一个终端可能被其他的用户使用。例如,用户从一个终端启动守护进程后退出,然后另外一个人也登录到这个终端。用户不希望后者在使用该终端的过程中,接收到守护进程的任何错误信息。同样,由终端键入的任何信号(
例如中断信号)
也不应该影响先前在该终端启动的任何守护进程的运行。虽然让服务器后台运行很容易
(
只要
shell命令行以&
结尾即可
)
,但用户还应该做些工作,让程序本身能够自动进入后台,且不依赖于任何终端。
守护进程没有控制终端,因此当某些情况发生时,不管是一般的报告性信息,还是需由管理员处理的紧急信息,都需要以某种方式输出。Syslog
函数就是输出这些信息的标准方法,它把信息发送给
syslogd
守护进程。
2.怎么创建一个守护进程?
有个基本要求:
- 必须作为我们 init 进程的子进程
- 不跟控制终端交互。
步骤:
- 使用 fork 函数创建一个新的进程,然后让父进程使用 exit 函数直接退出(必须要的)
- 调用 setsid 函数。(必须要的)
- 调用 chdir 函数,将当前的工作目录改成根目录,增强程序的健壮性。(不是必须要的)
- 重设我们 umask 文件掩码,增强程序的健壮性和灵活性(不是必须要的)
- 关闭文件描述符,节省资源(不是必须要的)
- 执行我们需要执行的代码(必须要的)
实验代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(void)
{
pid_t pid;
// 步骤一:创建一个新的进程
pid = fork();
//父进程直接退出
if (pid > 0)
{
exit(0);
}
if (pid == 0)
{
// 步骤二:调用 setsid 函数摆脱控制终端
setsid();
// 步骤三:更改工作目录
chdir("/");
// 步骤四:重新设置 umask 文件源码
umask(0);
// 步骤五:0 1 2 三个文件描述符
close(1);
close(2);
close(3);
// 步骤六:执行我们要执行的代码
while (1)
{
}
}
return 0;
}
运行测试,如下图所示:

1044

被折叠的 条评论
为什么被折叠?



