1. 学习进程创建, 等待, 终止. 使用代码实现.
2. 编写自主shell.
3. 封装fork/wait等操作, 编写函数 process_create(pid_t* pid, void* func, void* arg), func回调函数就是子进程执行的入口函数, arg是传递给func回调函数的参数.
4. 调研popen/system, 理解这两个函数和fork的区别.
1. 学习进程创建, 等待, 终止. 使用代码实现.
进程的创建:
fork
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
int ret = fork();
if(ret < 0)
{
perror("fork");
return 1;
}
else if (ret == 0)
{
//child
printf("I am child : %d!, ret: %d\n",getpid(),ret);
}
else
{
//father
printf("I am father : %d!, ret: %d\n",getpid(),ret);
}
sleep(1);
return 0;
}
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;
}
进程的终止:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int hot_beef_noodle = 0;
int main()
{
printf("---------------");
sleep(3);
// _exit(1);
//exit(1);
return 1;
//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 noodle\n");
}
printf("want to eat noodle\n");
return 0;
}
进程的等待
wait
#include<stdio.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<errno.h>
#include<string.h>
int main(void)
{
pid_t pid = fork();
if(pid == -1)
{
perror("fork");
exit(1);
}
else if(pid == 0)
{
sleep(20);
exit(10);
}
else
{
int status;
int ret = wait(&status);
if( ret > 0 && (status & 0x7f) == 0 )
{ //正常退出 (返回status低16位中的高8位,此时低8位为0)
printf("child exit code:%d\n", (status) >> 8 & 0xff);
}
else if(ret > 0)
{ //异常退出:终止信号(低8位)
printf("sig code:%d\n", status & 0x7f);
}
}
}
waitpid
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0)
{
printf("%s fork error\n",__FUNCTION__);
return 1;
}
else if( pid == 0 )
{
printf("child is run,pid id : %d\n",getpid());
sleep(5);
exit(257);
}
else
{
int status = 0;
pid_t ret = waitpid(-1, &status, 0);//阻塞式等待
printf("this is test for wait\n");
if( WEXITED && ret == pid )
{
printf("wait child 5s success,child return code is:%d.\n",WEXITSTATUS(status));
}
else
{
printf("wait child failed, return.\n");
return 1;
}
}
return 0;
}
2. 编写自主shell.
思路:
1.获取命令行
2.解析命令行
3.创建子进程(fork)
4.子进程程序替换(execvp)
5.父进程等待子进程退出(wait)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<ctype.h>
#include<sys/wait.h>
int main()
{
while(1)
{
printf("[luchun@localhost]$ ");
fflush(stdout);
char tmp[1024] = { 0 };
scanf("%[^\n]%*c", tmp);//输入一行字符串(以\n作为结束标志)到tmp数组中保存
//scanf("%[^\n]%*c",str)表示读入一行字符串。
//^表示"非",[^\n]表示读入换行字符就结束读入。
//*表示该输入项读入后不赋予任何变量,即scanf("%*[^\n]%*c")表示跳过一行字符串。
char* ptr = tmp;//用ptr指针从头开始标记tmp数组
int argc = 0;//argc用来记录tmp中命令单词的个数
char* argv[32] = { NULL };/创建一个指针数组用来存放tmp中每个单词首元素的地址 argc用来记录tmp中命令单词的个数
while(*ptr != '\0')
{
if(!isspace(*ptr))
{
argv[argc] = ptr;//找到(第)一个单词后用argv[argc]保存,然后argc++意思计数一个单词(命令)
argc++;
while(!isspace(*ptr) && *ptr != '\0')//用来寻找计算tmp中有效单词
ptr++;//循环 使ptr指针走到单词的最后一个位置的下一个位置(空格或者\0处),因为最后一次,也就是ptr走到单词最后一个字符时还是会进循环,是ptr++
*ptr = '\0';//就是把原来tmp中命令的空格改为\0保存在argc[argc]中
ptr++;//ptr指针赋值完往后走一个继续完后去遍历tmp 直至tmp中的\0
continue;//不用执行下面的ptr++,直接回到while判断即可
}
ptr++;//如果命令开始或者中间有空格,遇见空格一直往后走 直至找到非空格处
}
argv[argc] = NULL;//给指针数组末尾加上NULL(\0)作为指针数组结束的标志 因为是指针数组,故不用\0 而是用空指针NULL
if(!strcmp(argv[0],"cd"))//如果遇到cd命令
{
chdir(argv[1]); //需要改变文件路径
continue;
}
int pid = fork();//创建子进程
if(pid == 0)
{
execvp(argv[0],argv);//对子进程进行程序替换
exit(0);
}
wait(NULL);//父进程 进程等待
}
return 0;
}
程序运行结果:
3. 封装fork/wait等操作, 编写函数 process_create(pid_t* pid, void* func, void* arg), func回调函数就是子进程执行的入口函数, arg是传递给func回调函数的参数.
#include<stdio.h>
#include<unistd.h>
#include<wait.h>
#include<stdlib.h>
typedef void (*FUNC)(char*);
void* func(void* arg)
{
char* str = (char*)arg;
printf("%s,child pid is %d,father pid is %d\n",str,getpid(),getppid());
sleep(5);
return;
}
void process_create(pid_t* pid,void* func,char* arg)
{
*pid = fork();
if(*pid < 0)
{
perror("fork error\n");
return;
}
else if(*pid == 0)
{
while(1)
{
FUNC funct = (FUNC)func;
(*funct)(arg);
exit(0);
}
}
else
{
pid_t id = waitpid(-1, NULL, 0);
printf("wait child prosess successfully,child id is %d\n",id);
return;
}
return;
}
int main()
{
char* arg = "child is runing";
pid_t pid;
process_create(&pid, func, arg);
}
输出结果为:
4. 调研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。
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中