理解c语言system函数的返回值

c语言中的system函数可以说是程序执行时的一道重生之门,其重生妙效犹如我们之前《透析硬链接和软链接的区别》一文中的软链接文件。然而,system函数也带来了判断返回值的烦恼!本文分享我们对system函数的返回值的理解,希望对c语言学习者有所帮助(如有错误,还望指正,谢谢)。

先给出我们理解的system函数执行原理:

fork出子进程1,该子进程1通过execl来启动bash,bash会重新fork出一个子进程2去做实际的cmdstring命令,主进程会waitpid等待此子进程1的终结(潜在地,子进程1会等待子进程2)。

理解了这个执行过程,我们很容易完备枚举出system函数返回值的各个场景了。注意,我们在本文中会将此返回值分为高8bit和低8bit两部分(具体原因看完本文即可理解)。

  • 如果fork失败,则system返回-1(16bit的整体)。

  • 如果execl失败(包括非法的bash和非法的cmdstring),则system返回高8bit的127。

  • 如果waitpid失败但不是EINTR导致,则system返回-1(16bit的整体)且要设置errno。

  • 如果waitpid失败且是EINTR导致,则system返回高8bit的0和低8bit的bash返回值(128+signal number)。

  • 如果waitpid是成功的,则system返回高8bit的cmdstring的返回值和低8bit的0(0表示bash是正常终结的)。

通过上述5种情况,我们收获了关于返回值的以下启示:

  • 低8bit是bash的返回值,高8bit是cmdstring的返回值。

  • waitpid期间的EINTR返回也归入bash失败的场景。

  • waitpid期间的非EINTR终结以及fork失败归入整体失败的场景。

  • execl的失败归入cmdstring失败的场景。

可见,在fork+execl+waitpid不能完美终结的情况下,也要尽量靠近完美终结时的返回值方式:高低8bit分别(独立)存储cmdstring和bash的返回值,并且倾向于将失败场景归入cmdstring的失败。这种倾向性,无可厚非,毕竟求得cmdsting的返回值是system函数的核心业务。这样,cmdstring失败的错误码里预留了127给execl失败(或bash失败),也就是说cmdstring支持的返回值范围是[0..126]了,真是让人难以接受啊。。。

另外,我们看到,cmdstring的返回值是靠waitpid返回的,而此返回值经过了多道手:子进程2、子进程1、主进程,可谓繁琐至极(考虑换别的机制了?下次我们谈popen)。。。

综上,system函数的完美终结需要以下三个条件同时成立:(status代表system函数的返回值)

1)status != -1

2)bash正常终结【即低8bit为0,或者说:WIFEXITED(status)为true】

3)cmdstring正常终结【即高8bit为0,或者说:WEXITSTATUS(status)==0】

上述引入的两个宏,具体含义如下:

WIFEXITED(status)

returns true if the child terminated normally, that is, by call‐

ing exit(3) or _exit(2), or by returning from main().

WEXITSTATUS(status)

returns  the  exit  status  of  the child.  This consists of the

least significant 8 bits of the status argument that  the  child

specified  in  a  call to exit(3) or _exit(2) or as the argument

for a return statement in main().  This  macro  should  only  be

employed if WIFEXITED returned true.

可见,一个聚焦于bash的终结,一个聚焦于cmdstring的终结,各有分工,互有所赖:WEXITSTATUS(status)仅在WIFEXITED(status)为true时才能使用。

附上source code:(为方便理解,我们稍微做了修改)

/* If WIFEXITED(STATUS), the low-order 8 bits of the status.  */

#define __WEXITSTATUS(status)   (((status) & 0xff00) >> 8)

/* Nonzero if STATUS indicates normal termination.  */

#define __WIFEXITED(status) ((status & 0x7f) == 0)

另外,system函数里的waitpid函数还要和SIGCHLD信号的处理保持和谐共处:在主进程中需要mask SIGCHLD,waitpid之后再unmask SIGCHLD,这个mask/unmask处理是system函数内部完成的。否则,SIGCHLD的handler将完全可能破坏掉主进程的waitpid,从而无法保证system函数返回值的正确性了(考虑这个场景:system函数的调用者的SIG CHLD的handler里也调用了waitpid,这样就架空了system函数里的waitpid)。。。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Linux中,编写一个C语言程序来实现这个系统调用,可以使用以下代码: ```c #include <stdio.h> #include <unistd.h> #include <sys/syscall.h> #define __NR_MY_SYSCALL 398 // 用于定义自定义系统调用的号码 int main(int argc, char *argv[]) { if (argc != 3) { printf("Usage: %s <number> <flag>\n", argv[0]); return -1; } long num = atol(argv[1]); // 将第一个参数转换为long类型 int flag = atoi(argv[2]); // 将第二个参数转换为int类型 if (flag != 0 && flag != 1) { // 检查flag是否合法 printf("Invalid flag: %d\n", flag); return -1; } // 调用自定义系统调用 long ret = syscall(__NR_MY_SYSCALL, num, flag); printf("System call returned %ld\n", ret); return 0; } ``` 在上述代码中,我们首先检查了命令行参数的数量是否为3,如果不是,则输出使用说明并退出程序。接着,将第一个参数转换为long类型,将第二个参数转换为int类型,并检查flag的值是否合法。最后,调用自定义的系统调用,并输出返回值。 接下来,我们需要在内核中编写相应的系统调用实现。在Linux内核中,我们可以通过`syscalls/syscall.h`文件来定义新的系统调用号码。在这个文件中,我们需要添加以下代码: ```c #define __NR_my_syscall 398 // 定义新的系统调用号码 #define sys_my_syscall _my_syscall // 定义系统调用函数名 asmlinkage long sys_my_syscall(long num, int flag) { long ret = -1; if (flag == 0) { ret = num % 10; // 返回参数1的个位 } else if (flag == 1) { ret = (num / 10) % 10; // 返回参数1的十位 } return ret; } ``` 在上述代码中,我们首先定义了新的系统调用号码`__NR_my_syscall`,然后将系统调用函数名定义为`sys_my_syscall`。接着,我们实现了系统调用的函数体,根据flag的值计算出返回值,并将其返回。 最后,我们需要将新的系统调用号码添加到系统调用表中。在Linux内核中,系统调用表是一个数组,定义在`kernel/sys.c`文件中。我们可以在这个文件中添加以下代码: ```c [398] = sys_my_syscall, // 将新的系统调用添加到系统调用表中 ``` 现在,我们可以重新编译内核,并使用上面提到的C语言程序来测试我们的自定义系统调用了。例如,我们可以使用以下命令来测试: ```bash $ ./my_syscall 21009200064 0 # 返回值为4 $ ./my_syscall 21009200064 1 # 返回值为6 ``` 请注意,由于这是一个自定义的系统调用,因此需要重新编译和安装内核才能使用。为避免不必要的风险,请在测试前先备份您的系统,并确保您知道如何在出现问题时进行恢复。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值