LV05-17-system函数

摘要:

  本文主要是关于system函数相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
开发板 正点原子 i.MX6ULL Linux阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
点击查看本文更新记录

2022-12-13 更新内容

创建笔记。

点击查看本文参考资料
参考方向 参考原文
----
点击查看相关文件下载
-- --

前边我们在《LV05-03-操作系统-进程操作》中已经学习过system函数的功能,这里我们再深入了解一下这个函数,因为我们实际工程中可能会经常使用这个函数。

一、system函数

1. system() 

1.1函数说明

  在 linux 下可以使用 man 3 system 命令查看该函数的帮助手册:

1
2
#include <stdlib.h>
int system(const char *command);

【函数说明】该函数可以在我们当前程序当中执行任意 shell 命令。system() 函数其内部的是通过调用 fork() 、 execl() 以及 waitpid() 这三个函数来实现它的功能。 system() 会调用 fork() 创建一个子进程来运行 shell (可以把这个子进程称为 shell 进程),并通过 shell 执行参数 command 所指定的命令。

【函数参数】

  • command : const char * 类型,参数 command 指向需要执行的 shell 命令,以字符串的形式提供,如 “ls -al” 等。

【返回值】返回值为 int 类型,当参数 command 为 NULL ,如果 shell 可用则返回一个非 0 值,若不可用则返回 0 ;针对一些非 UNIX 系统,该系统上可能是没有 shell 的,这样就会导致 shell 不可能;如果 command 参数不为 NULL ,则返回值从以下的各种情况所决定:

  • 如果无法创建子进程或无法获取子进程的终止状态,那么 system() 返回-1;
  • 如果子进程不能执行 shell ,则 system() 的返回值就好像是子进程通过调用 _exit(127) 终止了;
  • 如果所有的系统调用都成功, system() 函数会返回执行 command 的 shell 进程的终止状态。

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
5
/* 需要包含的头文件 */
#include <stdlib.h>

/* 至少应该有的语句 */
system("ps -l");

【注意】

   system() 的主要优点在于使用上方便简单,编程时无需自己处理对 fork() 、 exec 函数、 waitpid() 以及 exit() 等调用细节, system() 内部会代为处理;当然这些优点通常是以牺牲效率为代价的,使用 system() 运行 shell 命令需要至少创建两个进程,一个进程用于运行 shell 、另外一个或多个进程则用于运行参数 command 中解析出来的命令,每一个命令都会调用一次 exec 函数来执行;所以从这里可以看出,使用 system() 函数其效率会降低,如果我们的程序对效率或速度有所要求,那么建议不要直接使用 system() 。

1.2使用实例

test.c
1
2
3
4
5
6
7
8
#include <stdio.h>

#include <stdlib.h>
int main(int argc, char *argv[])
{
system("ps -l");
return 0;
}

在终端执行以下命令:

1
2
gcc test.c -Wall # 生成可执行文件 a.out
./a.out # 执行程序

程序执行后,终端将会显示以下信息:

1
2
3
4
5
F S   UID     PID    PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S 1000 11992 9420 0 80 0 - 5035 do_wai pts/1 00:00:00 bash
0 S 1000 17347 11992 0 80 0 - 595 do_wai pts/1 00:00:00 a.out
0 S 1000 17348 17347 0 80 0 - 658 do_wai pts/1 00:00:00 sh
0 R 1000 17350 17348 0 80 0 - 5254 - pts/1 00:00:00 ps

2. system()函数源码

  上边的了解仅仅够我们了解到如何使用这个函数,以及这个函数有什么功能,那他内部是怎样的,使用过程中会有什么影响?我们先来看一下这个函数的源码,很遗憾,我没找到,它在 stdlib.h 文件中声明,但是我还是没有找到源码,就网上搜了一个,应该大差不差:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int system(const char *cmdstring)

{
pid_t pid;
int status;

if (cmdstring == NULL)
{
return (1);
}

if ((pid = fork()) < 0)
{
status = -1;
}

else if (pid == 0)
{

execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
-exit(127); // 子进程正常执行则不会执行此语句
}
else
{
while (waitpid(pid, &status, 0) < 0)
{
if (errno != EINTER)
{
status = -1;
break;
}
}
}
return status;
}

从这里可以看出其实system函数调用之后会创建一个子进程,子进程调用execl去执行相应的shell命令,然后父进程会调用waitpid等待子进程退出,对子进城进程回收,这也就意味着,调用system的进程在这个时候会被阻塞,当执行的shell命令需要较长时间才能执行完毕的时候,调用system的进程就会等待很久的时间。

二、system耗时实例

1. debug_printf.h

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/** =====================================================
* Copyright © hk. 2022-2025. All rights reserved.
* File name : test.c
* Author : qidaink
* Date : 2022-11-08
* Version :
* Description: 里边的函数定义最好还是写到C文件中,这里只是为了方便
* Others :
* Log :
* ======================================================
*/
#ifndef __DEBUG_PRINTF_H__
#define __DEBUG_PRINTF_H__

#include <stdio.h>

#define DEBUG_ENABLE 1 /* 是否开启调试信息 */
#define LOG_LEVEL DEBUG_ALL /* 调试信息显示级别 */

#define ERROR "ERROR" /* 错误 */
#define WARN "WARN " /* 警告 */
#define INFO "INFO " /* 信息 */
/* 颜色定义——字体颜色 */
#define CLS_ALL "\033[0m" /* 清除所有颜色 */
#define F_BLACK "\033[30m" /* 黑色字体 */
#define F_RED "\033[31m" /* 红色字体 */
#define F_GREEN "\033[32m" /* 绿色字体 */
#define F_YELLOW "\033[33m" /* 黄色字体 */
#define F_BLUE "\033[34m" /* 蓝色字体 */
#define F_PURPLE "\033[35m" /* 紫色字体 */
#define F_CYAN "\033[36m" /* 青色字体 */
#define F_WHITE "\033[37m" /* 白色字体 */
/* 颜色定义——背景颜色 */
#define B_BLACK "\033[40m" /* 黑色背景 */
#define B_RED "\033[41m" /* 红色背景 */
#define B_GREEN "\033[42m" /* 绿色背景 */
#define B_YELLOW "\033[43m" /* 黄色背景 */
#define B_BLUE "\033[44m" /* 蓝色背景 */
#define B_PURPLE "\033[45m" /* 紫色背景 */
#define B_CYAN "\033[46m" /* 青色背景 */
#define B_WHITE "\033[47m" /* 白色背景 */
/* 颜色定义——背景的字体 */
#define BLACK_RED "\033[30;41m" /* 红底黑字 */
#define BLACK_GREEN "\033[30;42m" /* 绿底黑字 */
#define BLACK_YELLOW "\033[30;43m" /* 黄底黑字 */

/* 调试等级枚举类型定义 */
enum DEBUG_LEVEL
{
DEBUG_OFF = 0,
DEBUG_ERROR = 1,
DEBUG_WARN = 2,
DEBUG_INFO = 3,
DEBUG_ALL = 4
};
//===========================================================================
/* 自定义类型和字体颜色 */
#define MYDEBUG(TYPE, COLOR, FMT, ARGS...) \
do \
{ \
if(DEBUG_ENABLE && (DEBUG_##TYPE <= LOG_LEVEL)) \
{ \
printf(COLOR); \
printf("[%s][%s:%s:%d]"FMT, TYPE, __FILE__, __FUNCTION__, __LINE__, ##ARGS); \
printf(CLS_ALL"\n"); \
} \
} while(0)
//===========================================================================
/* 平时调试使用 */
#include <string.h>
#include <time.h> // 时间函数用
#define filename(x) (strrchr(x,'/')?(strrchr(x,'/')+1):x)
#define NORMAL_DEBUG 1 // 注意使用下边两个宏需要自己添加换行,比较符合平时的习惯
// 获取时间函数
char *timeString(void)
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
struct tm *timeinfo = localtime(&ts.tv_sec);
static char timeString[30];
sprintf(timeString, "%.2d-%.2d %.2d:%.2d:%.2d",
timeinfo->tm_mon + 1,
timeinfo->tm_mday,
timeinfo->tm_hour,
timeinfo->tm_min,
timeinfo->tm_sec);
return timeString;
}
// 获取系统时间戳 单位 ms
int sys_pts_get_ms(void)
{
struct timespec tv;
long long lasttime = 0;

clock_gettime(CLOCK_MONOTONIC, &tv);
lasttime = tv.tv_sec * 1000 + tv.tv_nsec / (1000 * 1000);
return lasttime;

}

#define HKPRT(fmt...) \
do \
{ \
if(NORMAL_DEBUG) \
{ \
printf(F_YELLOW); \
printf("[%s][HK_LOG][%s:%d][%s]", timeString(), filename(__FILE__), __LINE__, __FUNCTION__); \
printf(CLS_ALL); \
printf(fmt); \
} \
} while(0)
#define HKPRTE(fmt...) \
do \
{ \
if(NORMAL_DEBUG) \
{ \
printf(F_RED); \
printf("[%s][HK_LOG][%s:%d][%s]", timeString(), filename(__FILE__), __LINE__, __FUNCTION__); \
printf(CLS_ALL); \
printf(fmt); \
} \
} while(0)

#endif

2. system_called.c

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <unistd.h>
#include "debug_printf.h"
int main(int argc, char *argv[])
{
/* code */
int time0 = 0;
int time1 = 0;
time0 = sys_pts_get_ms();
HKPRTE("This is system_called!!!\n");
sleep(1);
time1 = sys_pts_get_ms();
HKPRTE("system_called use time:%d\n", time1-time0);
return 0;
}

这个文件我们使用以下命令编译:

1
gcc system_called.c -o system_called -Wall

然后便会生成 system_called 可执行文件,这个文件我们会在测试程序中通过system函数执行这个可执行文件。

3. test.c

点击查看详情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/* 头文件 */
#include <stdio.h> /* perror fgets shmat*/
#include <sys/shm.h> /* shmget shmat*/
#include <sys/ipc.h> /* shmget ftok */
#include <string.h> /* strcpy */
#include <stdlib.h> /* system */
#include <sys/prctl.h>
#include <unistd.h>

#include <fcntl.h> /* sem_init For O_* constants */
#include <sys/stat.h> /* sem_init For mode constants */
#include <semaphore.h>/* sem_init sem_post */
#include <pthread.h> /* pthread_create */
#include <signal.h> /* signal */

#include "debug_printf.h"

sem_t sem_sys; /* 定义信号量 */

void *thread(void *arg); /* 读取数据线程 */
void deleteSemfile(int sig);/* 删除无名信号量 */
void sigintHandle(int sig); /* 捕获 Ctrl+\ */


/* 主函数 */
int main(int argc, char *argv[])
{

pthread_t tid;

/* 信号捕获 */
signal(SIGINT, deleteSemfile);
signal(SIGQUIT, sigintHandle);
/* 信号量初始化) */
sem_init(&sem_sys, 0, 0);
/* 创建线程 */
pthread_create(&tid, NULL, thread, NULL);
/* 写入数据 */
while(1)
{
sleep(1);
}

return 0;
}

/* 线程函数 */
void *thread(void *arg)
{
int time0 = 0;
int time1 = 0;
char * main_name = "thread";
prctl(PR_SET_NAME, (unsigned long)main_name);
pthread_detach(pthread_self());
while(1)
{
sem_wait(&sem_sys);
time0 = sys_pts_get_ms();
HKPRT("system call ./system_called\n");
system("./system_called");
time1 = sys_pts_get_ms();
HKPRT("system use time:%d\n", time1-time0);
}
pthread_exit("thread return!");
}
/* 捕获 Ctrl+\ */
void sigintHandle(int sig)
{
static int count = 0;
HKPRT("I catch the SIGQUIT![%d times] \n", ++count);
sem_post(&sem_sys);
}
/* 删除信号量 */
void deleteSemfile(int sig)
{
sem_destroy(&sem_sys);
HKPRT("Destroy sem_sys!\n");
exit(0);
}

  这是我们真正的测试函数,我们使用以下命令编译:

1
gcc test.c -lpthread -Wall

这样我们便会生成一个 a.out 可执行文件,a.out可执行文件运行后,我们在终端按下 ctrl + \ 就可以产生一个 SIGQUIT 信号。

4. 测试结果

  我们的上边的几个文件都放在同一目录下,然后我们执行a.out 可执行文件,再按下 ctrl+\ ,会有以下内容输出:

1
2
3
4
5
6
hk@vm:~/6temp/test$ ./a.out 
^\[01-15 15:18:19][HK_LOG][test.c:70][sigintHandle]I catch the SIGQUIT![1 times]
[01-15 15:18:19][HK_LOG][test.c:59][thread]system call ./system_called
[01-15 15:18:19][HK_LOG][system_called.c:10][main]This is system_called!!!
[01-15 15:18:20][HK_LOG][system_called.c:13][main]system_called use time:1001
[01-15 15:18:20][HK_LOG][test.c:62][thread]system use time:1004

可以看到,我们的 a.out 中的 thread 线程执行一次一共耗时1004ms,而 system_called 可执行程序就耗费了1001ms,于是我们知道,system在某个线程中被调用的时候我们是要考虑到system执行的时间的。