fork创建子进程的特点
- fork创建一个子进程,父进程返回子进程的pid,子进程中返回0。
- fork创建的子进程几乎拷贝了父进程所有的内容(三个段:堆栈、数据、代码),fork之前的代码被复制并不会被执行,fork之后的代码被复制并执行。
- fork创建进程一旦成功,进程之间的空间就相会独立。各自分配0-4G的虚拟内存空间。
- fork创建进程之前打开的文件可以通过复制拿到同一个文件描述符 操作同一个文件(同一个文件指针)。
- 如果父进程退出,子进程没有退出,子进程会变成孤儿进程被init进程收养。变成后台进程。
如果子进程退出,父进程没有退出。父进程没有回收子进程的资源,子进程就会变成僵尸进程,应该避免僵尸进程的产生。
defunct :僵尸进程的标志。
进程函数
fork和vfork(知道)
1.fork(): 子进程拷贝父进程的数据段,代码段
vfork(): 子进程与父进程共享数据段
2.fork(): 父子进程执行次序不确定
vfork(): 保证子进程先运行,在调用exec()或exit()之前,与父进程数据共享,在exec()或exit()调用之后,父进程才能运行
总结:fork: 更通用,适用于需要创建一个完全独立的子进程的场景,vfork: 更适用于子进程立即执行 exec() 覆盖其自身的场景,因为它避免了不必要的地址空间复制,提高了性能。
退出进程(exit)
void exit(int status);
功能:结束进程,刷新缓存
参数:退出的状态
不返回。
exit(0);
void _exit(int status);
功能:结束进程,不会刷新缓存
参数:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。
通常0表示正常结束;
其他的数值表示出现了错误,进程非正常结束
两者的区别
exit(0);
刷新缓存,关闭所有打开的文件指针
_exit(0);
立即终止进程,但是不会进行上述的操作
exit(0)和return 0;的区别
exit 是一个函数,结束进程
return 是一个关键字,结束函数
回收进程资源(wait)
pid_t wait(int *status);
功能:回收子进程资源(阻塞)
参数:status:子进程退出状态,不接受子进程状态设为NULL
返回值:成功:回收的子进程的进程号
失败:-1
示例代码:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if (pid < 0)
{
perror("creat err");
return -1;
}
else if (pid == 0)
{
printf("in child\n");
sleep(5);
printf("子进程结束\n");
}
else
{
printf("in parent\n");
pid_t cpid = wait(NULL);
printf("%d\n",cpid);
printf("子进程资源已回收\n");
}
return 0;
}
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(-1,NULL,WNOHANG):不阻塞回收任一子进程
waitpid(pid,NULL,0)阻塞回收指定的子进程
获取进程号
pid_t getpid(void);
功能:获取当前进程的进程号
pid_t getppid(void);
功能:获取当前进程的父进程号
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if (pid < 0)
{
perror("creat err");
return -1;
}
else if (pid == 0)
{
printf("in child\n");
printf("chpid = %d\n",getpid());
printf("chppid = %d\n",getppid());
}
else
{
printf("in parent\n");
printf("prpid = %d\n",getpid());
printf("prppid = %d\n",getppid());
}
return 0;
}
守护进程
特点
守护进程是后台进程;生命周期比较长,从系统启动时开启,系统关闭时结束;它是脱离控制终端且周期执行的进程。
创建按步骤
- 创建子进程,父进程退出
让子进程变成孤儿进程,成为后台进程;fork()
- 在子进程中创建新会话
让子进程成为会话组组长,为了让子进程完全脱离终端;setsid()
- 改变进程运行路径为根目录
原因进程运行的路径不能被删除或卸载;chdir("/")
- 重设文件权限掩码
增大进程创建文件时权限,提高灵活性;umask(0)
- 关闭文件描述符
将不需要的文件关闭;close()
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
// 1.创建子进程
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
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;
}
练习
创建一个守护进程,循环间隔1s向文件中写入一串hello。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
// 1.创建子进程
pid_t pid = fork();
if (pid < 0)
{
perror("fork err");
return -1;
}
if (pid == 0)
{
// 2.在子进程中创建会话
setsid();
// 3. 改变运行路径
chdir("/");
// 4. 重设文件权限掩码
umask(0);
// 5.关闭文件描述符
for (int i = 0; i < 3; i++)
{
close(i);
}
// 打开一个文件
int fd = open("./a.txt", O_RDWR | O_CREAT | O_APPEND, 0777);
if (fd < 0)
{
perror("open err");
return -1;
}
while (1)
{
write(fd,"hello\n",6);
sleep(1);
}
}
else
{
exit(0);
}
return 0;
}
exec函数族
作用:在当前进程中执行一个新的进程
1. int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
使用可执行文件的绝对路径或相对路径 path 来替换当前进程的映像。参数通过变长参数列表传递,必须以 NULL 结尾。
2. int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
类似 execl,但 file 只需要是可执行文件的名称,函数会根据环境变量 PATH 查找可执行文件的路径。
3. int execle(const char *path, const char *arg, ...
/*, (char *) NULL, char * const envp[] */);
与 execl 类似,但允许传递一个自定义的环境变量列表 envp。这个环境会代替当前进程的环境变量。
4. int execv(const char *path, char *const argv[]);
使用路径 path 和参数数组 argv[] 替换当前进程。argv[0] 是程序名,argv[] 数组必须以 NULL 结束。
5. int execvp(const char *file, char *const argv[]);
类似 execv,但会根据 PATH 环境变量查找可执行文件。
6. int execvpe(const char *file, char *const argv[],char *const envp[]);
与 execvp 类似,但允许使用自定义的环境变量 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
失败返回错误码
#include <stdio.h>
#include <pthread.h>
void *mythread(void *arg)
{
printf("子线程执行结束\n");
}
int main(int argc, char const *argv[])
{
pthread_t tid;
//创建一个线程
if(pthread_create(&tid,NULL,mythread,NULL))
{
printf("create failed\n");
return -1;
}
printf("主线程执行中。。。\n");
sleep(2);
printf("主线程执行结束\n");
return 0;
}
未完待续……
1167

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



