嵌入式学习-IO进程-Day04
进程的函数接口
fork和Vfork
回收进程资源
wait
waitpid
退出进程
获取进程号(getpid,getppid)
守护进程
守护进程的特点
创建步骤
exec函数族
线程
概念
线程和进程的区别
线程资源
线程函数接口
创建线程(pthread_create)
退出线程
进程的函数接口
fork和Vfork
1.fork(): 子进程拷贝父进程的数据段,代码段
vfork(): 子进程与父进程共享数据段
2.fork(): 父子进程执行次序不确定
vfork(): 保证子进程先运行,在调用exec()或exit()之前,与父进程数据共享,在exec()或exit()调用之后,父进程才能运行
总结:fork: 更通用,适用于需要创建一个完全独立的子进程的场景,vfork: 更适用于子进程立即执行 exec() 覆盖其自身的场景,因为它避免了不必要的地址空间复制,提高了性能。
回收进程资源
wait
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
功能:回收子进程资源(阻塞)
参数:status:子进程退出状态,不接受子进程状态设为NULL
返回值:成功:回收的子进程的进程号
失败:-1
用法:wait(NULL);
示例代码:
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
wait(NULL) == waitpid(-1,NULL,0) //阻塞回收任意子进程资源
waitpid(pid,NULL,0); //阻塞回收指定进程
waitpid(-1,NULL,WNOHANG);不阻塞回收任意子进程
非阻塞示例代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.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("in child\n");
sleep(5); // 睡眠是为了看到现象
printf("子进程退出\n");
}
else
{
printf("尝试回收我的子进程资源\n");
while (1)
{
pid_t result = waitpid(pid, NULL, WNOHANG);
if (result == 0)
{
printf("子进程还在运行.....\n");
sleep(1);
}
else
{
printf("子进程结束\n");
break;
}
}
}
return 0;
}
退出进程
void exit(int status);
功能:结束进程,刷新缓存
参数:退出的状态
不返回。
void _exit(int status);
功能:结束进程,不刷新缓存
参数:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。
通常0表示正常结束;
其他的数值表示出现了错误,进程非正常结束
exit和return的区别
exit:函数,结束进程。
return:关键字,结束函数。
exit和_exit的区别
exit():刷新缓存区,关闭所有打开的文件指针
_exit():立即终止进程,不会执行上述操作
示例代码:
获取进程号(getpid,getppid)
pid_t getpid(void);
功能:获取当前进程的进程号
pid_t getppid(void);
功能:获取当前进程的父进程号
练习:创建子进程,在父子进程中分别获取他们的进程号和父进程号,获取结束,回收子进程资源,退出进程。
守护进程
守护进程的特点
守护进程是后台进程;生命周期比较长,从系统启动时开启,系统关闭时结束;它是脱离控制终端且周期执行的进程。
创建步骤
1.创建子进程,父进程退出
子进程成为孤儿进程,成为后台进程(fork)
2.在子进程当中创建会话
让子进程成为会话组组长,为了让子进程完全脱离终端;setsid()
3.改变进程的运行路径为根目录
原因进程运行的路径不能被删除或卸载;chdir("/")
4.重设文件权限掩码
增大进程创建文件时权限,提高灵活性;umask(0)
5.关闭文件描述符
将不需要的文件关闭;close();
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
//在子进程中创建新的会话
setsid();
//改变路径到根目录
chdir("/");
//改变文件权限掩码
umask(0);
//关闭文件描述符
for (int i = 0; i < 3; i++)
{
close(i);
}
while(1);
}
else
{
exit(0);
}
return 0;
}
练习:
创建一个守护进程,循环间隔1s向文件中写入一串字符“hello”
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0)
{
// 在子进程中创建新的会话
setsid();
// 改变路径到根目录
chdir("/");
// 改变文件权限掩码
umask(0);
// 关闭文件描述符
for (int i = 0; i < 3; i++)
{
close(i);
}
// 打开一个文件
int fd = open("/tmp/info.log", O_WRONLY | O_CREAT | O_TRUNC, 0777);
if (fd < 0)
{
perror("open err\n");
return -1;
}
// 循环写入
while (1)
{
write(fd, "hello\n", 6);
sleep(1);
}
}
else
{
exit(0);
}
return 0;
}
exec函数族
作用:在当前进程中执行一个新的程序。
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[]);
线程
概念
线程是一个轻量级的进程,为了提高系统的性能引入线程,线程和进程都参与统一的调度。
线程和进程的区别
共同点:都为操作系统提供了并发能力
不同点:
- 调度和资源上:线程是系统调度的最小单位,进程是资源分配的最小单位。
- 地址空间上:同一个进程创建多个线程共享进程资源,进程的地址空间相互独立。
- 通信方面:线程的通信相对简单,只需要通过全局变量就能实现。但是需要考虑临界资源(临界资源包括同一个文件,全局变量等)访问的问题,进程间的通信相对复杂,需要借助进程间的通信机制(借助3g-4g的的内核空间)
- 安全性方面:线程的安全性相对较差,当进程结束时会导致所有线程退出,进程相对于安全。
线程资源
共享的资源:可执行的指令,静态的数据,进程中打开的文件描述符,信号处理函数。当前的工作目录,用户的ID,用户的组ID
私有资源:线程ID (TID)、PC(程序计数器)和相关寄存器、堆栈、错误号 (errno)、信号掩码和优先级、执行状态和属性
线程标识:
主线程的 TID 和 PID 是相同的,每个子线程有自己独立的 TID,但它们都共享相同的 PID
线程函数接口
创建线程(pthread_create)
头文件
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
功能:创建一个线程
参数:1.pthread_t *thread:线程标识,成功创建线程后,pthread_create 会将新线程的 ID 写入 thread 指向的内存位置。
2.const pthread_attr_t *attr:线程属性, NULL:代表设置默认属性
3. void *(*start_routine):函数名,代表线程函数,指向一个函数的指针,这个函数就是线程的执行体(也就是线程的入口函数)。该函数必须符合 void *(*start_routine)(void *) 的原型,即接受一个 void * 类型的参数,并返回一个 void * 类型的值。
函数指针一般都是作为函数的参数使用:意思是在一个函数中回调另一方功能函数
4.void *arg:传递给 start_routine 的参数。arg 是一个通用的指针,可以传递任何类型的数据(通常是一个结构体的指针,以便传递多个参数)。如果不需要传递参数,可以传递 NULL。
返回值:成功返回0
失败返回错误码
示例代码
退出线程
void pthread_exit(void *retval);功能:用于执行退出线程
参数:任意类型的数据,一般写NULL
返回值:无
用法:
pthread_exit(NULL);