0
点赞
收藏
分享

微信扫一扫

c++webserver/第二章 多进程开发

萨科潘 2022-02-20 阅读 68

1.Linux下的PCB

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YcLbORpy-1645290246154)(第二种 多进程开发.assets/image-20220216000346333.png)]

2.进程状态转换

1.三态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EqjbeFw6-1645290246155)(第二种 多进程开发.assets/image-20220216001528866.png)]

2.五态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7097jIur-1645290246155)(第二种 多进程开发.assets/image-20220216001720840.png)]

3.ps命令.

//静态查看进程
ps aux(查看当前全部进程) | ajx

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-blzxSzFs-1645290246156)(第二种 多进程开发.assets/image-20220216002356429.png)]

4.top命令

//动态查看进程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rLSa0svJ-1645290246156)(第二种 多进程开发.assets/image-20220216003042146.png)]

5.kill命令

//杀死进程
kill -9/SIGKILL	pid		强制杀死进程
killall -name 进程名	  根据进程名杀死  

6.进程关系

ppid	父进程
pid		当前进程
pgid	进程组号

7.进程函数

1.创建进程

pid_t fork(void);
/*
	创建一个进程
	返回值:fork返回两次,一次是父进程,一个是子进程(区分父和子进程)
		  父进程中返回创建的子进程ID(大于0),-1表示失败,置errno.(进程数满|内存不足)
		  子进程返回0
	从函数执行开始,复制创建出一个子进程,两个进程交叉执行!
	因为为复制,所以代码相同,变量值相同,但是变量对于的!物理地址不同!
	用getpid()可以查看当前进程pid;
*/

#include <stdio.h>
#include <unistd.h>
int main()
{
    pid_t pid = fork();
    if (pid > 0)
    {
        printf("当前为父进程,pid = %d,其子进程的pid = %d\n", getpid(), pid);
    }
    else if (pid == 0)
    {
        printf("当前为子进程,pid = %d,其父进程为:%d\n", getpid(), getppid());
    }
    else if (pid < 0)
    {
        printf("创建进程失败");
    }
    return 0;
}

2.进程退出

void exit(int status); //c库
/*
	参数
	1.	退出时的状态(子进程退出之后状态发给父进程,帮助回收资源等);
*/
void _exit(int status); //linux系统
// !!退出时不会刷新IU缓冲,如果打印时没有\n.可能不能显示!!

区别:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kTVtCvH8-1645290246157)(第二种 多进程开发.assets/image-20220217215042130.png)]

8.GDB调试多进程

命令作用
set-follow-fork-mode[parent | child]设置跟踪的进程
show-follow-fork-mode显示现在执行的进程
set-detach-on-fork[on | off]默认on,是否脱离父进程运行,off挂起(8.0可能有bug)
info inferiors查看当前程序进程信息
inferior id切换进程,调试(需要子进程挂起)
detach inferiors id使程序的某个进程脱离GDB调试,自己运行

9.exec函数族

!int execl(const char *path,const char *arg,.../* (char *) NULL*/) ;	//常用,c库
/*
	参数:
		1.	可执行文件路径,也可以是shell命令(命令文件)
		2.	执行可执行文件需要的参数列表(可以多个)
			第一个参数一般无作用,写执行程序的名字即可.第二个开始写参数,以NULL结束
	返回值:成功无返回(因为代码也被替换了),失败返回-1,继续执行下面代码;
*/
!int execlp(const char *file,const char *arg, ... /* (char *)NULL*/);	//常用,c库
/*
	- 会到环境变量中找指定的可执行文件,找到执行,找不到就执行不成功(ps命令可以不写/bin)
	- 不包括当前路径
	参数
		1.	执行的文件名  
	其它一样
*/
int execle(const char*path,const char*arg,./*,(char*)null,char*const envp[]*/)//l(list)		参数地址列表,以空指针结尾
int execv(const char*path,char*const argv[])int execvp(Const char*file,char*const argv[])//v (vector)	存有各参数地址的指针数组的地址(用一个字符串数组)
int execvpe(const char*file,char*const argv[]char*const envp[]);
//p(path)	按PATH环境变量指定的目录搜索可执行文件
!int execve(const char*filenename,char*const argv[]char*const envp[]);//linux内核函数
//e (environment)	存有环境变量字符串地址的指针数组的地址

10.孤儿进程(没有父亲)

11.僵尸进程(有躯壳没灵魂)

12.进程回收

pid_t wait(int *status);
/*
	参数:
	1.	进程退出时的状态信息,传入的是一个int类型的地址,参数是一个传出参数;
	2.	返回值:成功返回子进程id,失败-1(所有子进程结束,调用函数失败);
	调用wait()函数的进程被阻塞,知道一个子进程结束或者收到一个不能被忽略的信号时才被唤醒
	如果没有子进程或者子进程都已经结束,返回-1;
*/
pid_t waitpid(pid_t pid, int *status, int options);
/*
	收回指定pid的进程,
	参数
	1.	>0 子进程的pid	 				=0  回收当前进程组的所有子进程
		-1 回收所有的子进程(相当于wait())  <-1 某个进程组的组id的绝对值,回收个id进程组的子进程
	2.	进程退出时的状态信息,传入的是一个int类型的地址,参数是一个传出参数;
	3.	设置阻塞(0)或者非阻塞(WNOHANG)
	返回值:>0.返回该进程id | =0	option = WNOHANG时,表示还有子进程 | <0 错误,或者没有子进程
*/
waitpid(-1,&status,0) = wait(&status);

status状态类型

WIFEXITED(status)		//非0,进程正常退出(判断程序是否正常退出)
WEXITSTATUS(status)	//如果上宏为真,获取进程退出的状态 (exit的参数)
    
WIFSIGNALED(status)	//非0,进程异常终止(判断为什么异常退出)
WTERMSIG(status)		//如果上宏为真,获取使进程终止的信号编号
    
WIFSTOPPED(status)		//非0,进程处于暂停状态
WSTOPSIG(status)		//如果上宏为真,获取使进程暂停的信号的编号
    
WIFCONTINUED (status)	//非0,进程暂停后已经继续运行

13.进程间通信(IPC)

目的:

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 通知事件:一个进程需要向另一个或一组进程发送消息通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供互斥和同步机制
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I1X29hnN-1645290246157)(第二种 多进程开发.assets/image-20220217231627248.png)]

14.匿名管道(环形队列)

特点:

  • 管道其实是一个在内核内存中维护的缓冲器,这个缓冲器的存储能力是有限的,不同的操作系统大小不一定相同。
  • 管道拥有文件的特质:读操作、写操作,匿名管道没有文件实体,有名管道有文件实体,但不存储数据。可以按照操作文件的方式对管道进行操作。
  • 一个管道是一个字节流,使用管道时不存在消息或者消息边界的概念(相当于没有协议),从管道读取数据的进程可以读取任意大小的数据块,而不管写入进程写入管道的数据块的大小是多少。
  • 通过管道传递的数据是顺序的,从管道中读取出来的字节的顺序和它们被写入管道的顺序是完全一样的。
  • 在管道中的数据的传递方向是单向的,一端用于写入,一端用于读取,管道是半双工的(同一时间只能一边发给另外一边)
  • 从管道读数据是一次性操作.数据一旦被读走,它就从管道中被抛弃.释放空间以便写更多的数据,在管道中无法使用lseek ()来随机的访问数据。
  • 匿名管道只能在具有公共祖先的进程(父进程与子进程或者两个兄弟进程,具有亲缘关系)之间使用。因为有相同的文件描述符,所有对于通过文件描述符和管道两端可以进行通信.

使用:

//创建匿名管道,用于进程通信
int pipe (int pipefd[ 2]);
 /*
 	参数
 	1.	int pipefd[2]是个传出参数.
 		[0]对应读端(read),[1]对应写端(write )
 	返回值:成功返回0,失败返回-1;
 	注意:!!!创建管道一定要在fork()之前!!!,创建之后才能对于同一个管道(文件描述符对于内容一样)
 		 管道默认是阻塞的,如果没有数据,read阻塞.如果管道满了,write阻塞
 */
//查看管道缓冲大小命令
ulimit -a  //(pipe size 一块的大小.多少块).可修改
//查看管道缓冲大小函数
long fpathconf(int fd,int name);
/*
	long size = fpathconf(pipefd[0],_PC_PIPE_BUFF);	//size就为大小
*/

//设置非阻塞状态
fcnl(pipefd[],flag);

读写特点(阻塞I/O的特殊情况):

总结:
  1. 读管道;
    • 管道中有数据,read返回实际读到的字节数。
    • 管道中无数据:
      • 写端被全部关闭,read返回0(相当于读到文件的末尾)
      • 写端没有完全关闭,read阻塞等待
  2. 写管道:
    • 管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号)
    • 管道读端没有全部关闭:
      • 管道已满,write阻塞
      • 管道没有满,write将数据写入,并返回实际写入的字节数

15.有名管道(命名管道,FIFO文件)

1.与匿名管道不同

2.有名管道使用

//通过命令创建有名管道
mkfifo	名字

//通过函数创建有名管道
int mkfifo (const char *pathname,mode_t mode);
/*
	参数
	1.	管道文件路径
	2.	文件权限(open相同,会与~umask按位与)
	返回值:成功返回0,失败返回-1, 置errno;
*/

/*
- 一旦使用mkfifo创建了一个FIFo,就可以使用open打开它,常见的文件I/o函数都可用于fifo。如: close、read、write、unlink等。
- FIFO严格遵循先进先出 (First in First out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作
- 打开一端时如果没有另外一端打开,那么管道会变成阻塞状态.直到有两端都打开.
- 如果在运行的时候,一段断开,另外一端也断开
*/

3.特殊点

  1. 读管道;
    • 管道中有数据,read返回实际读到的字节数。
    • 管道中无数据:
      • 写端被全部关闭,read返回0(相当于读到文件的末尾)
      • 写端没有完全关闭,read阻塞等待
  2. 写管道:
    • 管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号)
    • 管道读端没有全部关闭:
      • 管道已满,write阻塞
      • 管道没有满,write将数据写入,并返回实际写入的字节数

16.内存映射

1.定义

2.通信原理:

3.使用

//映射内存文件到内存中
void *mmap(void *addr, size_ t length, int prot, int flags,int fd,off t offset);
/*
	参数:
    1.	映射内存的首地址(传入NULL,由内核指定).
    2.	映射数据长度,不能为0.建议使用文件长度(按页分,页的整数倍大小)
    3.	申请内存映射区的操作权限(PROT_EXEC(执行)| READ | WRIED | NONE(无))
    	要操作映射区,一定要有读权限.如果要写就或上
    4.	[ MAP_SHARED(与磁盘文件进行同步) | MAP_PRIVATE(与文件不同步,重新创建一个新文件) | MAP_ANONYMOUS(匿名映射,不需要文件,fd指定-1,搭配用) ];
    5.	文件描述符,文件大小不能为0,open权限不能与前面有冲突(这里没有的,上面也不能有);
    6.	偏移量.一般不用,必须指定4K整数倍.0表示不偏移.如果不是会错误
    返回值:成功返回映射区首地址,失败返回MAP_FAILED;
*/

//释放内存映射
int munmap (void *addr, size t length) ;
/*
	参数
	1.	要释放内存大小
	2.	释放内存大小,与nmap的length一样
*/

4.使用对象

  1. 有关系的进行(父子进程) --还没有子进程的时候,通过父进程创建映射区.再创建子进程时就可以共享内存映射区
  2. 没有关系的进程
    1. 准备大小不为0的文件
    2. 进程1:通过磁盘文件创建内存映射 -得到一个操作这块内存的指针
    3. 进程2:通过磁盘文件创建内存映射 -得到一个操作这块内存的指针
    4. 使用内存映射区通信
  3. 内存映射区通信,非阻塞

5.注意事项

  1. 可以对指针进行++操作,但是不建议.因为释放内存会麻烦;
  2. 如果 flagprot 参数不一致,mmap时会失败并返回MAP_FAILED;
  3. mmap后把文件描述符指针关闭,不会产生影响
  4. ptr越界产生段错误
  5. 它还可以用于拷贝文件(速度快) -内存拷贝,太大内存装不下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NZZ03bz7-1645290246157)(第二种 多进程开发.assets/image-20220218235606883.png)]

6.匿名映射

17.信号

1.定义

2.使用信号的两个主要目的是:

  • 让进程知道已经发生了一个特定的事情。强迫进程执行它自己代码中的信号处理程序。

3.信号的特点:

  • 简单
  • 不能携带大量信息
  • 满足某个特定条件才发送优先级比较高

4.信号列表

5.信号的处理动作

  1. 信号的5中默认处理动作

    • Term 终止进程

    • Ign 当前进程忽略掉这个信号

    • Core 终止进程,并生成一个core文件(异常退出的错误信息)

    • stop 暂停当前进程

    • cont 继续执行当前被暂停的进程

  2. 信号的几种状态:产生、未决、递达

  3. SIGKILL和SIGSTOP信号不能被捕捉、阻塞或者忽略,只能执行默认动作。

//查找错误信息函数

ulimit -c 1024;	//指定core文件大小,默认为0.不生成
gcc -g ___.c -o ___;	//得到可执行文件
./___				//执行可执行文件,得到core文件
gdb __;				//进入调试状态
core-file core		//得到错误信息

6.信号相关的函数

//给某个进程发送任何信号sig
int kill(pid_t pid,int sig);
/*
	参数:
	1.	发送进程的pid
		>0	发送给指定进程
		=0,	向当前进程组中全部进程发送
		=-1	给每一个有权限接受的进程发送
		<-1	向取反的pgid进程组发送(-123向pgid = 123发送)
	2.	信号名称sig(用宏值)
*/


//给当前进程发送信号
int raise (int sig) ;
/*
	参数:
	1.	发送的信号
	返回值:成功0,失败非0;
*/

//!!!实际的时间 = 内核时间 + 用户时间 + 消耗的时间(IO操作)!!!

//发送SIGABRT信号给当前的进程,杀死当前进程
void abort (void) ;

//设置定时器,单位秒,开始调用时开始倒计时,为0是函数会给当前进程发送一个信号:SIGALARM.实际时间
unsigned int alarm(unsigned int seconds);
/*
	不会阻塞;无论进程什么状态,都会执行
	参数
	1.	seconds: 倒计时的时长.单位:秒.如果参数为0,定时器无效;
				 取消一个定时器,通过alarm;
	返回值:之前没有定时器,返回0;
		  之前有定时器,返回倒计时剩余的时间;
	SIGALARM: 默认终止当前的进程,每一个进程 有且只有唯一 的一个定时器(重复调用会覆盖上面的定时器);
*/

//周期性定时器,可以替代alarm函数,精度也比较高.单位微妙
int setitimer(int which,const struct itimerval *new_val,struct itimerval *old_value);
{
/*
	参数
	1.	指定时钟类型[	ITIMER_REAL(真实时间,正常时间3个.) | ITIMER_VTRTUAL(虚拟时间,用户时间) | ITIMER_PROF(用户时间,内核时间)]
	2.	定时器的结构体
	  	 struct itimerval 
	  	 {
	  	 	  struct it_value
	  	 	  {
	  	 	     	struct timeval it_interval; 	//每个阶段的时间,间隔时间(延迟后每几秒执行一次)
              		struct timeval it_value;    //延迟多长时间执行定位器(开始)
	  	 	  };

              struct it_interval 			//时间的结构体
              {
            		time_t      tv_sec;        //秒数
            		suseconds_t tv_usec;       //微妙
        	  };
         };
   3.	上一个定时器的属性(传出参数),直接置null即可;
*/
}


//信号捕捉,SIGKILL SIGSTOP不能被捕捉和忽略.!信号捕捉一定要在信号产生前面!
sighandler_t signal(int signum,sighandler_t handler);
/*
	参数
	1.	要捕捉的信号
	2.	捕捉到信号后如何处理
		- SIG_IGN:	忽略信号(定时器用)
		- SIG_DFT:	使用信号默认行为
		- 回调函数:	  内核调用,程序员只负责写.
	返回值:成功返回上一次注册的信号处理函数地址,第一次返回NULL
		  失败返回SIG_ERR,设置errno
*/
int sigaction (int signum,const struct sigaction *act,struct sigaction *oldact) ;

7.回调函数

void (*sighandler_t)(int); //函数指针
//int类型参数表示捕捉到的信号的值;

//例子
void myalarm(int num)
{
}

; //秒数
suseconds_t tv_usec; //微妙
};
};
3. 上一个定时器的属性(传出参数),直接置null即可;
*/
}

//信号捕捉,SIGKILL SIGSTOP不能被捕捉和忽略.!信号捕捉一定要在信号产生前面!
sighandler_t signal(int signum,sighandler_t handler);
/*
参数
1. 要捕捉的信号
2. 捕捉到信号后如何处理
- SIG_IGN: 忽略信号(定时器用)
- SIG_DFT: 使用信号默认行为
- 回调函数: 内核调用,程序员只负责写.
返回值:成功返回上一次注册的信号处理函数地址,第一次返回NULL
失败返回SIG_ERR,设置errno
*/
int sigaction (int signum,const struct sigaction *act,struct sigaction *oldact) ;


#### 7.回调函数

~~~cpp
void (*sighandler_t)(int); //函数指针
//int类型参数表示捕捉到的信号的值;

//例子
void myalarm(int num)
{
}
举报

相关推荐

0 条评论