目录
1. 进程创建
1.1 fork函数
在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include <unistd.h>
pid_t fork(void);
//返回值:子进程中返回0,父进程返回子进程id,出错返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核做:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
- fork返回,开始调度器调度
当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程,看如下程序。
int main( void )
{
pid_t pid;
printf("Before: pid is %d\n", getpid());
if ( (pid=fork()) == -1 )
perror("fork()"),exit(1);
printf("After:pid is %d, fork return %d\n", getpid(), pid);
sleep(1);
return 0;
}
运行结果:
[root@localhost linux]# ./a.out
Before: pid is 43676
After:pid is 43676, fork return 43677
After:pid is 43677, fork return 0
这里看到了三行输出,一行before,两行after。进程43676先打印before消息,然后它有打印after。另一个after消息有43677打印的。注意到进程43677没有打印before,为什么呢?如下图所示
所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行并且后续代码是共享的,所以43677没有打印before。注意,fork之后,谁先执行完全由调度器决定。
1.2 fork函数返回值
1.子进程返回0。
2.父进程返回的是子进程的pid。
1.3 写时拷贝:
通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:
在发生写时拷贝(Copy-on-Write,COW)时,父进程和子进程并不是对全部的数据进行拷贝,而是对需要修改的个别数据进行单独的拷贝一份。
1.4 fork常规用法
一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子
进程来处理请求。
一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数
1.5 fork调用失败的原因
系统中有太多的进程,导致内存资源不足
实际用户的进程数超过了限制
总结:创建进程是要占用资源的,因为进程 = 内核数据结构 + 代码和数据,所以当创建进程的话本质上是多了个进程,因为操作系统也要管理这个进程,所以操作系统必须要为该进程创建对应的内核数据结构 对象 ,以及为了承载它的代码和数据必须的想办法申请内存,把代码和数据加载到内存当中,所以这就叫做进程创建。
2. 进程终止
进程退出场景
1.情况分类:
a. 正常执行完了(1. 结果正确 2. 结果不正确)
b. 崩溃了(进程异常)[信号] - 崩溃的本质:进程某些原因,导致进程收到了来自操作系统的信号(kill -9
2.理解:
如何理解进程退出?OS内少了一个进程,OS就要释放进程对应的内核数据结构+代码和数据(如果有独立的)。
3.操作:
都有哪些进程退出方式呢?
- main函数return退出。其他函数return呢?仅仅代表该函数返回->进程执行,本质就是main执行流执行。
- exit(int exit code)函数。exit(int exit code):exit code代表的就是进程的退出码,等价于main的return xxx,exit函数的作用是在代码的任意地方调用exit函数都表示进程退出!
- _exit(int exit code)函数:貌似等价于exit。
_exit和exit是两个不同的函数,用于在程序中终止进程的执行。它们之间的主要区别如下:
- _exit函数:
- _exit是操作系统提供的系统调用,用于立即终止进程的执行,不会进行任何清理工作。
- 调用_exit函数后,进程会立即退出,不会执行后续的代码、关闭文件描述符、刷新I/O缓冲区等操作。
- _exit函数的原型为:void _exit(int status);
- exit函数:
- exit是C标准库提供的函数,用于正常地终止进程的执行,并进行一系列清理工作。
- 调用exit函数会顺序执行一些清理操作,例如关闭已打开的文件、刷新I/O缓冲区、调用注册的atexit函数等。
- exit函数的原型为:void exit(int status);
可以看出,主要区别在于_exit函数是立即终止进程,不进行任何清理工作,而exit函数则会进行一系列清理操作后再终止进程。因此,一般情况下,推荐使用exit函数,以确保程序能够正常地释放资源、关闭文件等,防止资源泄漏。
int main()
{
printf("hello");
exit(0);//C语言库函数
}
运行结果:
[root@localhost linux]# ./a.out
hello[root@localhost linux]#
int main()
{
printf("hello");
_exit(0);//系统调用函数
}
运行结果:
[root@localhost linux]# ./a.out
[root@localhost linux]#
需要注意的是,exit函数底层是通过自动调用_exit函数来终止进程,因此在调用exit函数后,程序会先执行清理操作,然后才会真正终止。如果需要立即终止进程并且不进行任何清理工作,可以直接调用_exit函数,所以库函数和系统接口函数是上下层关系。
通过这个代码的运行结果虽然我们不知道缓冲区在哪,但是一定知道缓冲区不在操作系统内!因为在第一个代码中使用了exit()函数,它会先刷新标准I/O流缓冲区,然后调用_exit()系统调用来直接退出程序。而在第二个代码中使用了_exit()函数,它会直接退出程序,不会刷新标准I/O流缓冲区。所以第一个代码中的printf("hello")会被打印出来,而第二个代码中的printf("hello")不会被打印出来。
return退出:
return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。
3. 进程等待
3.1 进程等待的必要性
进程等待的必要性在于充分利用系统资源,避免产生僵尸进程,并确保正确处理子进程的退出状态。
以下是进程等待的必要性的几个方面:
- 避免产生僵尸进程:如果一个进程在子进程结束后没有调用wait()或waitpid()来获取子进程的退出状态,子进程会成为僵尸进程,占用系统资源,并且父进程无法获得子进程的退出状态,无法正确处理。通过等待子进程的退出状态,父进程可以及时回收子进程的资源,并且清理子进程的相关信息,避免产生僵尸进程。
- 释放进程资源:进程结束后,系统会保留一些资源,如打开的文件描述符、信号处理器等。通过调用wait()或waitpid()来等待进程的结束,父进程可以及时释放这些资源,提高系统的资源利用率,避免内存泄漏。
- 处理子进程的退出状态:子进程结束时,会向父进程发送一个退出状态,包含了子进程的退出码、终止信号等信息。通过调用wait()或waitpid(),父进程可以获取子进程的退出状态,并根据不同的退出状态进行相应的处理,如记录日志、重启进程等。
- 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出
综上所述,进程等待的必要性在于避免产生僵尸进程,释放进程资源,及时处理子进程的退出状态,保障系统的正常运行和资源利用。
注:当一个子进程结束时,无论是通过return语句还是通过exit()函数来结束子进程的执行流,它都会成为一个僵尸进程,直到父进程调用wait()或waitpid()来等待并获取子进程的退出状态。
3.2进程等待的方法
wait方法:
wait()是一个系统调用函数,用于父进程等待子进程的结束,并获取子进程的退出状态。它的原型如下:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
参数说明:
- status:是一个指向整型变量的指针,用于存储子进程的退出状态。如果不关心子进程的退出状态(退出结果),可以将该参数设置为NULL。
返回值说明:
- 如果成功等到一个子进程结束,返回子进程的进程ID;
- 如果没有子进程或者调用过程中被信号中断,返回-1。
wait()函数的使用流程通常如下:
- 父进程调用fork()创建子进程。
- 子进程开始运行,并在适当的时机调用exit()或_exit()来结束进程,并设置退出状态。
- 父进程调用wait()来等待子进程的结束,并获取子进程的退出状态。
- 父进程根据子进程的退出状态进行相应的处理。
wait()函数的调用会阻塞父进程,直到有一个子进程结束。如果有多个子进程,可以使用循环调用wait()来逐个等待子进程的结束。wait()函数也可以配合信号处理函数来实现非阻塞等待子进程的结束。
注意:wait()函数只能等待直接子进程的结束,无法等待其他进程的结束。如果需要等待指定的子进程,可以使用waitpid()函数。
waitpid方法:
系统调用函数waitpid()是一个用于等待子进程结束并获取子进程状态的函数。它的原型为:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
参数说明:
- pid:指定要等待的子进程的进程ID。有几种取值情况:
-
- pid > 0:表示等待进程ID为pid的子进程。
- pid = -1:表示等待任意子进程(与wait等效)。
- pid = 0:表示等待与当前进程在同一个进程组中的任意子进程。
- pid < -1:表示等待进程组ID为pid绝对值的任意子进程。
- status:是一个指向整型变量的指针,用于保存子进程的退出状态信息。如果status不为NULL,则保存子进程的退出状态(和wait的status是一样的,是一个输出型参数,不要把它当做一个完整的整数,而应该看做位图,里面保存了子进程的退出码和退出信号,因为指针里保存的是一个整形的地址,有32个比特位 0-31, 16 - 31的比特位直接舍弃不使用,8 - 15比特位用来表示子进程退出码,0 - 6 表示进程退出信号,7表示core dump标志,这个标志后面解释,所以我们可以通过位运算提取出退出码和退出信号)。
用来提取status里保存退出状态的信息:
第一方法通过位运算进行提取
获取退出码
(status>>8)&0xFF
获取退出信号
status & 0x7F
第二种方法通过函数提供的宏参数进行提取
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出,也就是判断子进程是否接受到信号,0为正常退出,非0表示异常,这也是为什么信号里面是从1开始而不是从0开始的原因)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
- options:参数用于指定附加的选项,控制waitpid函数的行为(options翻译为选项的意思)。常用的选项有:
-
- WNOHANG:如果没有子进程结束,立即返回,不会阻塞等待。
- WUNTRACED:如果子进程进入暂停状态(比如收到SIGSTOP信号),也立即返回。
- WCONTINUED:如果子进程在暂停状态后又恢复运行(比如收到SIGCONT信号),也立即返回。
这些选项的具体数值是由宏定义在头文件 <sys/wait.h> 中的。以下是常用选项的值:
- WNOHANG:表示为1,用于非阻塞地等待子进程。
- WUNTRACED:表示为2,用于等待暂停的子进程。
- WCONTINUED:表示为8,用于等待被继续运行的子进程。
options参数是用来控制waitpid函数的行为的,它可以通过按位或操作将不同的选项组合在一起使用。以下是options参数的三个常用选项及其用法:
- WNOHANG(非阻塞轮询模式):
-
- 用法:options |= WNOHANG;
- 作用:在没有子进程结束的情况下立即返回,而不阻塞等待子进程结束。如果没有设置该选项,waitpid函数会阻塞等待子进程结束。
- WUNTRACED(追踪暂停子进程):
-
- 用法:options |= WUNTRACED;
- 作用:用于检测子进程是否进入了暂停状态,比如收到了SIGSTOP信号。如果子进程进入暂停状态,waitpid函数会立即返回。
- WCONTINUED(追踪恢复运行的子进程):
-
- 用法:options |= WCONTINUED;
- 作用:用于检测子进程是否在暂停状态后恢复运行,比如收到了SIGCONT信号。如果子进程在暂停状态后恢复运行,waitpid函数会立即返回。
示例用法:
// 非阻塞模式,等待任意子进程结束
options |= WNOHANG;
pid_t pid = waitpid(-1, &status, options);
if (pid > 0) {
// 子进程结束,获取退出状态
if (WIFEXITED(status)) {
// 子进程正常退出
int exit_status = WEXITSTATUS(status);
printf("子进程 %d 正常退出,退出状态为 %d\n", pid, exit_status);
} else if (WIFSIGNALED(status)) {
// 子进程被信号终止
int signal_num = WTERMSIG(status);
printf("子进程 %d 被信号终止,信号号码为 %d\n", pid, signal_num);
}
}
// 追踪暂停和恢复运行的子进程
options |= WUNTRACED | WCONTINUED;
pid_t pid = waitpid(-1, &status, options);
if (pid > 0) {
// 子进程结束,获取退出状态
if (WIFEXITED(status)) {
// 子进程正常退出
int exit_status = WEXITSTATUS(status);
printf("子进程 %d 正常退出,退出状态为 %d\n", pid, exit_status);
} else if (WIFSIGNALED(status)) {
// 子进程被信号终止
int signal_num = WTERMSIG(status);
printf("子进程 %d 被信号终止,信号号码为 %d\n", pid, signal_num);
} else if (WIFSTOPPED(status)) {
// 子进程进入暂停状态
int stop_signal = WSTOPSIG(status);
printf("子进程 %d 进入暂停状态,暂停信号为 %d\n", pid, stop_signal);
} else if (WIFCONTINUED(status)) {
// 子进程恢复运行
printf("子进程 %d 恢复运行\n", pid);
}
}
注意,以上示例中的status变量是一个整型指针,用于保存子进程的退出状态信息。在waitpid函数返回时,可以通过一些宏函数(如WIFEXITED、WEXITSTATUS、WIFSIGNALED等)来判断子进程的退出状态和终止原因。
注:如果不使用options的这些选项作为参数时,可以把options位置的参数设置为0。
waitpid返回值说明:
- 如果指定的子进程还在运行,且没有设置WNOHANG选项,则waitpid函数会阻塞等待,直到子进程结束,返回子进程的PID。
- 如果指定的子进程已经结束,则waitpid函数会立即返回,返回子进程的PID。
- 如果出现错误,waitpid函数返回-1,并设置errno变量来表示具体的错误原因。
waitpid函数的作用类似于wait函数,但waitpid函数提供了更多的选项和灵活性,可以处理更多复杂的子进程管理情况。
注:如果子进程没有通过exit()或return语句来设置退出码,父进程调用wait()是无法获取到子进程的退出状态的。wait()函数只能等待子进程的结束并获取其退出状态,如果子进程没有设置退出码,父进程调用wait()返回的退出状态可能是未定义的。
在子进程结束时,它的退出状态由exit()或return语句传递给父进程。如果子进程没有设置退出码,父进程调用wait()只能获取到一个特殊的退出状态,例如默认值0或SIGCHLD信号引起的结束状态。
因此,为了能够正确获取子进程的退出状态,子进程应该通过exit()或return语句来设置一个合适的退出码,父进程再通过调用wait()来读取该退出码。
3.3获取子进程status(退出码)
wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态信息。
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status不能简单的当作整形来看待,可以当类似作位图来看待,具体细节如下图(只研究status低16比特位):
4.进程程序替换
fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
4.1替换函数
其实有六种以exec开头的函数,统称exec函数:
#include <unistd.h>`
int execl(const char *path, const char *arg, ...);
int execv(const char *path, char *const argv[]);
int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execvpe(const char *path, char *const argv[], char *const envp[]);
int execve(const cahr* filename, char* char* const argv[], char* const envp[]);
这些函数属于unistd.h头文件中的系统调用。
这些函数是用于执行外部程序的,它们的作用是将当前进程替换为指定的程序并执行。
注:如果替换成功,不会有返回值,如果替换失败,一定有返回值,如果失败了,必定返回,只要有返回值,就说明替换失败了,不用对该函数进行返回值判断,只要继续向后运行一定是失败的。
下面是每个函数的具体作用和使用方法:
- int execl(const char *path, const char *arg, ...):
-
- 作用:使用指定的路径执行一个新的程序。
- 参数:
-
-
- path:指定要执行的程序的路径。
- arg:指定要传递给程序的命令行参数。
- ...:可变参数,用于指定更多的命令行参数。
-
-
- 示例:
execl("/bin/ls", "ls", "-l", NULL);
- int execv(const char *path, char *const argv[]):
-
- 作用:使用指定的路径执行一个新的程序。
- 参数:
-
-
- path:指定要执行的程序的路径。
- argv:指定要传递给程序的命令行参数的字符串数组。
-
-
- 示例:
char *args[] = {"ls", "-l", NULL};
execv("/bin/ls", args);
- int execlp(const char *file, const char *arg, ...):
-
- 作用:使用环境变量PATH来查找可执行文件,并执行一个新的程序。
- 参数:
-
-
- file:指定要执行的程序的文件名。
- arg:指定要传递给程序的命令行参数。
- ...:可变参数,用于指定更多的命令行参数。
-
-
- 示例:
execlp("ls", "ls", "-l", NULL);
- int execvp(const char *file, char *const argv[]):
-
- 作用:使用环境变量PATH来查找可执行文件,并执行一个新的程序。
- 参数:
-
-
- file:指定要执行的程序的文件名。
- argv:指定要传递给程序的命令行参数的字符串数组。
-
-
- 示例:
char *args[] = {"ls", "-l", NULL};
execvp("ls", args);
- int execle(const char *path, const char *arg, ..., char *const envp[]):
-
- 作用:使用指定的路径执行一个新的程序,并传递环境变量。
- 参数:
-
-
- path:指定要执行的程序的路径。
- arg:指定要传递给程序的命令行参数。
- ...:可变参数,用于指定更多的命令行参数。
- envp:指定要传递给程序的环境变量的字符串数组。
-
注:指定要传递给程序的环境变量的字符串数组是覆盖式传入,操作系统会自动默认向每个运行的程序传递一个环境变量表。环境变量表是一个字符串数组,其中存储了一组环境变量键值对。
在C语言中,环境变量表包含在main函数的参数中,即int main(int argc, char *argv[], char *envp[])。其中,envp是一个指向环境变量表的指针,即使我们不显示的把envp写出来。但我们传入自定义的环境变量时,就会把里面的值给覆盖掉。通过遍历envp指针所指向的字符串数组,可以获取每个环境变量的名称和值。每个环境变量都以"键=值"的形式存储在字符串中,以NULL指针结尾表示环境变量表的结束
-
- 示例:
char *args[] = {"ls", "-l", NULL};
char *env[] = {"PATH=/bin", NULL};
execle("/bin/ls", "ls", "-l", NULL, env);
- int execvpe(const char *path, char *const argv[], char *const envp[]):
-
- 作用:使用环境变量PATH来查找可执行文件,并执行一个新的程序,并传递环境变量。
- 参数:
-
-
- path:指定要执行的程序的路径。
- argv:指定要传递给程序的命令行参数的字符串数组。
- envp:指定要传递给程序的环境变量的字符串数组。
-
-
- 示例:
char *args[] = {"ls", "-l", NULL};
char *env[] = {"PATH=/bin", NULL};
execvpe("ls", args, env);
- int execve(const char* filename, char* char* const argv[], char* const envp[]):
-
- 作用:使用指定的路径执行一个新的程序,并传递环境变量。
- 参数:
-
-
- filename:指定要执行的程序的路径。
- argv:指定要传递给程序的命令行参数的字符串数组。
- envp:指定要传递给程序的环境变量的字符串数组。
-
-
- 示例:
char *args[] = {"ls", "-l", NULL};
char *env[] = {"PATH=/bin", NULL};
execve("/bin/ls", args, env);
这些exec函数族的共同的作用是执行一个新的程序,替换当前的进程映像。通过这些函数,可以在Linux中实现进程间的映像替换,即将当前进程替换为另一个程序,从而实现程序的执行和功能的切换。每个函数适用于不同的场景和参数传递方式,提供了灵活性和定制性。
注:这些exec函数如果传完参数或者没有参数必须以NULL结尾。
函数解释:
1. 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。2. 如果调用出错则返回-1。 3. 所以exec函数只有出错的返回值而没有成功的返回值。
根据这几个特点我们不用对该函数进行返回判断,只需要继续向后运行就证明它一定是调用失败了!
命名理解:
这些函数原型看起来很容易混,但只要掌握了规律就很好记。
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量
注:当一个子进程执行程序替换(exec)时,它会完全覆盖自己的地址空间和代码,然后开始执行新的程序。在这种情况下,子进程的原始代码、数据和虚拟地址空间都将被新程序取代。
由于子进程在执行程序替换后成为了一个全新的进程,父进程对于旧的子进程的资源回收工作就变得无关紧要。父进程不再需要回收或处理旧的子进程,因为旧的子进程已经不存在了。
程序替换是一个特殊的操作,它用于在一个进程中执行一个新的程序,而无需创建新的进程。通过程序替换,子进程可以更新自己的代码和数据,然后继续从新程序指定的入口点开始执行。
需要注意的是,在某些情况下,父进程可能需要等待子进程执行程序替换完成,以确保新程序成功启动。这可以通过使用一些机制,如wait或waitpid系统调用,在父进程中等待子进程的状态变化来实现。但是,父进程无需显式回收子进程,而是依赖操作系统在子进程结束时自动回收其相关资源。
总之,当子进程执行程序替换时,父进程不再需要回收或处理旧的子进程,因为旧的子进程已经不存在了,被新程序取代。
5. 做一个简易的shell
考虑下面这个与shell典型的互动:
[root@localhost epoll]# ls
client.cpp readme.md server.cpp utility.h
[root@localhost epoll]# ps
PID TTY TIME CMD
3451 pts/0 00:00:00 bash
3514 pts/0 00:00:00 ps
用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。
然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。
所以要写一个shell,需要循环以下过程:
1. 获取命令行
2. 解析命令行
3. 建立一个子进程(fork)
4. 替换子进程(execvp)
5. 父进程等待子进程退出(wait)
实现代码:
代码中用到的几个不常见的函数:
getcwd函数:
getcwd()函数是一个POSIX标准定义的函数,用于获取当前工作目录的绝对路径。
函数原型如下:
#include <unistd.h>
char *getcwd(char *buf, size_t size);
参数说明:
- buf:指向一个字符数组的指针,用于存储获取到的当前工作目录的路径。
- size:buf数组的大小,用于防止溢出。
返回值:
- 如果调用成功,返回指向buf的指针,即当前工作目录的绝对路径。
- 如果调用失败,返回NULL,并设置errno来指示错误的类型。
使用示例:
#include <stdio.h>
#include <unistd.h>
int main() {
char currentDir[1024];
if (getcwd(currentDir, sizeof(currentDir)) != NULL) {
printf("Current directory: %s\n", currentDir);
} else {
perror("getcwd() error");
return 1;
}
return 0;
}
在上述示例中,我们声明了一个字符数组currentDir,用于存储获取到的当前工作目录的路径。然后,我们调用getcwd()函数,将返回的路径存储到currentDir中,并使用printf()函数将其打印出来。
请注意,在使用getcwd()函数时,需要确保buf指向足够大的字符数组,并且保证数组足够大,以便存储当前工作目录的绝对路径。
basename函数:
basename()函数是一个C库函数,用于从给定的路径中提取文件名或目录名。
函数原型如下:
#include <libgen.h>
char *basename(char *path);
参数说明:
- path:指向一个以null结尾的字符串,表示一个文件路径或目录路径。
返回值:
- 返回一个指向提取的文件名或目录名的字符串指针。如果提取失败或path为空字符串,则返回.。
使用示例:
#include <stdio.h>
#include <libgen.h>
int main() {
char *path = "/home/user/Documents/example.txt";
char *filename = basename(path);
printf("Filename: %s\n", filename);
char *directory = dirname(path);
printf("Directory: %s\n", directory);
return 0;
}
在上述示例中,我们声明了一个字符串指针path,并将其设置为一个文件路径"/home/user/Documents/example.txt"。然后,我们调用basename()函数,将返回的文件名存储到filename中,并使用printf()函数将其打印出来。
同样地,还可以使用dirname()函数来获取路径的父目录名。
需要注意的是,basename() 函数会直接修改传入的 path 字符串,因此,请确保传入的字符串是可修改的字符数组或者动态分配的内存。如果传入的参数是一个常量字符串或者指向常量字符串的指针,将导致未定义行为。
chdir函数:
chdir函数是一个用于改变当前工作目录的函数。它的功能是将当前工作目录更改为指定的目录。
在C语言中,可以使用chdir函数来更改当前工作目录。它位于头文件<unistd.h>中,并且其原型如下:
int chdir(const char *path);
函数参数path是一个字符串,表示要更改到的目录的路径。它可以是绝对路径(如"/path/to/directory")或相对路径(如"../directory")。函数返回值为0表示成功,-1表示失败。
下面是一个使用chdir函数的示例代码:
#include <stdio.h>
#include <unistd.h>
int main() {
char *newDir = "/path/to/directory";
if (chdir(newDir) == 0) {
printf("当前工作目录已更改为:%s\n", newDir);
} else {
perror("chdir失败");
return 1;
}
return 0;
}
该示例中,首先定义了一个字符串newDir,表示要更改到的目录的路径。然后使用chdir函数将当前工作目录更改为newDir,并根据函数返回值判断操作是否成功。如果成功,则打印出当前工作目录已更改的消息;如果失败,则使用perror函数打印出错误信息并返回1。
请注意,chdir函数只改变当前工作目录,不会改变进程的执行路径。要获取当前工作目录,可以使用getcwd函数。