进程创建, 等待, 终止. 使用代码实现.
进程创建
fork()
从已存在的进程中,创建一个具有独立地址空间的新的进程。新进程被称为子进程,而原进程为父进程。
函数原型:pid_t fork(void);
返回值:fork有两个返回值,子进程中返回0,父进程返回子进程id;出错返回-1。
fork之后,父进程与子进程共享代码,但数据各自私有一份。
fork常规用法:
1.父进程创建子进程,父子进程同时执行不同的代码段。所以fork之后常用if分流。
2.一个进程要执行不同的程序,调用exec函数。
vfork()
vfork也是用于创建子进程,而子进程与父进程共享地址空间。
vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。
vfork代码实现:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
//pid_t vfork(void);
int pid = vfork();
if (pid == 0) {
printf("i am child!!\n");
sleep(5);
return -1;
//exit(0);
printf("i am child two!!\n");
}
printf("i am parent!!\n");
return 0;
}
进程等待
必要性:
1.子进程退出,父进程若不管不顾,就可能造成“僵尸进程”的问题,进而造成内存泄漏。
2.进程一旦变成僵尸状态,kill -9也无能为力。
3.父进程需要通过进程等待来接收子进程的退出信息,回收子进程的资源。
分类:阻塞式等待、非阻塞式等待。
进程等待的方法:
wait
函数原型:pid_t wait(int* status);
返回值:成功返回被等待进程pid,失败返回-1。
参数:输出型参数,获取子进程退出信息,不关心则可以设置成NULL。
waitpid
函数原型:pid_t waitpid(pid_t pid,int* status,int options);
返回值:
-
当正常返回的时候waitpid返回收集到的⼦子进程的进程ID;
-
如果设置了选项WNOHANG,⽽而调⽤用中waitpid发现没有已退出的子进程可收集,则返回0;
-
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在。
参数:
pid:
pid=-1,等待任一个子进程,与wait等效。
pid>0,等待进程ID与pid相等的子进程。
status:
WIFEXITED:若为正常终止子进程返回的状态,则为真。
WEXITSTATUS:若WIFEXITED非零,提取子进程退出码。
options:
WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
代码实现:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
int main()
{
int pid = fork();
if (pid < 0) {
//errno是一个全局变量,存储每次系统调用出现错误原因编号
//strerror,通过错误编号获取字符串错误原因
printf("fork error :%s\n", strerror(errno));
//perror直接打印上一个系统调用错误原因
perror("fork error");
}else if(pid == 0) {
sleep(3);
exit(257);
}
//pid_t wait(int *status);
//阻塞等待任意一个子进程退出,获取返回值
//wait(NULL);
//pid_t waitpid(pid_t pid, int *status, int options);
//阻塞等待任意一个子进程或者指定的子进程退出
//pid: -1:等待任意一个子进程 pid>0:等待指定子进程
//options: WNOHANG:将waitpid设置为非阻塞 0:默认阻塞
//返回值:若WNOHANG被指定,没有子进程退出则立即报错返回0;错误:-1
int statu;
while(waitpid(-1, &statu, WNOHANG) == 0) {
//非阻塞轮询操作
printf("drink coffee\n");
sleep(1);
}
if ((statu & 0x7f) == 0){
printf("exit code:%d\n", (statu >> 8) & 0xff);
}
if (WIFEXITED(statu)) {
printf("exit code:%d\n", WEXITSTATUS(statu));
}
while(1) {
printf("i am parent\n");
sleep(1);
}
return 0;
}
进程终止
进程终止
进程退出场景:
1.代码运行完毕,结果正确;
2.代码运行正确,结果不正确;
3.代码异常终止。
进城常见退出方法:
正常终止(可通过echo $? 查看进程退出码)
1.从main返回
2.调用exit
3._exit
异常退出:Ctrl + c,信号终止
_exit函数
函数原型:void _exit(int status);
参数:status定义了进程的中止状态,父进程通过wait来获取该值。
exit函数
函数原型:void exit(int status);
进程退出代码:
进程退出代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int hot_beef_noodle = 0;
int main()
{
printf("-----------------");
sleep(3);
_exit(1); //任意位置调用都是退出进程
//exit(1) //任意位置调用都是退出进程
//return 1; //main函数中的return也是退出进程
//char *ptr = NULL;
//memcpy(ptr, "nihao", 5);
printf("i want to eat hot beef noodle\n");
if (hot_beef_noodle) {
printf("eat hot beef noodle\n");
}else {
printf("eat old noodler\n");
}
printf("want to eat noodle!!\n");
return 0;
}
编写自主shell.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/wait.h>
int main()
{
while(1) {
printf("[boxing@localhost]$ ");
fflush(stdout);
char tmp[1024] = {0};
scanf("%[^\n]%*c", tmp);
printf("[%s]\n", tmp);
//需要将整体字符串解析出:程序名称+参数
//" ls -a -l" -> "ls" "-a" "-l"
char *ptr = tmp;
int argc = 0;
char *argv[32] = {NULL};
while(*ptr != '\0') {
if (!isspace(*ptr)) {
//指针走到非空白字符处
argv[argc] = ptr;
argc++;
while(!isspace(*ptr) && *ptr != '\0') ptr++;
*ptr = '\0';
ptr++;
continue;
}
ptr++;
}
argv[argc] = NULL;
//argv[0]=ls argv[1] = -l argv[2] = -a
//判断当前命令是否是内建命令
if (!strcmp(argv[0], "cd")) {
//改变当前工作路径
chdir(argv[1]);
continue;
}
int pid = fork();
if (pid == 0) {
execvp(argv[0], argv);
//若子进程程序替换失败,则直接退出,因为终端不需要多个shell
exit(0);
}
//等待子进程退出,避免僵尸进程
wait(NULL);
}
return 0;
-封装fork/wait等操作, 编写函数 process_create(pid_t pid, void func, void* arg), func回调函数就是子进程执行的入口函数, arg是传递给func回调函数的参数.**
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
typedef void (*FUNC)(char*) ;//定义一个函数指针FUNC
void func(char* arg){
printf("%s,child pid is %d,father pid is %d\n",arg,getpid(),getppid());
sleep(5);
return;
}
void process_create(pid_t* pid, void* func, char* arg){
*pid = fork();
pid_t id;
if( *pid < 0 ){
perror("fork");
return;
}else if( *pid == 0 ){
//child
FUNC funct = (FUNC)func;
//设置一个函数指针funct并将函数func的地址赋值给它。
(*funct)(arg);//调用函数指针指向的函数,参数为arg.
exit(0);
}else{
//father
id = waitpid(-1,NULL,0); //父进程阻塞式等待
printf("wait child successful!,child id is %d\n",id);
return;
}
return;
}
int main(){
pid_t pid;
process_create(&pid, func, "haha");
return 0;
}
popen/system, 这两个函数和fork的区别.
system()函数
函数原型
#include <stdlib.h>
int system(const char *command);
system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令,此命令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。
调用/bin/sh来执行参数指定的命令,/bin/sh 一般是一个软连接,指向某个具体的shell。
实际上system()函数执行了三步操作:
1.fork一个子进程;
2.在子进程中调用exec函数去执行command;
3.在父进程中调用wait去等待子进程结束。
返回值:
1>如果 exec 执行成功,即 command 顺利执行,则返回 command 通过 exit 或 return 的返回值。(注意 :command 顺利执行不代表执行成功,当参数中存在文件时,不论这个文件存不存在,command 都顺利执行)
2>如果exec执行失败,也即command没有顺利执行,比如被信号中断,或者command命令根本不存在, 返回 127
3>如果 command 为 NULL, 则 system 返回非 0 值.
4>对于fork失败,system()函数返回-1。
判断一个 system 函数调用 shell 脚本是否正常结束的方法的条件应该是
1.status != -1
2.(WIFEXITED(status) 非零且 WEXITSTATUS(status) == 0
popen()函数:创建一个管道用于进程间通信,并调用shell,因为管道被定义为单向的 所以 type 参数 只能定义成 只读或者 只写, 不能是 两者同时, 结果流也相应的 是只读 或者 只写.
函数原型:
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
函数功能:popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令。这个进程必须由 pclose 关闭。
command参数:
command 参数 是 一个 字符串指针, 指向的是一个 以 null 结束符 结尾的字符串, 这个字符串包含 一个 shell 命令. 这个命令 被送到 /bin/sh 以 -c 参数 执行, 即由 shell 来执行.
type 参数 也是 一个 指向 以 null 结束符结尾的 字符串的指针
参数type可使用“r”代表读取,“w”代表写入。
依照此type值,popen()会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。
随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中。
返回值:
若成功则返回文件指针,否则返回NULL,错误原因存于errno中
区别
1.system 在执行期间,调用进程会一直等待 shell 命令执行完成(waitpid),但是 popen 无需等待 shell 命令执行完成就返回了。可以理解为,system为串行执行,popen 为并行执行。
2.popen 函数执行完毕后必须调用 pclose 来对所创建的子进程进行回收,否则会造成僵尸进程的情况。
3.popen 没有屏蔽 SIGCHLD ,如果我们在调用时屏蔽了 SIGCHLD ,如果在 popen 和 pclose 之间调用进程又创建了其他子进程并调用进程注册了 SIGCHLD 来处理子进程的回收工作,那么这个回收工作会一直阻塞到 pclose 调用。