【Linux下的cpp】进程

发布时间:2026/7/7 11:55:51
【Linux下的cpp】进程 Linux与cpp——进程文章目录Linux与cpp——进程信号设置程序对信号的处理signal()信号类型发送信号函数kill()多进程和信号终止进程调用可执行程序system()exec()函数族创建进程fork()创建子进程僵尸进程共享内存1. 创建/获取共享内存2. 连接共享内存到当前进程地址空间3. 从进程中分离共享内存4. 操作共享内存案例循环队列信号量生产消费者模型本笔记中的进程→狭义中的Linux操作系统下运行的c程序信号在Linux操作系统中我们可以使用killall或kill命令向进程传递信号用数值表示。# 以下的demo表进程killall-14demo# 查看进程是否存活killall-0demo进程在接收到信号后会进行处理有以下三种处理方式默认 - 终止进程宏为SIG_DFL)设置信号处理函数收到信号后执行函数忽略信号宏为SIG_IGN设置程序对信号的处理signal()头文件unistd.h signal.h函数声明sighandler_t signal(int signum, sighandler_t handler);signum: 信号的编号handler: 宏SIG_DFLSIG_IGN OR 可调用对象voidEXIT(intsig){...}intmain(){// 忽略全部的信号防止程序被信号异常中止。for(intii1;ii64;ii)signal(ii,SIG_IGN);// 如果收到2和15的信号Ctrlc和kill、killall本程序将主动退出。signal(2,EXIT);signal(15,EXIT);}信号类型常用信号类型信号名信号值默认处理动作发出信号的原因SIGHUP1A终端挂起或者控制进程终止SIGINT2A键盘中断CtrlcSIGQUIT3C键盘的退出键被按下SIGILL4C非法指令SIGABRT6C由abort(3)发出的退出指令SIGFPE8C浮点异常SIGKILL9AEF采用kill -9 进程编号 强制杀死程序。SIGSEGV11CEF无效的内存引用数组越界、操作空指针和野指针等。SIGPIPE13A管道破裂写一个没有读端口的管道。SIGALRM14A由闹钟alarm()函数发出的信号。可以用作定时器SIGTERM15A采用“kill 进程编号”或“killall 程序名”通知程序。SIGUSR110A用户自定义信号1SIGUSR212A用户自定义信号2SIGCHLD17B子进程结束信号SIGCONT18进程继续曾被停止的进程SIGSTOP19DEF终止进程SIGTSTP20D控制终端tty上按下停止键SIGTTIN21D后台进程企图从控制终端读SIGTTOU22D后台进程企图从控制终端写其它64A自定义信号处理动作一项中的字母含义如下A 默认的动作是终止进程。B 默认的动作是忽略此信号将该信号丢弃不做处理。C 缺省的动作是终止进程并进行内核映像转储core dump。D 缺省的动作是停止进程进入停止状态的程序还能重新继续一般是在调试的过程中。E 信号不能被捕获。F 信号不能被忽略。发送信号函数kill()等价kill/killall声明int kill(pid_t pid, int sig)sig指定信号若为0则没有任何信号送出但系统会检查执行是否错误常用于检查某个进程是否仍在运行pid指定进程作用将指定的信号传给指定的进程返回值成功0失败-1errno会被设置参数pid:pid 0代表具体的进程编号pid 0将信号传给和当前进程相同进程组的所有进程。常用于父进程给所有子进程发送信号且父进程也会收到。pid -1将信号广播推送给系统内所有进程系统关机时会用。多进程和信号在多进程的服务程序中如果子进程收到退出信号子进程自行退出。如果父进程收到退出信号则应该先向全部的子进程发送退出信号然后自己再退出。#includeiostream#includeunistd.h#includesignal.husingnamespacestd;voidFathEXIT(intsig);// 父进程的信号处理函数。voidChldEXIT(intsig);// 子进程的信号处理函数。intmain(){// 忽略全部的信号不希望被打扰。for(intii1;ii64;ii)signal(ii,SIG_IGN);// 设置信号,在shell状态下可用 kill 进程号 或 Ctrlc 正常终止些进程// 但请不要用 kill -9 进程号 强行终止signal(SIGTERM,FathEXIT);signal(SIGINT,FathEXIT);// SIGTERM 15 SIGINT 2while(true){if(fork()0)// 父进程的流程。{sleep(5);continue;}else// 子进程的流程。{// 子进程需要重新设置信号。signal(SIGTERM,ChldEXIT);// 子进程的退出函数与父进程不一样。signal(SIGINT,SIG_IGN);// 子进程不需要捕获SIGINT信号。while(true){cout子进程getpid()正在运行中。\n;sleep(3);continue;}}}}// 父进程的信号处理函数。voidFathEXIT(intsig){// 以下代码是为了防止信号处理函数在执行的过程中再次被信号中断。signal(SIGINT,SIG_IGN);signal(SIGTERM,SIG_IGN);cout父进程退出sigsigendl;kill(0,SIGTERM);// 向全部的子进程发送15的信号通知它们退出。// 在这里增加释放资源的代码全局的资源。exit(0);}// 子进程的信号处理函数。voidChldEXIT(intsig){// 以下代码是为了防止信号处理函数在执行的过程中再次被信号中断。signal(SIGINT,SIG_IGN);signal(SIGTERM,SIG_IGN);cout子进程getpid()退出sigsigendl;// 在这里增加释放资源的代码只释放子进程的资源。exit(0);}运行中不断创建子进程执行杀死某个子线程kill 14844杀死父进程后终止进程可以用echo $?命令行查看进程终止状态正常终止的线程值为0 非0都为异常状态终止方式状态析构return正常正常析构释放exit()正常调用全局对象析构局部不会调用_exit()正常了解直接退出无清理动作_Exit()正常了解直接退出无清理动作abort()异常接受信号异常注atexit()函数int atexit(void (*funtion)(void))登记终止函数用于进程退出前的首尾工作exit()调用终止函数的顺序与登记时相反会先运行func2再运行func1调用可执行程序在Cpp文件中调用Linux的可执行程序如命令行另一个cpp程序等system()声明int system(const char * string)“程序不存在” 或者 成功但终止状态非0返回非0成功且终止状态为0 返回0// 等价于命令行 ls -l /tmpintretsystem(/bin/ls -l /tmp);exec()函数族// 声明方式如下intexecl(constchar*path,constchar*arg,...);intexeclp(constchar*file,constchar*arg,...);intexecle(constchar*path,constchar*arg,...,char*constenvp[]);intexecv(constchar*path,char*constargv[]);intexecvp(constchar*file,char*constargv[]);intexecvpe(constchar*file,char*constargv[],char*constenvp[]);失败返回-1失败原因存在errno中在A进程中使用exec调用程序B则B将取代A进程的进程号、代码段、数据段和堆栈。换句话说进程不会切换回A继续执行代码。// 如下// 参数运行程序运行程序选项操作对象0不能省略// 最后一个参数0不能省略。intretexecl(/bin/ls,/bin/ls,-lt,/tmp,0);创建进程查看进程树pstree -p 进程编号需要先安装psmisc获取进程IDpid_tgetpid(void);// 获取当前进程的ID。pid_tgetppid(void);// 获取父进程的ID。fork()创建子进程fork()可以用于创建子进程也就是父进程的副本。fork()函数被调用一次但返回两次。两次返回的区别是子进程的返回值是0而父进程的返回值则是子进程的进程ID。子进程和父进程执行内容一致且都从fork()之后开始执行当子进程和父进程同时运行时其顺序不确定需要使用同步机制来确保执行顺序正确子进程将获得父进程的数据空间、堆栈的副本但不是原本的那一份虽然父子拥有的数据虚拟内存地址一致但物理内存不一样。父子进程共享同一个文件偏移量加入父进程在a.txt写入1000行数据父进程在运行时创建了子进程那么结束后a.txt写入的内容将是2000行#includeiostream#includefstream#includeunistd.husingnamespacestd;intmain(){ofstream fout;fout.open(/tmp/tmp.txt);// 打开文件。fork();for(intii0;ii10000000;ii)// 向文件中写入一千万行数据。{fout进程getpid()西施ii极漂亮\n;// 写入的内容无所谓。}fout.close();// 关闭文件。}用法父进程复制自己但执行不同代码比如用if判断copy过去的代码是一致的当进程需要执行(exec)另一个程序时。可以理解为替身攻击假设A需要使用exec()执行B程序但exec()之后的代码不会继续执行那么A可以fork出一个A’由A’去执行B程序那么A就可以执行完成自身代码。vfork()针对上述用法2我们可以直接使用vfork()vfork()不会复制父进程的地址空间子进程会立即调用exec。且vfork()会保证子程序先运行子程序调用exec或exit后父进程才会恢复。僵尸进程通常指子进程结束还没善后前父进程已经结束导致子进程的进程描述符还在系统中依然占用资源。产生的原因可能是父进程没有等待子进程结束并处理就已经结束了。避免方式子进程退出时内核会向父进程发送SIGCHLD信号。父进程在创建子进程前表明signal(SIGCHLD,SIG_IGN)即退其退出不感兴趣那么子进程会退出后立刻释放。父进程使用wait()/waitpid()等函数等待子进程结束在子进程退出前父进程将会阻塞等待。// 通常使用这个 返回值为子进程编号pid_twait(int*stat_loc);stat_loc为子进程终止信息。宏WIFEXITED(stat_loc)判断是否正常终止宏WEXITSTATUS(stat_loc)会获取终止状态正常为1详见终止状态宏WTERMSIG(stat_loc)可以获取终止进程的信号如果该进程异常终止是由于信号造成的话若父进程任务繁忙可以通过捕获SIGCHLD信号在信号处理函数中调用wait()/waitpid()进行处理实例1演示第二种避免方式的check#includeiostream#includeunistd.h#includesys/types.h#includesys/wait.husingnamespacestd;intmain(){if(fork()0){// 父进程的流程。intsts;pid_t pidwait(sts);cout已终止的子进程编号是pidendl;if(WIFEXITED(sts)){cout子进程是正常退出的退出状态是WEXITSTATUS(sts)endl;}else{cout子进程是异常退出的终止它的信号是WTERMSIG(sts)endl;}}else{// 子进程的流程。// sleep(100);// 以下两种写法会引起异常终止11// int *p 0;// *p 10;cout1endl;exit(1);}}实例2演示第三种避免方式#includeiostream#includeunistd.h#includesys/types.h#includesys/wait.husingnamespacestd;voidfunc(intsig)// 子进程退出的信号处理函数。{intsts;pid_t pidwait(sts);cout已终止的子进程编号是pidendl;if(WIFEXITED(sts)){cout子进程是正常退出的退出状态是WEXITSTATUS(sts)endl;}else{cout子进程是异常退出的终止它的信号是WTERMSIG(sts)endl;}}intmain(){signal(SIGCHLD,func);// 捕获子进程退出的信号。if(fork()0){// 父进程的流程。while(true){cout父进程忙着执行任务。\n;sleep(1);}}else{// 子进程的流程。sleep(5);// int *p0; *p10;exit(1);}}使用killall bodythread即可退出共享内存多线程可以通过全局变量来共享/传递数据多进程通过共享内存来共享和传递数据。共享内存只能使用内置数据类型共享内存不能使用STL共享内存不能使用move语义1. 创建/获取共享内存函数声明int shmget(key_t key, size_t size, int shmflg);key十六进制整数共享内存键值size共享内存的大小以字节为单位。shmflg: 共享内存的访问权限(与文件的权限一样)例0666全用户可读写例IPC_CREAT不存在则创建。返回值成功返回共享内存的id非负整数 失败返回-1通常由于内存不足没有权限tips: 可以使用ipcs -m查看系统共享内存ipcs -m sm_id手工删除该共享内存2. 连接共享内存到当前进程地址空间声明:void *shmat(int shmid, const void *shmaddr, int shmflg);shmid共享内存id由shmget()函数返回的共享内存标识shmaddr指定连接地址通常填0表系统自动选择连接地址shmflg:标志位通常填0返回成功则返回连接起始地址失败返回(void*)-13. 从进程中分离共享内存声明int shmdt(const void *shmaddr);shamddr 分离地址返回成功返回0失败返回-14. 操作共享内存最常用操作是删除声明int shmctl(int shmid, int command, struct shmid_ds *buf);shmid: 共享内存idcommand: 操作指令删除IPC_RMIDbuf: 操作的共享内存的地址如果要删除填0。返回成功0失败-1案例创建一个共享内存连接当前进程更改共享内存内容然后断开连接并释放。#includeiostream#includecstdio#includecstdlib#includecstring#includeunistd.h#includesys/ipc.h#includesys/shm.husingnamespacestd;// super girl 结构体structstgirl{intno;// 编号charname[51];// 姓名不能用string(其实是不支持STL容器因为会调用堆内存我们这里暂时不要涉及)};intmain(intargc,char*argv[]){if(argc!3){coutUsing: ./spgirl no name\n;return-1;}// 1. 创建共享内存intshmidshmget(0x5005,sizeof(stgirl),0640|IPC_CREAT);if(shmid-1){coutshmget(0x5005) failed.\n;return-1;}coutshmidshmidendl;// 2. 把共享内存连接到当前进程的地址空间stgirl*ptr(stgirl*)shmat(shmid,0,0);// 连接失败返回-1if(ptr(void*)-1){coutshmat() failed\n;return-1;}// 3. 使用共享内存对共享内存进行读/写cout原值noptr-no,nameptr-nameendl;// 显示共享内存中的原值。ptr-noatoi(argv[1]);// 对超女结构体的no成员赋值。strcpy(ptr-name,argv[2]);// 对超女结构体的name成员赋值cout新值noptr-no,nameptr-nameendl;// 显示共享内存中的当前值。// 4. 把共享内存从当前进程分离shmdt(ptr);// 5. 删除共享内存if(shmctl(shmid,IPC_RMID,0)-1){coutshmctl failed\n;return-1;}return0;}运行效果循环队列循环队列即数据结构中的循环队列有一个头指针和一个尾指针维护一个队列容器。有空的时候可以手写一个循环队列。注意当循环队列和共享内存结合使用时一定要额外定义初始化函数因为在共享内存分配循环队列对象时并不会调用构造函数。且类型不能为STL容器需要为内置类型信号量用于多进程同步机制。P 加锁减值V 解锁加值生产消费者模型没什么不同只是用法上复杂了一点用到了共享内存。但因为本章探讨的是进程同步那么需要用到POSIX共享内存和信号量。生产者代码