linux 进程与线程
什么是进程
进程就是程序的一次动态执行的过程
进程是程序执行和资源管理的最小单位(在未被开启线程的情况下)
程序一旦被运行,就会产生一个进程,并且会被每一个进程分配一个0-4G(32OS下)的内存空间,意味着分配空间大小和操作系统的位数有关。
什么是进程标识符
每一个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证
进程的特点
- 独立性
进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间 - 动态性
进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,程序加入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的 - 并发性
多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响
父子进程
进程A创建了进程B,那么A叫做父进程,B叫做子进程,父进程是相对的概念,理解为人类中的父子关系
pid_t fork(void)
函数功能:使用fork函数创建一个进程
函数返回值:fork函数调用成功,返回两次 返回值为0 ,代表当前进程是子进程
返回值非负数,代表当前进程为父进程 调用失败 ,返回-1
fork创建子进程的一般目的
一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待客户端的服务请求。当这种情求达到时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。
一个进程要执行一个不同的程序。这对shell是常见的情况,在这种情况下子进程从fork返回后立即调用exec。
模拟socket 创建进程(服务器对接客户端的应用场景)示例代码:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
pid_t pid;
int data;
while(1){
printf("please input a data\n");
scanf("%d",&data);
if(data ==1 )
{
pid = fork();
if(pid >0){
}
else if(pid == 0){
while(1){
printf("do net request,pid=%d\n",getpid());
sleep(3);
}
}
}
else{
printf("wait, do noting\n");
}
}
return 0;
}
输入非1时候,模拟没有客户端进行交互
输入1时候,模拟有客户端进行交互 ,创建子进程来进行交互,子进程号为:1111
模拟多个客户端进行交互时 ,创建多个子进程来进行交互,子进程号为:9756 / 9758 / 9759
vfork创建进程
vfork直接使用父进程存储空间,不用拷贝(写操作才进行拷贝)
vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行
ps 常带的一些参数
命令参数 | 说明 |
---|---|
-e | 显示所有进程. |
-f | 全格式 |
-h | 不显示标题 |
-l | 长格式 |
-w | 宽输出 |
-a | 显示终端上的所有进程,包括其他用户的进程 |
-r | 只显示正在运行的进程 |
-u | 以用户为主的格式来显示程序状况 |
-x | 显示所有程序,不以终端机来区分 |
ps -ef 的每列的含义是什么呢
子进程退出方式
正常退出:
- Mian函数调用return
- 进程调用exit(),标准c库
- 进程调用_exit()或者——Exit(),属于系统调用
- 进程最后一个线程返回
- 最后一个线程调用pthread_exit
#include<stdlib.h>
void exit(int status);
#include<unistd.h>
void _exit(int status);
#include<stdlib.h>
void _Exit(int status);
异常退出:
- 调用abort
- 当进程收到某些信号时候,如ctrl+C
- 最后一个线程对取消(cancellation),请求作出响应
等待子进程退出
为什么要等待子进程退出?
创建子进程的目的就是为了让它去干活,在网络请求当中来了一个新客户端介入,创建子进程去交互,干活也要知道它干完没有.
僵尸进程
子进程退出状态不被收集,会变成僵死进程(僵尸进程)
等待函数:wait(状态码) 的使用
#include<sys/types.h>
#inlcude<sys/wait.h>
pid_t wait(int *status); //参数status 是一个地址
pid_t waitpid(pid_t pid , int *status ,int options);
int waitid(idtype_t idtype ,id_t id ,siginfo_t *infop, int options);
-
如果其所有子进程都还在运行,则阻塞。: 通俗的说就是子进程在运行的时候,父进程卡在wait位置阻塞,等子进程退出后,父进程开始运行。
-
如果一个子进程已终止,正等待父进程获取其终止状态,则会取得该子进程的终止状态立即返回。
-
如果它没有任何子进程,则立即出错返回。
收集退出进程状态
pid = vfork();
if(pid >0){
while(1){
printf("cnt=%d\n",cnt);
printf("this is father print pid is %d\n",getpid());
sleep(1);
}
}else if(pid == 0){
wait(NULL); // 参数:status 是一个地址 为空 表示不关心退出状态
while(1){
printf("this is child print pid is =%d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3 ){
exit(0);
}
}
}
等待函数:waitpid()的使用
wait和waitpid的区别之一
wait使父进程(调用者)阻塞,waitpid有一个选项 ,可以使父进程(调用者)不阻塞。
pid_t waitpid(pid_t pid , int *status ,int options);
pid == -1 | 等待任一子进程。就这一方面而言,waitpid与wait等效。 |
pid > 0 | 等待其进程ID与pid相等的子进程。 |
pid == 0 | 等待其组ID等于调用进程组ID的任一子进程 |
pid <-1 | 等待其组ID等于pid绝对值的任一子进程。 |
WCONTINUED | 若实现支持作业控制,那么由pid指定的任一子进程在暂停后已经继续,但其状态尚未报告,则返回其状态(POSIX.1的XSI扩展) |
WNOHANG | 若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0; |
WUNTRACED | 若某实现支持作业控制,而由pid指定的任一子进程已处于暂停状态。 |
孤儿进程
父进程如果不等待子进程退出,在子进程结束前就了结束了自己的“生命”,此时子进程就叫做孤儿进程。
孤儿进程被收留
Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程【init进程(pid=1)是系统初始化进程】。init 进程会自动清理所有它继承的僵尸进程。
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int cnt=0;
int status =10;
pid = fork();
if(pid >0){
printf("this is father print pid is %d\n",getpid());
}
else if(pid == 0){
while(1){
printf("this is child print pid is =%d,my father pid is=%d\n",getpid(),getppid());
sleep(1);
cnt++;
if(cnt == 3 ){
exit(5);
}
}
}
return 0;
}
exec族函数
我们用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
exec族函数作用
- 一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。
- 一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec。
system函数
#include<stdlib.h>
int system(const char * string);
system和execl不同的是:sysem运行完调用的可执行文件后还会继续执行源代码。
创建守护进程
1.创建子进程,父进程退出
2.在子进程中创建新会话
3.改变当前目录为根目录
4.重设文件权限掩码
5.关闭文件描述符
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
//功能:实现守护进程的创建
//(1)创建子进程并让父进程退出
pid_t pid = fork();
if(pid < 0)
{
perror("fork error");
exit(-1);
}
else if(pid > 0)
{
//让父进程退出
exit(0);
}
//(2)创建新的会话组
setsid();
//(3)改变守护进程工作目录为根目录
chdir("/");
//(4)设置文件掩码
umask(0);
//(5)关闭从父进程拷贝过来的文件描述符
int fdtablesize = getdtablesize();
int i;
for(i=0;i<fdtablesize;i++)
{
close(i);
}
//剩下的都是子进程
//创建并打开一个文件
char buf[30] = {"I am Daemon Process!\n"};
while(1)
{
int fw = open("Message.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
if(fw < 0)
{
perror("open error");
exit(-1);
}
//写入文件
write(fw, buf, strlen(buf));
close(fw);
sleep(1);
}
return 0;
}
popen函数
#include<stdio.h>
FILE *popen (const char *command ,const char *type);
int pclose(FILE *stream);
函数参数:
command: 是一个指向以NULL结束的shell命令字符串的指针。这行命令将被传到bin/sh并且使用 -c标志,shell将执行这个命令。
type: 只能是读或者写中的一种,得到的返回值(标准I/O流)也具有和type相应 的只读或只写类型。如果type是”r“则文件指针连接到command的标准输出;如果type是”w“则文件指针连接到command的标准输入。
返回值: 如果调用成功,则返回一个读或者打开文件的指针,如果失败,返回NULL,具体错误要根据errno判断
函数作用: popen()函数用于创建一个管道:其内部实现为调用fork产生一个子进程,执行一个shell以运行命令来开启一个进程这个进程必须由pclose函数关闭。
popen函数的使用
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(void)
{
char ret[1024]={0};
FILE *fp;
fp = popen("ps","r");
int nread = fread(ret,1,1024,fp);
printf("read ret %d byte ,ret =%s\n",nread ,ret);
return 0;
}