C语言进程打开其他程序:
在qt中,我们会用到一个叫QProcess的类,它可以打开一个外部的程序,但和c的不太一样,其无法创建进程,并用该进程打开程序,而是直接打开外部程序。
exec函数族
C语言进程可以通过在进程中调用exec函数族执行某些程序,但那个子进程的所以从父进程继承的内容都会被替换掉。它能让父子进程执行不一样的程序,并且父进程不受到影响。
最大的使用exec的程序就是我们的Shell(命令行)。
execl/execlp:
exec族的函数:
int execl(const char *path,const char *arg,...);
int execlp(const char *file,const char *arg,...);
两个函数功能相同,成功执行的时候执行指定的程序,失败返回EOF。
其中path是程序的地址(绝对或相对都可以),而file直接写程序名就可以了,系统会在PATH自己找对应的路径。
arg就是main函数里的argv[],是程序调用的参数,第一个为程序名,后面为调用的参数名。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid;
pid = fork();
if (pid == -1) {
perror("fork");
return 1;
} else if (pid == 0) {
printf("子进程PID: %d\n", getpid());
// 使用execl执行ls命令
execl("/bin/ls", "ls", "-a", "-l", "/etc", NULL);
perror("execl");
exit(1);
} else {
printf("父进程PID: %d, 子进程PID: %d\n", getpid(), pid);
}
return 0;
}
除了execl和execlp还有两个常用的。
execv/execvp:
int execv(const char *path,const char *argv[]);
int execvp(const char *file,const char *argv[]);
和上面的一样,只是后面的一长串的东西变成了指针数组,如下:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid;
pid = fork();
if (pid == -1) {
perror("fork");
return 1;
} else if (pid == 0) {
printf("子进程PID: %d\n", getpid());
// 使用execv执行ls命令
char *args[] = {"/bin/ls", "-a", "-l", "/etc", NULL};
execv("/bin/ls", args);
perror("execv");
exit(1);
} else {
printf("父进程PID: %d, 子进程PID: %d\n", getpid(), pid);
}
return 0;
}
system:
如果觉得上面的创建过程太麻烦,还有个最简单的函数:
int system(const char *command);
command是你要执行的程序,成功就会返回程序执行的结果(包括输出等),失败返回EOF。
主进程会等commond结束在继续。
cpp
#include <stdlib.h>
#include <stdio.h>
int main() {
int ret = system("ls -l"); // 列出当前目录文件(Linux命令)
if (ret == -1) {
printf("无法执行命令\n");
}
return 0;
}
进程回收:
前面说过进程可以通过exit函数结束,,对于子进程,其结束后由父进程负责回收,如果父进程先一步结束,那子进程就会变成孤儿进程,被shell的init进程收养,那如果子进程结束后父进程没有马上回收呢?
那它就会变成僵尸进程,pcb等无法释放,直到父进程结束后,被init释放,所以我们父进程要主动去回收子进程。
wait:
在unistd.h头文件的wait函数就是用于回收子进程的:
(!!!!在新的c语言标准中,wait函数改了头文件,在<sys/wait.h>中)
pid_t wait(int *status);
成功会返回子进程的进程号,失败返回EOF。
为了确保子进程能成功回收,父进程会一直在wait函数等待(阻塞)直至子进程结束回收。
若有多个子进程,哪个先结束就会先回收那个。
函数的参数会保存子进程的返回值和结束方式的地址,所以要先创建好一个整型变量,然后传地址给函数。如果不希望接收返回值,就i传递一个NULL,父进程会直接释放子进程。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
int status;
pid_t pid;
if((pid = fork()) < 0){
perror("fork");
exit(-1);
}else if(pid == 0){
sleep(5);
exit(2);
}else{
wait(&status);
printf("%x\n",status);
}
return 0;
}
进程可通过exit/return等方式结束,属于正常结束,都会返回某个值(0~255),除此之外还有通过信号结束。对于status返回值,c语言也定义了很多的宏,可对其数据进行提取,分析等:
| 宏 | 含义 |
|---|---|
WIFEXITED(status) |
为真表示子进程正常调用 exit/_exit 终止 |
WEXITSTATUS(status) |
仅在 WIFEXITED 为真时有效,返回低 8 位退出码 |
WIFSIGNALED(status) |
为真表示子进程被信号杀死(如 kill -9) |
WTERMSIG(status) |
返回杀它的信号编号 |
waitpid:
和wait差不多的函数
pid_t waitpid(pid_ pid,int *status,int option);
第一个参数是指定回收的对象,第二个是返回值,第三个是父进程的状态。
成功返回子进程的pid,如果进程还没,还结束就会返回0,失败返回EOF。
option有两个选择,一、0,表示以阻塞态的方式等待到子进程结束,二、WNOHANG,表示不会阻塞,即当前单次查看子进程的状态。
waitpid(-1,&status,0) == wait(&status)。
-1表示父进程的任意子进程
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid = fork();
if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); }
if (pid == 0) {
printf("child: my pid=%d, going to sleep 2s\n", getpid());
sleep(2);
exit(42);
}
int status;
printf("parent: waiting for child %d ...\n", pid);
if (waitpid(pid, &status, 0) == -1) {
perror("waitpid");
exit(EXIT_FAILURE);
}
if (WIFEXITED(status))
printf("parent: child exited with code %d\n", WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("parent: child killed by signal %d\n", WTERMSIG(status));
else
printf("parent: child stopped/continued\n");
return 0;
}
守护进程:
第一课的时候,进程分为三种,守护进程是一种和终端无关,一直在后台执行的进程。在系统启动时运行,系统关闭时结束,不受任何程序运行。Linux中含有大量的守护进程,如ssh,打印服务等,若想写后台服务程序,守护进程就是不二之选。
会话、进程组:
Linux以会话、进程组的方式管理进程。
每个进程属于一个进程组,子进程和父进程在一个进程组里。
会话是一个或多个进程的集合,在平常使用中,当我们打开终端的时候,就会创建一个会话。所有在这个终端的进程都属于这个会话。而终端打开的第一个程序:shell程序,那么他就是这个会话的首进程,也就是会话的组长。
守护进程的创建:
我们用子进程来创建守护进程:
先创建一个子进程,然后关闭父进程,使子进程变成孤儿进程,被init收养(拜托父进程的控制)
通过setsid()函数创建一个新的会话。(摆脱当前的终端,使其成为新的会话的首进程,成为独立的进程)
更改当前的工作目录,使其在一个不会被修改的目录工作。(保护守护进程)
重设文件掩码(保证守护进程创建的文件不会受掩码影响,不让系统影响自身工作)
关闭打开的所有从父进程继承的文件描述符。(使用getdtablesize()函数得到其继承的文件数,用for循环全部关闭)
(因为脱离了终端,使用标准输入输出流都将无法使用,所以使用文件io)
将当前系统时间每隔 1 秒写入 time.log 文件
cpp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main() {
pid_t pid;
FILE *fp;
time_t t;
int i;
pid = fork();
if (pid < 0) {
perror("fork");
exit(-1);
} else if (pid > 0) {
exit(0); // 父进程退出
}
// 子进程成为守护进程
setsid(); // 创建新会话
umask(0); // 清除文件权限掩码
chdir("/tmp"); // 更改工作目录
// 关闭所有打开的文件描述符
for (i = 0; i < getdtablesize(); i++) {
close(i);
}
// 打开日志文件
if ((fp = fopen("time.log", "a")) == NULL) {
perror("fopen");
exit(-1);
}
// 守护进程主循环
while (1) {
time(&t);
fprintf(fp, "%s", ctime(&t));
fflush(fp);
sleep(1);
}
fclose(fp);
return 0;
}