目录
概念
程序:编译好的可执行文件,存放在磁盘上的指令和数据的有序集合,是静态的,没有任何执行的概念。
进程:独立的可调度的任务,是执行一个程序所分配资源的总称,是程序的一次执行过程,是动态的,包括创建、调度、执行和消亡。
特点
- 系统会为每一个进程分配从0-4G的虚拟空间,0-3G(用户空间)是每个进程所独有的,3-4G(内核空间)是所有进程共有的。
- CPU调度进程时会给进程分配时间片(几毫秒~几十毫秒),当时间片用完后,CPU再进行其他进程的调度,实现进程的轮转,从而实现多任务的操作。
进程段
Linux中的进程包含三个段:
- “数据段”存放的是全局变量、常数以及动态数据分配的数据空间(如malloc()函数取得的空间)等
- “正文段”存放的是程序中的代码
- “堆栈区”存放的是函数的返回地址、函数的参数以及程序中的局部变量
进程分类
- 交互进程:该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。该类进程经常与用户进行交互,需要等待用户的输入,当接收到用户的输入后,该类进程会立刻响应,典型的交互式进程有:shell命令进程、文本编辑等。
- 批处理文件:该类进程不属于某个终端,它被提交到一个队列中以便执行。
- 守护进程:该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。
进程状态
运行态(TASK_RUNNING):R
指正在被CPU运行或者就绪的状态。这样的进程被称为running进程。
睡眠态(等待态):
可中断睡眠态(TASK_INTERRUPPTIBLE)S:
处于等待状态中的进程,一旦被该进程等待的资源被释放,那么该进程就会进入运行状态。
不可中断睡眠态(TASK_UNINTERRUPTIBLE)D:
该进程状态只能用wake_up() 函数才能唤醒。
暂停态(TASK_STOPPED)T:(又叫停止态)
当进程收到信号SIGSTOP、SIGSTP、SIGTTIN或SIGTTOU时,就会进入暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。
死亡态:X
进程结束。
僵尸态(TASK_ZOMBIE):Z
当进程已经终止运行,但还占用系统资源,要避免僵尸态的产生。
进程状态切换图
进程创建后,进程进入就绪状态,当CPU调度到此进程时进入运行态,当时间片用完时,此进程就会进入就绪态,如果此进程正在执行一些IO操作(阻塞操作)会进入阻塞态,完成IO操作(阻塞结束)后又可进入就绪态,等待CPU的调度,当进程运行结束即进入结束态。
函数接口
创建子进程
fork
格式:pid_t fork(void);
功能:创建子进程
返回值:
成功:在父进程中:返回子进程的进程号 >0
在子进程中:返回值为0
失败:-1并设置errno
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
int a = 0;
printf("hello\n");
pid_t pid = fork();
printf("world\n");
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
a = 10;
printf("in clild a:%d\n", a); //10
//while(1);
sleep(1);
}
else
{
printf("in parent a:%d\n", a); //0
wait(NULL);//阻塞回收子进程资源
printf("gggggg\n");
}
return 0;
}
/*
1.fork之前的代码被复制,但是不会在重新再执行一遍
fork之后代码被复制,并且会分别被执行一遍 */
函数特点:
- 子进程几乎拷贝了父进程的全部内容。包括代码、数据、系统数据段中的pc值、栈中的数据、父进程中打开的文件等;但是他们的PID、PPID是不一样的。
- 父子进程有独立的地址空间,互不影响;当在相应的进程中改变全局变量、静态变量,都互相不影响。
- 若父进程先结束,,子进程成为孤儿进程,被init进程收养,子进程变成后台进程。
- 若子进程先结束,父进程如果没有及时回收,子进程变成僵尸进程(要避免僵尸进程)
总结:
- fork之前的代码会被复制,但是不会再执行一次,fork之后的代码会被复制,并且分别执行一遍
- fork两个进程相互独立,子进程几乎拷贝了父进程的所有代码,但内存空间独立
- fork之前打开的文件,fork之后拿到相同的文件描述符,操作同一个文件指针
回收进程资源
wait
格式:pid_t wait(int *status);
功能:回收子进程资源(阻塞)
参数:status:子进程退出状态,不接受子进程状态设为NULL
返回值:
成功:回收的子进程的进程号
失败:-1
waitpid
pid_t waitpid(pid_t pid, int *status, int options);
功能:回收子进程资源
参数:
pid:>0 指定子进程进程号
=-1 任意子进程
=0 等待其组ID等于调用进程的组ID的任一子进程
<-1 等待其组ID等于pid的绝对值的任一子进程
status:子进程退出状态
options:0:阻塞
WNOHANG:非阻塞
返回值:
正常:结束的子进程的进程号
当使用选项WNOHANG且没有子进程结束时:0
出错:-1
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
//创建父子进程
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
printf("hello\n");
sleep(1);
}
else
{
printf("world\n");
//wait(NULL);//阻塞回收任意子进程的资源
//waitpid(-1,NULL,0);//等同于wait(NULL)
//waitpid(-1,NULL,WNOHANG);//不阻塞
waitpid(pid, NULL, 0);//阻塞回收指定子进程
printf("ni hao\n");
}
return 0;
}
结束进程
exit
格式:void exit(int status);
功能:结束进程,刷新缓存
参数:退出的状态
不返回
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
void fun(int a, int b)
{
printf("china\n");
printf("a+b=%d", a + b);
//_exit(0);//不刷新缓冲不会打印a+b,不打印hhhhh
exit(0);//刷新缓冲区,不打印hhhhh
return;
}
int main(int argc, char const *argv[])
{
//创建父子进程
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
printf("hello\n");
fun(3, 4);
sleep(1);
printf("hahahahaha\n");
}
else
{
printf("world\n");
waitpid(pid, NULL, 0); //阻塞回收指定子进程
printf("ni hao\n");
}
return 0;
}
_exit
void _exit(int status);
功能:结束进程,不刷新缓存
参数:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。
返回值:
通常0表示正常结束;
其他的数值表示出现了错误,进程非正常结束
return 和 exit 的区别:
exit:是一个函数,不管在子函数还是主函数,都可以结束进程(进程的退出)
return:是关键字,当子函数中有return时返回到函数调用位置,并不结束进程(函数的退出)
获取进程号
getpid
格式:pid_t getpid(void);
功能:获取当前进程的进程号
getppid
pid_t getppid(void);
功能:获取当前进程的父进程号
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0) //子进程
{
printf("inchild pid:%d,ppid:%d\n", \
getpid(),getppid());
}
else //父进程
{
printf("inparent pid:%d cpid:%d\n",\
getpid(),pid);
}
return 0;
}
守护进程
特点
守护进程是后台进程;生命周期比较长,从系统启动时开启,系统关闭时结束;它是脱离控制终端且周期执行的进程。
步骤
1. 创建子进程,父进程退出
让子进程变成孤儿进程,成为后台进程;fork()
2. 在子进程中创建新会话
让子进程成为会话组组长,为了让子进程完全脱离终端;setsid()
3. 改变进程运行路径为根目录
原因是进程运行的路径不能被删除或卸载;chdir("/")
4. 重设文件权限掩码
目的:增大进程创建文件时权限,提高灵活性;umask(0)
5. 关闭文件描述符
将不需要的文件关闭;close()
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc, char const *argv[])
{
//创建子进程,父进程退出
//让子进程变成孤儿进程,成为后台进程;
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
//2. 在子进程中创建新会话
//让子进程成为会话组组长,为了让子进程完全脱离终端;
setsid();
//3.改变进程运行路径为根目录
//原因进程运行的路径不能被删除或卸载;
chdir("/");
//4.重设文件权限掩码
umask(0);
//5.关闭文件描述符
for (int i = 0; i < 3; i++)
close(i);
while (1)
; //让子进程不结束
}
else
exit(0);
return 0;
}
练习1:
创建一个守护进程,循环间隔1s向文件写入一串“hello”字符
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
//1.创建父子进程,父进程退出
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
//2.在子进程中创建新会话,让子进程成为会话组组长
setsid();
//3.改变运行路径为根目录
chdir("/");
//4.重设文件掩码
umask(0);
//5.关闭文件描述符
for (int i = 0; i < 2; i++)
close(i);
//打开文件
int fd = open("/tmp/info.log", O_RDWR | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
{
perror("open err");
return -1;
}
while (1)
{
write(fd, "hello\n", 6);
sleep(1);
}
}
else
{
exit(0);
}
return 0;
}
练习2:
通过父子进程完成对文件的拷贝(cp),父进程要求从文件开始到文件的一半开始拷贝,子进程拷贝从文件的一半到文件的末尾。要求用文件IO命令行传参
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
ssize_t s = 0;
char buf[32] = {0};
//1.打开文件
int src = open(argv[1], O_RDONLY);
if (src < 0)
{
perror("src err");
return -1;
}
int dest = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (dest < 0)
{
perror("dest err");
return -1;
}
off_t off = lseek(src, 0, SEEK_END) / 2;
//2.创建父子进程
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
//子进程复制后半部分
//移动文件指针
lseek(src, off, SEEK_SET);
lseek(dest, off, SEEK_SET);
//循环读写
while ((s = read(src, buf, 32)) != 0)
write(dest, buf, s);
}
else
{
//父进程复制前半部分
wait(NULL);
//移动指针
lseek(src, 0, SEEK_SET);
lseek(dest, 0, SEEK_SET);
//循环读写
while (off > 0)
{
if (off > 32)
s = read(src, buf, 32);
else
s = read(src, buf, off);
write(dest, buf, s);
off -= s; //剩下要读的字符个数
}
}
close(src);
close(dest);
return 0;
}