[APUE] 实战1:实现system

[APUE] 实战1:实现system受《APUE》第八章进程控制启发,想自己实现一个可以支持多个命令行参数,但尚不具备信号处理能力的 system(1) 。巩固一下字符串处理以及进程控制的三个元函数 fork(), exec..(), wait() 的使用。进而为后期实现 shell 做准备。一、任务要求1. 整体目标实现函数原型:void system(const char* cm...
摘要由CSDN通过智能技术生成

[APUE] 实战1:实现system

受《APUE》第八章进程控制启发,想自己实现一个可以支持多个命令行参数,但尚不具备信号处理能力的 system(1) 。巩固一下字符串处理以及进程控制的三个元函数 fork(), exec..(), wait() 的使用。进而为后期实现 shell 做准备。

一、任务要求

1. 整体目标

实现函数原型:void system(const char* cmd) 使其能够调用 shell 执行 cmd[] 中的指令,就像直接在命令行中键入 cmd[] 一样

2. 细节说明

指令长度

  • 一行命令最多有 15 15 15 个参数
  • 每个参数最长为 20 20 20 字节

输入格式

  • cmd[] 为有效的,可在 shell 中直接执行的一行命令,例如 ps -a
  • 相邻的两个参数由一个或多个空格分隔,即 ls -als -a 等价

二、任务难点

1. 字符串处理

  • 对字符串按空格进行拆分
  • 运用 <string.h> 中的函数,例如 memcpy(3) strlen(1) strtok(2)

2. 辨析各类指针

  • 指针数组:char* argv[]
  • 字符串:char* achar a[] 的区别
  • 内存分配:对 memset() 的返回值需指定的类型

3. exec函数族

该函数族在<unistd.h> 头文件中,共有 6 6 6 个函数。网上各类资源对于该函数的说明都不够全面。在调用的时候,极容易困惑、出错。我采用的是 int execv(2)。而在原书中,Stevens 先生采用的是 int execv(2+)

三、实现过程

1. 分析 execv(2)

函数原型

int execv(const char* pathname, char* const argv[])

工作原理

当进程调用该函数时,改进程执行的程序完全替换为指定的新程序。即当前进程的正文段、数据段、堆和栈段均被替换,而进程ID号不变。

因而为了使其能够正常返回至原有的运行逻辑,需:

  • fork(0) 一个子进程
  • 由该子进程 execv(2) 相应的功能函数
  • 而父进程 wait() 子进程结束后清理

这三步也是一般程序涉及进程控制的必要步骤,因而被称为进程控制原语

参数分析

  • pathname[]

    将被装入的程序的绝对路径,例如 /bin/ps

  • char* argv[]

    指针数组,每个元素为 char* 类型的指针,指向每个命令行参数。特别要注意的是,最后一个元素需指向空指针 (char*)0 表示已读取至最后一个参数。例如,命令 ps -a 拆分至 argv[] 可得:

    argv[0] = "ps";
    argv[1] = "-a";
    argv[2] = (char*)0;
    

2. 处理字符串

① 目标

分割命令行参数并装入不同的内存单元

② 步骤

分割命令行参数

一开始是想自己实现的,但在实现的过程中在 <string.h> 中发现了不少有用的函数,其中就有一个直接满足了我的需求。下面来介绍一下这个“冷门函数”:

  • 函数原型

    char* strtok(char* str, const char* delim)
    
  • 返回值

    首个两连续分隔符之间的非空字符串。若无匹配项,则返回 NULL

  • 参数分析

    • str[]:待分割的字符串,首次调用时填写,称之为“绑定”,之后用 NULL 来指代分割后剩下的字符串。特别要注意,传入的 str 会被修改。
    • delim[]:分隔符
  • 样例

    // test_strtok.c
    #include <string.h>
    #include <stdio.h>
    
    int main () 
    {
    	char a[] = "I#Love##Programming#In Unix Environment#";
    	printf("a = %s\n\n", a);
    
    	char* tok;
    	tok = strtok(a, "#");
    	while (tok != NULL)
    	{
    		printf("tok = %s\n", tok);
    		printf("a = %s\n\n", a);
    		tok = strtok(NULL, "#");
    	}
    }
    

    例如以 “I#Love##Programming#In Unix Environment#” 作为待分割的字符串,以 “#” 作为分隔符,得到如下结果:

    a = I#Love##Programming#In Unix Environment#
    
    tok = I
    a = I
    
    tok = Love
    a = I
    
    tok = Programming
    a = I
    
    tok = In Unix Environment
    a = I
    
    [Run In Ubuntu 16.04 LTS]
    

装入不同的内存单元并由 argv[] 指向

考虑要对字符串完全分割,所以要在 while 中不断调用 strtok(2) 。又要用不同的指针指向分割后的字符串,所以需在循环体内声明指针并赋值。因此,处理字符串部分的函数可作出:

// Split cmd[] to argv[]
char* fstArg = strtok(cmdcpy, " ");
argv[argc++] = fstArg;
while (1)
{
    char* remArgs = strtok(NULL, " ");
    if (remArgs == NULL) break;
    argv[argc++] = remArgs;
} 
argv[argc++] = (char*)0;

细心观察的朋友会发现,strtok(2) 传入的第一个参数是 cmdcpy[] 而不是 cmd[]。因为前面说到,strtok(2) 会改变参数的值,而 cmd[] 又是常量,不可改变,因此作了一个它的副本 cmdcpy[],手动按值传递

四、源代码及运行示例

1. 源代码

第一版支持多个命令行参数,但不支持信号处理的 system(1) 的具体实现给出如下:

#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

const int MAX_ARGC 		= 15;
const int MAX_ARGLEN	= 30;

/* Function
 	Execute the command
 */
int system(const char* cmd)
{
	// Init char* argv[]
	char* cmdcpy = (char*)malloc(sizeof(char) * MAX_ARGLEN);
	memcpy(cmdcpy, cmd, strlen(cmd)+1);
	
	int argc = 2, arglen = 0;
	char* argv[MAX_ARGC];
	char arg0[] = "sh";char arg1[] = "-c";
	argv[0] = arg0;argv[1] = arg1;

	// Split cmd[] to argv[]
	char* fstArg = strtok(cmdcpy, " ");
	//printf("fstArg = %s\n", fstArg);
	argv[argc++] = fstArg;
	while (1)
	{
		char* remArgs = strtok(NULL, " ");
		if (remArgs == NULL) break;
		argv[argc++] = remArgs;
	} 
   	argv[argc++] = (char*)0;

	// Invoke fork(0), execv(2), wait(1)
	if (fork() == 0)
	{
		printf("bash will execute\n");
		execv("/bin/sh", argv);
		printf("bash completed\n");
	}
	int chldID;
	if ( (chldID = wait(NULL))  > 0)
	{
		printf("chldProcess %d terminated successfully\n", chldID);
		return 0;
	}
	
	return -1;
}

2. 运行示例

测试函数

int main(int argc, char const *argv[])
{
	system("ps -a");
	system("ls -l");
	return 0;
}

运行结果

   PID TTY          TIME CMD
  9274 ?        00:00:00 systemd
  9278 ?        00:00:00 (sd-pam)
  9342 ?        00:00:00 gnome-keyring-d
 10003 ?        00:00:00 sh
 10007 ?        00:00:00 zeitgeist-daemo
 10014 ?        00:00:00 zeitgeist-fts
 10018 ?        00:00:00 zeitgeist-datah
 10028 ?        00:00:00 bash
 10036 ?        00:00:00 impsys
 10037 ?        00:00:00 sh
 10038 ?        00:00:00 ps
apue.2e
impsys
impsys.c
NetProg
qtProj
unpv13e
xv6-public
cmdcpy = ps -a
chldProcess 10037 terminated successfully
cmdcpy = ls -l
chldProcess 10039 terminated successfully
[Finished in 1.1s]
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值