迅为嵌入式Linux学习笔记4——进程
进程指的是正在运行的程序,是操作系统分配资源的最小单位。
进程ID
每个进程都有唯一的标识符,这个标识符就是进程ID,简称pid
进程间通信的方法
- 管道通信:分为有名管道和无名管道
- 信号通信:信号的发送,信号的接收,信号的处理
- IPC通信:共享内存,消息队列,信号量
- socket通信
进程的三种状态
- 就绪态
- 执行态
- 阻塞态
进程控制
创建进程
fork函数
头文件:
#include <unistd.h>
函数原型:
pid_t fork(void);
返回值:
fork函数有三种返回值:
- 在父进程中,fork返回新创建的子进程的PID
- 在子进程中,fork返回0
- 如果出现错误,fork返回一个负值
获得pid函数
pid_t getpid(void);//获得当前进程的PID
pid_t getppid(void);//获得当前进程父进程的PID
例程1
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t pid;
pid = fork();
if (pid < 0) {
printf("fork 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;
}
// 子进程是从fork函数开始往下执行的
// 父子进程的执行顺序是不一定的,可能父进程先执行,也可能子进程先执行
exec函数族
在Linux中并没有exec函数,而是有6个以exec开头的函数族,下面列举了exec函数族的6个函数成员的函数原型
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execv(const char *path, char *const argv[]);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
换核不换壳
- 当进程调用exec函数时,进程的用户空间代码和数据空间会完全被新的程序替代。
- .test 和 .data 都会替换成新的程序。
在Linux中使用exec函数族主要有以下两种情况:
- 当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何exec函数族让自己重生。
- 如果一个进程想执行另一个程序,那么它就可以调用fork函数新建一个进程,然后调用任何一个exec函数使子进程重生。
例程2
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t pid;
pid = fork();
if (pid < 0) {
printf("fork 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());
execl("/home/samba/linux/15/hello", "hello", NULL);// 第一个参数是程序路径,第二个参数是程序名,可变参数是程序所需参数,最后一个参数以NULL结尾
exit(1);// 假如程序执行失败,退出当前进程,返回1(成功返回0,失败返回1)
}
return 0;
}
ps和kill命令
ps命令:
ps命令可以列出系统中当前运行的那些进程。
- 命令格式:ps [参数]
- 命令功能:用来显示当前进程的状态
- 常用参数:aux(a:显示终端上的所有进程,u:显示归属用户、相关的内存使用情况,x:显示不关联终端的进程)
# 使用管道查找aux线程
ps aux | grep aux
kill命令:
kill命令用来杀死进程
- 举例:kill -9(SIGKILL) PID
- kill -l //查看有哪些信号
孤儿进程和僵尸进程
孤儿进程
父进程结束以后,子进程还未结束,这个子进程就叫做孤儿进程。孤儿进程会被init进程(PID号为1)领养,init进程成为孤儿进程的父进程。
例程3
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t pid;
pid = fork();
if (pid < 0) {
printf("fork error\n");
return -1;
}
// 父进程
if (pid > 0) {
printf("This is parent, parent pid is %d\n", getpid());
}
// 子进程
if (pid == 0) {
sleep(2);
printf("This is child, child pid is %d, parent pid is %d\n", getpid(), getppid());
}
return 0;
}
僵尸进程
子进程结束以后,父进程还在运行,但是父进程不去释放进程控制块,这个子进程就叫做僵尸进程。
例程4
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t pid;
pid = fork();
if (pid < 0) {
printf("fork error\n");
return -1;
}
// 父进程
if (pid > 0) {
printf("This is parent, parent pid is %d\n", getpid());
while(1);
}
// 子进程
if (pid == 0) {
printf("This is child, child pid is %d, parent pid is %d\n", getpid(), getppid());
exit(0);
}
return 0;
}
wait函数
头文件与函数原型:
#include <sys/wait.h>
pid_t wait(int *status);
// 返回值:成功返回回收的子进程的pid,失败返回-1
wait函数是一个阻塞函数,调用一次只能回收一个进程。每当父进程调用wait函数,就会去分析当前进程的某个子进程是否已经退出,如果找到了,就回去回收这个子进程的信息,并将它彻底销毁。如果没有找到,wait函数就会一直阻塞在这里,直到出现某子进程退出为止。
与wait函数的参数有关的两个宏定义:
WIFEXITED(status): 如果子进程正常退出,则该宏定义为真
WEXITSTATUS(status): 如果子进程正常退出,则该宏定义的值为子进程的退出值。
例程5
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
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));
}
}
// 子进程
if (pid == 0) {
sleep(2);
printf("This is child\n");
exit(6);
}
return 0;
}
守护进程
守护进程运行在后台,不跟任何控制终端关联。
守护进程创建有两个要求:
- 必须作为init进程的子进程
- 不跟控制终端交互
创建步骤:
- 使用fork函数创建一个新的进程,然后让父进程使用exit函数直接退出。(必须要的)
- 调用setsid函数。抛弃控制终端,退出当前进程组和会话。创建一个新的会话,并在新会话里创建新的进程组,调用函数的这个进程就会成为新会话新进程组里的唯一进程。(必须要的)
- 调用chdir函数,将当前的工作目录改成根目录,蹭强程序的健壮性。(不是必须要的)
- 重设我们umask文件掩码,增强程序的健壮性和灵活性。(不是必须要的)
- 关闭文件描述符,节省资源。(不是必须要的)
- 执行我们需要执行的代码。(必须要的)
例程6
#include <stdio.h>
#include <unistd.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);
// 步骤五:关闭标准输入,标准输出,标准出错
close(0);
close(1);
close(2);
// 步骤六:执行我们要执行的代码
while (1) {
}
}
}