在嵌入式Linux中创建四个进程,2个实时进程,2个非实时进程。每个进程中创建两个线程,在线程中调用系统服务来测试任务可中断睡眠、不可中断睡眠、暂停3种状态的切换,用一个进程来测试进程退出过程。
文章末尾附全部代码~
运行全过程:
创建过程
1、进程创建(主函数中)
(1)当pid==0时,说明此时代码运行是在子进程中,会退出该子进程,以免子进程继续创建子进程,父进程中的pid则为创建的子进程的pid号。
(2)父进程和子进程资源是共享的,包括:
代码段(共享相同的可执行代码)、全局变量和静态变量、打开的文件描述符(文件、管道、信号量等资源)、当前工作目录和进程根目录等
(3)不共享的资源:
进程ID、父进程的堆和子进程的堆(内容相同,但位于的内存空间不同)、栈、信号量和消息队列、打开文件的偏移量
2、子进程创建函数
(1)pid==0,表示只在fork()创建的子进程中执行,父进程会直接跳过该块
3、运行结果
如设想一样,一个父进程创建了三个子进程
4、若注释掉break,让子进程创建子进程
(1)父进程创建了3个子进程
(2)子进程0创建了两个子线程1和2:13150和13149
(3)子进程1创建了一个子线程2:13152
(3)子进程2没有再创建子线程,因为已达for循环末尾
子进程继承父进程的代码从父进程的当刻运行处开始。
5、子线程创建(主函数中):
(1)子进程和父进程的代码段是一样的,需要通过进程的pid号来判断此时是哪个进程在运行。
(2)只有运行到指定进程,才运行该进程相对应的代码。
(3)每个线程的线程体都是单独的,需要分开创建,不能只用单个函数。
6、子线程创建函数
每个线程都有专属于自己的线程体,parent_thread为该线程的线程执行函数
7、子线程体
创建线程后,线程立即开始执行线程体(线程函数)
8、调度策略
(1)普通调度策略:SCHED_OTHER,对应值为0
(2)实时调度策略,先进先出方式调度:SCHED_FIFO,对应值为1
(3)实时调度策略,时间片轮转方式调度:SCHED_RR,对应值为2
9、任务状态切换:S可中断睡眠、D不可中断睡眠、T暂停、Z退出(主函数中)
(1)S可中断睡眠:父进程中不断申请一个信号量,不断等待使进程切换为S可中断睡眠
(2)D不可中断睡眠:将子进程0通过vfork()使创建的子进程先运行(不可中断),进而使进程sleep(100)(进入睡眠)切换为D不可中断睡眠状态
(3)T暂停状态:通过execvp函数给子进程1发送SIGSTOP信号(命令:kill -STOP 进程的pid号)
(4)Z退出状态:通过exit(1)使子进程退出,状态切换为Z退出状态
(5)终端状态查询ps-aux:
相关函数
1、进程创建相关函数:
创建子进程fork():
(1)如果子进程创建成功,在父进程中返回子进程的PID,而在子进程中返回0。
(2)如果子进程创建失败,则在父进程中返回 -1。
getpid():
获取当前进程的PID号
getppid():
获取当前进程父进程的PID号
2、进程调度策略相关函数:
(1)sched_get_priority_max()函数:参数为传入调度策略对应的宏定义。函数为可以获取实时进程优先级的最大值。
(2)sched_setscheduler()函数:第一个参数为需要改变调度策略的目标线程,第二个参数为改变的调度策略,第三个参数为一个结构指针类型,用以指定调度优先级。
(3)sched_getscheduler()函数:参数为需要返回调度策略的进程pid。
3、线程创建相关函数
(1)pthread_create():
创建线程。第一个参数指向线程标识符的指针,第二个参数设置线程的属性,第三个参数是线程运行函数的起始地址,第四个参数为运行函数的参数。
(2)pthread_join():
等待一个指定线程终止,并获取该线程的退出状态。
第一个参数为要等待的线程tid,第二个参数可设为NULL,若不关心退出状态。
(3)gettid():
获取当前线程的tid号(若为进程的主线程,tid号与pid号一致)
(4)pthread_exit():
退出线程,不关心退出值则参数设置为(NULL)
4、任务状态切换相关函数:
(1)可中断睡眠S
sem_open():
创建信号量。第一个参数为信号量的外部名字,第二个参数指定创建或打开信号量的方式和标志位,第三个参数为权限位,第四个参数为信号灯的初值。
sem_wait():
等待(阻塞)信号量,对信号量进行P操作(等待、减小)
(2)不可中断睡眠D
vfork():
(1)创建一个新的进程。
(2)保证子进程先运行,在它调用 exec(进程替换) 或 exit(退出进程) 之后父进程才可能被调度运行。(让程序不可中断)
(3)子进程中返回0,父进程中返回子进程的PID。
sleep():让当前进程挂起一段时间(进入睡眠)
vfork()与fork()的区别
(1)vfork()相比fork()创建子进程时不会复制父进程的地址空间(包括代码段、数据段、堆和栈),子进程与父进程共享地址空间。
(2)在调用exec函数族之前,它与父进程共享地址空间,因此对共享的数据进行修改会影响到父进程中相同地址空间中的数据。
(3)vfork()相比fork()通常用于立即执行一个新的程序。
(3)暂停状态T:
sprintf():
第一个参数为目的字符串,第二个参数为格式化字符串。该应用中将整数打印到字符串中
execvp():
第一个参数为要运行的命令名称,第二个参数( argv )表示command 的参数列表,是一个char*字符串数组。
(4)退出状态Z:
exit():参数0表示正常退出,非0表示异常退出。
5、程序运行:
sudo gcc -pthread Task2.c -o Task2
(1)sudo:超级用户权限
(2)gcc: GNU编译器集合(GNU Compiler Collection)的命令,用于编译C语言程序
(3)-pthread:告诉编译器链接 POSIX 线程库(pthread),以支持多线程程序。
(4)Task2.c:要编译的源代码文件的名称。
(5)-o Task2:指定编译输出的可执行文件,-o后面的参数是输出文件的名称。
sudo ./Task2
因为需要访问系统级资源,需要超级用户权限。
代码
/*******************************************1、引入函数库***********************************************/
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <string.h>
#include <errno.h>
#include<assert.h>
#include<pthread.h>
#include<unistd.h>
//pthread用户库内没有gettid(),所以需要下面的头文件以及gettid()函数定义
#include<sys/syscall.h>
//信号量函数库
#include <semaphore.h>
#include <fcntl.h>
#include <errno.h>
/*******************************************2、函数声明***********************************************/
//进程创建函数
void creat_child_process(); // 创建子进程
void child1_Process_status_change(); // 子进程1状态更改为实时进程
void child2_Process_status_change(); // 子进程2状态更改为实时进程
//线程创建函数
pid_t gettid(); // 线程号获取函数
void *parent_thread(); // 父进程的线程体
void *child_pid0_thread(); // 子进程0的线程体
void *child_pid1_thread(); // 子进程1的线程体
void *child_pid2_thread(); // 子进程2的线程体
void create_parent_thread(); // 创建父进程的线程
void create_child_pid0_thread(); // 创建子进程0的线程
void create_child_pid1_thread(); // 创建子进程1的线程
void create_child_pid2_thread(); // 创建子进程2的线程
/*******************************************3、全局变量定义***********************************************/
//全局变量定义
pid_t pid; // pid_t被定义为整数类型,用于存储进程ID
int child_pid[3]; // 子进程PID
int i = 0;
int parent_pid = 0; // 父进程PID
int rc,old_scheduler_policy; // 进程状态查看
struct sched_param my_params; // 用以指定目标线程的优先级
char str[10]; // 用以将整形修改为字符型
char *argv[] = {"kill","-STOP","pid",NULL}; // 用以向终端发送命令
sem_t *Sem_TASK_INTERRUPTIBLE; // 定义信号量
/*********************************************4、主函数***********************************************/
int main(void)
{
// 创建信号量
Sem_TASK_INTERRUPTIBLE = sem_open("mysem", O_CREAT | O_RDWR , 0666, 0); // 创建名为"Sem_TASK_INTERRUPTIBLE"的信号量
if (Sem_TASK_INTERRUPTIBLE == SEM_FAILED)
{
printf("errno=%d\n", errno);
return -1; // 创建失败则返回-1退出
}
// 获取进程的PID号,子进程为父进程号+1
parent_pid = getpid();
child_pid[0] = parent_pid+1;
child_pid[1] = parent_pid+2;
child_pid[2] = parent_pid+3;
old_scheduler_policy=sched_getscheduler(0);
printf("**************************************************\n");
printf("进程调度策略:\n");
printf("SCHED_OTHER = %d SCHED_FIFO =%d SCHED_RR=%d \n",SCHED_OTHER,SCHED_FIFO,SCHED_RR);
printf("*******************进程系统启动*******************\n");
printf("I'm start parent, pid: %d, ppid: %d,scheduler = %d\n", getpid(), getppid(),old_scheduler_policy);
// 创建三个子进程
for(;i < 3;i++)
{
creat_child_process(i); // 调用子进程创建函数
if (pid==0) // 为子进程时
{
break; // 退出子进程的整个for循环,否则子进程又会继续创建子进程
}
}
sleep(1); // 等待子进程创建完成
/*******************************************更改进程状态***********************************************/
// 更改子进程1为实时进程
if (child_pid[1]==getpid())
{
child1_Process_status_change();
}
sleep(1);
// 更改子进程2为实时进程
if (child_pid[2]==getpid())
{
child2_Process_status_change();
}
sleep(1); //等待子进程状态更改完成
/*******************************************创建线程***********************************************/
// 在父进程中创建线程
if (parent_pid==getpid()) // 当处于父进程parent时
{
printf("creat thread:\n");
printf("parent_pid: 主线程tid = %d\n",gettid()); // 串口输出主线程tid
create_parent_thread(); // 调用子线程创建函数
}
sleep(1);
// 在三个子进程中创建线程
if (child_pid[0]==getpid()) // 当处于子进程child[0]时
{
printf("child_pid[0]: 主线程tid = %d\n",gettid()); // 串口输出主线程tid
create_child_pid0_thread(); // 调用子线程创建函数
}
sleep(1);
if (child_pid[1]==getpid()) // 当处于子进程child[1]时
{
printf("child_pid[1]: 主线程tid = %d\n",gettid()); // 串口输出主线程tid
create_child_pid1_thread(); // 调用子线程创建函数
}
sleep(1);
if (child_pid[2]==getpid()) // 当处于子进程child[2]时
{
printf("child_pid[2]: 主线程tid = %d\n",gettid()); // 串口输出主线程tid
create_child_pid2_thread(); // 调用子线程创建函数
}
sleep(5);
/*******************************************进程状态切换***********************************************/
// 将父进程状态切换为:S可中断睡眠
if (parent_pid==getpid()) // 当处于父进程parent时
{
printf("Task status switching:\n");
printf("将父进程 [%d]的进程状态切换为:S可中断睡眠\n",gettid());
sem_wait(Sem_TASK_INTERRUPTIBLE); // 阻塞等待读信号量,使进程状态切换为S可中断睡眠
}
sleep(1);
// 将子进程0状态切换为:D不可中断睡眠
if (child_pid[0]==getpid()) // 当处于子进程child[0]时
{
printf("将子进程0[%d]的进程状态切换为:D不可中断睡眠\n",gettid());
if(!vfork()) // 让子进程先运行
{
sleep(100); // 睡眠100s
}
}
sleep(1);
// 将子进程1状态切换为:T暂停状态
if (child_pid[1]==getpid()) // 当处于子进程child[1]时
{
printf("将子进程1[%d]的进程状态切换为:T暂停状态\n",gettid());
sprintf(str,"%d",child_pid[1]); // 将PID号从int型转化为字符串char型
argv[2]=str; // 将需要切换状态的进程PID号写入
if(execvp("kill",argv) == -1) // 在终端中发送命令
{
printf("execvp failed!\n");
}
}
sleep(1);
// 将子进程2退出:Z退出状态
if (child_pid[2]==getpid()) // 当处于子进程child[2]时
{
printf("将子进程2[%d]退出 :Z退出状态\n",gettid());
exit(0); // 退出进程
}
if (parent_pid==getpid()) // 当处于子进程child[0]时
{
if(execlp("ps","ps","-aux",NULL) == -1)
{
printf("execlp failed!\n");
}
printf("after execlp*****\n");
}
sleep(100);
return 0;
}
/*******************************************5、函数定义*********************************************/
/********************************************创建子进程**********************************************/
/*************************************************************************************************************************
函 数 : creat_child_process(int i)
函数名 : 创建子进程
作 用 : 创建父进程中的子进程child[0]、child[1]、child[2]
*************************************************************************************************************************/
void creat_child_process(int i)
{
pid = fork(); // 子进程中返回子进程的PID号给变量pid
if(pid == 0) // 该块只在该子进程中执行,写到外面会也到父进程中执行
{
old_scheduler_policy=sched_getscheduler(0); // 参数0:取得当前进程的调度策略
printf("I'm child_pid[%d], PID: %d, PPID: %d,scheduler = %d\n", i,getpid(),getppid(),old_scheduler_policy);
}
else if(pid<0) // 创建失败fork()返回-1
{
printf("fork error!");
exit(1); // 到主调程序,表示异常退出
}
}
/**********************************************进程状态更改************************************************/
/*************************************************************************************************************************
函 数 : ×××××_Process_status_change(void)
函数名 : 进程状态更改
作 用 : 更改子进程child[1]、child[2]为实时进程
*************************************************************************************************************************/
// 更改子进程1为实时进程
void child1_Process_status_change()
{
printf("change process status:\n");
my_params.sched_priority=sched_get_priority_max(SCHED_FIFO); // 设置进程优先级
rc=sched_setscheduler(child_pid[1],SCHED_FIFO,&my_params); // 更改进程状态为SCHED_FIFO
if(rc<0) // 如果进程调度失败,返回1
{
perror("set SCHED_FIFO error");
exit(1);
}
old_scheduler_policy=sched_getscheduler(child_pid[1]); // 获取进程的调度策略
printf("the child_pid[1]:%d scheduler = %d \n",child_pid[1],old_scheduler_policy); // 串口打印进程状态
}
// 更改子进程2为实时进程
void child2_Process_status_change()
{
my_params.sched_priority=sched_get_priority_max(SCHED_RR); // 设置进程优先级
rc=sched_setscheduler(child_pid[2],SCHED_RR,&my_params); // 更改进程状态为SCHED_RR
if(rc<0) // 如果进程调度失败,返回1
{
perror("set SCHED_FIFO error");
exit(1);
}
old_scheduler_policy=sched_getscheduler(child_pid[2]); // 获取进程的调度策略
printf("the child_pid[2]:%d scheduler = %d \n",child_pid[2],old_scheduler_policy); // 串口打印进程状态
}
/********************************************线程创建**********************************************/
// 线程号获取函数
pid_t gettid()
{
return syscall(SYS_gettid);
}
/*************************************************************************************************************************
函 数 : ×××××_thread(void)
函数名 : 子线程的函数体
作 用 : 定义子线程中执行的代码
*************************************************************************************************************************/
// 父进程子线程体
void *parent_thread()
{
printf("parent_pid: 子线程tid = %d\n",gettid()); // 获取所属线程号
pthread_exit(NULL); // 线程退出,不关心退出值则参数为:NULL
}
// 子进程0子线程体
void *child_pid0_thread()
{
printf("child_pid[0]: 子线程tid = %d\n",gettid()); // 获取所属线程号
pthread_exit(NULL); // 线程退出,不关心退出值则参数为:NULLwhile(1)
}
// 子进程1子线程体
void *child_pid1_thread(void *arg)
{
printf("child_pid[1]: 子线程tid = %d\n",gettid()); // 获取所属线程号
pthread_exit(NULL); // 线程退出,不关心退出值则参数为:NULL
}
// 子进程2子线程体
void *child_pid2_thread()
{
printf("child_pid[2]: 子线程tid = %d\n",gettid()); // 获取所属线程号
pthread_exit(NULL); // 线程退出,不关心退出值则参数为:NULL
}
/*************************************************************************************************************************
函 数 : create_×××××_thread(void)
函数名 : 创建子线程
作 用 : 创建进程parent、child[0],child[1],child[2]的子线程
*************************************************************************************************************************/
// 创建父进程的子线程
void create_parent_thread()
{
pthread_t id; // 定义线程的数据结构
int i = 0;
pthread_create(&id,NULL,parent_thread,(void*)&i); // 创建线程,新建线程执行的函数为parent_thread
pthread_join(id,NULL); // 等待线程结束
}
// 创建子进程0的子线程
void create_child_pid0_thread()
{
pthread_t id; // 定义线程的数据结构
int i = 0;
pthread_create(&id,NULL,child_pid0_thread,(void*)&i); // 创建线程,新建线程执行的函数为child_pid0_thread
pthread_join(id,NULL); // 等待线程结束
}
// 创建子进程1的子线程
void create_child_pid1_thread()
{
pthread_t id; // 定义线程的数据结构
int i = 0;
pthread_create(&id,NULL,child_pid1_thread,(void*)&i); // 创建线程,新建线程执行的函数为child_pid1_thread
pthread_join(id,NULL); // 等待线程结束
}
// 创建子进程2的子线程
void create_child_pid2_thread()
{
pthread_t id; // 定义线程的数据结构
int i = 0;
pthread_create(&id,NULL,child_pid2_thread,(void*)&i); // 创建线程,新建线程执行的函数为child_pid2_thread
pthread_join(id,NULL); // 等待线程结束
}