进程是程序在计算机中的一次执行实例,包括了程序的代码、数据、以及运行的上下文环境。进程管理是操作系统的核心任务之一。
fork)新建进程。fork, exec, exit)#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork(); // 通过fork创建一个新进程 [1]
if (pid == 0) {
printf("This is the child process.\n"); // 子进程执行的代码 [2]
} else if (pid > 0) {
printf("This is the parent process.\n"); // 父进程执行的代码 [3]
} else {
printf("Fork failed.\n"); // fork失败的情况 [4]
}
return 0;
}
[1] 创建新进程:fork() 函数用于创建一个新进程。在调用 fork() 之后,父进程和子进程都会执行从 fork() 返回的下一条语句。返回值在两个进程中不同:子进程中 fork() 返回0,而父进程中返回子进程的进程ID。如果返回-1,则表示 fork 创建进程失败。
[2] 子进程执行的代码:在子进程中,当 pid 等于 0 时,执行 printf("This is the child process.\n");。这段代码标识当前运行进程为子进程。
[3] 父进程执行的代码:在父进程中,当 pid 大于 0 时,执行 printf("This is the parent process.\n");。这段代码标识当前运行进程为父进程,并输出子进程的进程ID。
[4] fork失败的情况:如果 fork() 返回 -1,则输出 printf("Fork failed.\n");。这种情况下可能是由于资源受限或者系统,导致无法创建新进程。
fork() 的使用常见于多进程程序中,通过创建子进程以并行执行任务。但需要注意不同操作系统的和资源分配问题。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
char *args[] = { "/bin/ls", NULL }; // 参数数组 [1]
execv(args[0], args); // 执行'ls'命令 [2]
printf("This statement won't be executed if execv is successful.\n"); // 不执行? [3]
return 0;
}
char *args[] = { "/bin/ls", NULL }; 定义了一个参数数组,用于存储将被执行的命令及其参数。这里的 /bin/ls 是 Linux 环境下的一个命令路径,该命令用于列出目录文件。数组的最后一个元素为 NULL,用来标识参数列表的结束。execv(args[0], args); 用于用 args[0] 指定的程序(这里是 /bin/ls)替换当前进程。在成功执行之后,当前程序的代码段、数据段等都将被新的程序加载,因此除非 execv 执行出错,否则 execv 之后的代码将不会再执行。printf("This statement won't be executed if execv is successful.\n"); 这行代码在 execv 成功执行后是不会执行的,因为当前程序将被 /bin/ls 的内容替代。如果 execv 失败,比如提供的路径不正确或者没有执行权限,这行代码会输出提示信息。这个示例程序展示了如何使用 execv来执行另一个程序,并替换当前进程的样例。当需要在程序中执行外部命令时,可以考虑使用此函数或类似的 exec 系列函数 (execl, execvp, execle 等)。
exit:终止当前进程,返回状态码供父进程查看。父进程通常使用 wait 系列函数获取子进程的终止状态。
#include <stdlib.h>
int main() {
// 一些代码
exit(0); // 正常结束程序
return 0;
}
信号:信号是用于通知进程某个事件发生的机制。常见信号包括 SIGINT、SIGKILL、SIGTERM 等。进程可以捕捉信号并进行相应处理。
#include <stdio.h>
#include <signal.h>
// 定义信号捕捉处理函数
void signal_handler(int signal) {
printf("Received signal %d\n", signal); // 打印接收到的信号编号 [1]
}
int main() {
signal(SIGINT, signal_handler); // 捕捉 SIGINT 信号,并指定处理函数 [2]
while (1); // 无限循环,保持程序运行,等待信号中断 [3]
return 0;
}
signal_handler 被调用时,printf 用于输出接收的信号编号。在此示例中,输出会显示 SIGINT 的默认数值(通常为2)。signal(SIGINT, signal_handler); 设置程序对 SIGINT 信号(终端中断信号,通常由 Ctrl+C 生成)的响应动作为调用 signal_handler 函数。signal 函数用来设置信号处理程序,可以用来忽略或捕捉信号。while (1); 语句创建了一个无限循环,此处程序将持续运行并等待 SIGINT 信号中断该循环。在这段时间内,程序会停滞在此循环中,用户可以通过发送 SIGINT(Ctrl+C)来触发信号处理函数,从而跳出循环。通常情况下,程序在接收到信号之后会继续到程序结束部分进行处理,但由于这里没有明示的退出机制,程序主要用于演示捕获信号。这种信号处理机制通常用于需要处理异步事件的程序,以确保程序能响应用户中断、定时事件或其他系统信号。
进程调度:操作系统根据一定的策略(如时间片轮转、优先级调度)决定将 CPU 分配给哪个进程。调度器是实现这一策略的组件。
进程优先级:在现代操作系统中,进程管理是至关重要的一环,它决定了一个进程何时被系统调度运行以及运行的时间长度。一个重要的管理手段便是通过调整进程的优先级,使得某些重要进程能够获得更多的CPU时间,进而提高系统的整体性能。
这段代码演示了如何使用C语言在Unix系统中管理进程的优先级。
#include <stdio.h>
#include <unistd.h>
#include <sys/resource.h> // 需要包含头文件来支持进程优先级相关操作
int main() {
// 使用 getpriority 函数获取当前进程的优先级
int priority = getpriority(PRIO_PROCESS, getpid());
printf("Current priority: %d\n", priority); // 输出当前优先级 [1]
// 使用 nice 函数提升当前进程的优先级
int ret = nice(-10); // 提升优先级 [2]
if (ret == -1) perror("nice"); // 检查函数返回值是否有错误 [3]
// 再次获取当前进程的优先级以便确认修改
priority = getpriority(PRIO_PROCESS, getpid());
printf("New priority: %d\n", priority); // 输出新的优先级 [4]
return 0;
}
getpriority() 函数以PRIO_PROCESS为参数标识进程,通过 getpid() 获取当前进程ID,从而查得该进程的当前优先级。优先级的数值越小,优先级越高。nice() 函数提升当前进程的优先级,传递 -10 的参数表示尝试减少Nice值(提高优先级)。在Unix系统中,默认情况下,普通用户只能增加Nice值,即降低优先级。nice() 返回值为 -1 时,通常用 perror() 函数输出错误信息。值得注意的是,提升优先级可能需要更高的权限(如超级用户权限),否则会引发错误。getpriority() 确认优先级是否被成功修改,并输出新的值。理解并适当调整进程的优先级有助于开发高效、稳定的系统应用,特别是在操作需要响应时间或处理能力要求较高的情况下。
改进内存管理部分的提纲如下:
内存管理是操作系统和应用程序共同关心的一项关键技术,本节将介绍主要的内存分配策略,包括动态内存分配和虚拟内存。
动态内存分配
动态内存分配允许程序在运行时分配和释放内存。C语言中,malloc、free、calloc、realloc等函数用于动态分配和管理内存。
#include <stdlib.h>
#include <stdio.h>
int main() {
int *arr = (int *)malloc(5 * sizeof(int)); // 动态分配内存 [1]
if (arr == NULL) { // 检查 malloc 的返回值 [2]
perror("malloc failed");
return 1;
}
for (int i = 0; i < 5; i++) {
arr[i] = i * 10; // 赋值使用动态内存 [3]
printf("arr[%d] = %d\n", i, arr[i]);
}
free(arr); // 释放内存 [4]
return 0;
}
[1] 动态分配内存:malloc(5 * sizeof(int)) 分配了一个可以存储5个整数的内存块,并返回指向这块内存的指针。如果分配失败,返回NULL。
[2] 检查 malloc 的返回值:if (arr == NULL) 判断内存分配是否成功。如果失败,arr将为NULL,使用perror输出错误信息。
[3] 赋值使用动态内存:arr[i] = i * 10 通过指针访问分配的内存,并为每个元素赋值。
[4] 释放内存:使用free(arr)释放之前分配的内存,以避免内存泄漏。在分配的内存不再需要使用时,必须显式释放它。
注意事项:
malloc返回值是否为NULL以防止分配失败。free释放内存。calloc函数会将分配的内存初始化为0,而malloc不会。虚拟内存
当物理内存已满并需要调入新的页面时,操作系统需决定哪些页面需要被置换出去。常见的页面置换算法有:
mmap)#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
// 主函数
int main() {
int fd = open("example.txt", O_RDONLY); // 打开文件 [1]
if (fd == -1) { // 检查文件是否打开成功 [2]
perror("open failed");
return 1; // 打开失败返回 [3]
}
size_t length = 4096; // 假设映射文件大小为4KB [4]
char *data = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0); // 内存映射 [5]
if (data == MAP_FAILED) { // 检查映射是否成功 [6]
perror("mmap failed");
close(fd); // 关闭文件描述符 [7]
return 1; // 映射失败返回 [8]
}
printf("%s\n", data); // 显示文件内容 [9]
munmap(data, length); // 解除映射 [10]
close(fd); // 关闭文件描述符 [11]
return 0;
}
[1] 打开文件:使用 open() 函数以只读模式(O_RDONLY)打开名为 “example.txt” 的文件,并返回文件描述符 fd。如果文件不存在或无法打开,就会返回 -1。
[2] 检查文件是否打开成功:判断 open() 函数返回的文件描述符 fd 是否为 -1,以确认文件是否成功打开。
[3] 打开失败返回:如果文件打开失败,则输出错误信息并返回错误码 1。
[4] 假设映射文件大小:设定待映射的内存大小,假设为 4096 字节,即 4KB。
[6] 检查映射是否成功:判断 mmap() 返回的指针是否为 MAP_FAILED 来确认内存映射是否成功。
[7] 关闭文件描述符:映射失败时,关闭文件描述符 fd。
[8] 映射失败返回:若映射失败,输出错误信息并返回错误码 1。
[9] 显示文件内容:通过 printf("%s\n", data); 输出映射内存中的文件内容。
[10] 解除映射:使用 munmap() 函数解除文件至内存的映射,将 data 和对应的长度传入。
[11] 关闭文件描述符:操作完毕后,使用 close(fd); 关闭文件描述符 fd。
注意事项:
mmap的返回值需检查是否为MAP_FAILED。munmap解除映射以释放资源。堆:
堆是用于动态内存分配的大块内存区域,程序使用malloc、free、realloc等函数在堆上进行手动内存管理。堆的内存分配和释放需要程序员严格管理,容易导致内存泄漏和分配错误。
栈:
#include <stdio.h>
#include <stdlib.h> // 包含 malloc 和 free 的头文件
// 定义函数 function
void function() {
int local_variable = 10; // 栈上分配的局部变量 [1]
}
// 主函数
int main() {
int *heap_variable = (int *)malloc(sizeof(int)); // 堆上分配的内存 [2]
if (heap_variable == NULL) {
perror("malloc failed"); // 错误处理 [3]
return 1;
}
*heap_variable = 20; // 使用动态分配的内存 [4]
function(); // 调用 function 函数 [5]
free(heap_variable); // 释放堆内存 [6]
return 0;
}
[1] 栈上分配的局部变量:local_variable 是在栈帧内定义的局部变量。当 function 函数被调用时,分配其内存;在函数返回后,内存自动释放。栈内存适用于短暂的、本地的变量分配。
[2] 堆上分配的内存:通过 malloc() 函数动态分配内存,返回一个指向分配内存的指针。此处分配了一个 int 类型变量的内存。动态内存分配在堆上进行,堆内存适合于需要在多次函数调用之间持久化的内存。
[3] 错误处理:在堆内存分配时,必须检查其返回值是否为 NULL,以确保内存分配成功,如果失败则输出错误信息。
[4] 使用动态分配的内存:通过取消引用指针 heap_variable 并赋值 20,使用了动态分配的内存。
[5] 调用 function 函数:在 main 中调用 function 函数,执行栈上变量的分配、使用和自动释放。
[6] 释放堆内存:在整个使用过程后,通过 free() 函数显式释放在堆上分配的内存,防止内存泄漏。堆内存管理需要程序员自行负责分配和释放。
对比:
通过学习本节内容,读者应该能够理解C语言中内存分配的基本策略和常见问题,能够在实际项目中有效地进行内存管理,避免内存泄漏和未定义行为。
在C语言中进行文件操作,需要使用标准库中的文件操作函数。以下是这些操作的详细描述:
fopen函数来创建和打开文件。FILE *fopen(const char *filename, const char *mode);
filename: 文件名路径。mode:文件打开模式,例如"r"(只读),"w"(写入),"a"(追加)。#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "w"); // [1] 打开文件用于写入
if (file == NULL) { // [2] 检查文件是否成功打开
perror("Error opening file"); // [3] 输出错误信息
return 1; // [4] 返回错误状态
}
// 可以在这里对文件进行操作
fclose(file); // [5] 关闭文件
return 0; // [6] 程序成功结束
}
[1] 打开文件用于写入:fopen("example.txt", "w") 尝试以写入模式打开一个名为 example.txt 的文件。如果文件不存在,fopen 会创建一个新文件;如果文件存在,它会清除文件内容。
[2] 检查文件是否成功打开:检查返回的文件指针 file 是否为 NULL。如果为 NULL,则表示打开文件失败,可能因为权限不足,路径不对,或者其他原因。
[3] 输出错误信息:perror 函数用于打印出错误信息,帮助程序员了解程序打开文件失败的原因。这个函数会打印出与前一次函数调用失败相关的错误消息,通常通过标准错误输出流 stderr 打印。
[4] 返回错误状态:如果文件未成功打开,程序返回 1 表示异常状态,以区别于正常执行返回的 0。
[5] 关闭文件:成功完成对文件的操作后,使用 fclose(file) 关闭文件。这是一个良好的做法,以保证释放系统资源,并确保所有缓冲的数据写入到文件中。
[6] 程序成功结束:程序正常结束时返回 0,这通常表示程序已成功执行无异常。
通过这一过程,我们看到如何有效地处理文件输入/输出的常用模式,包括资源管理(如关闭文件)和错误处理。这样做有助于保证系统资源的高效使用和程序的稳定性。
fread和fwrite函数进行二进制文件读写。对文本文件,则使用fprintf和fscanf。size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
int fprintf(FILE *stream, const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "w"); // 打开文件,模式为写入 [1]
if (file == NULL) {
perror("Error opening file"); // 出错处理 [2]
return 1;
}
fprintf(file, "Hello, World!\n"); // 写入文件 [3]
fclose(file); // 关闭文件 [4]
file = fopen("example.txt", "r"); // 打开文件,模式为读取 [5]
if (file == NULL) {
perror("Error opening file"); // 出错处理 [6]
return 1;
}
char buffer[100];
fgets(buffer, 100, file); // 读取文件 [7]
printf("%s", buffer); // 打印读取的数据 [8]
fclose(file); // 关闭文件 [9]
return 0;
}
fopen() 函数,以写入模式 ("w") 打开名为 "example.txt" 的文件。若文件不存在则创建新文件,若文件存在则清空内容。fopen() 返回 NULL 时,表示文件打开失败,使用 perror() 打印错误信息。fprintf() 方法向文件写入字符串 "Hello, World!\n"。fclose() 关闭已打开的文件,写入操作完成后应及时关闭文件以防止数据丢失。fopen() 函数以读取模式 ("r") 打开文件 "example.txt"。fgets() 函数读取文件中的内容,最多读取 buffer 的大小减一(此处为100字节),保证有空间存储终止符。printf() 将从文件中读取的内容输出到终端。本示例展示了文件的基本操作,包括如何创建、写入以及读取一个文本文件。正确的文件打开模式和错误处理可以确保程序的稳健性和安全性。
fclose来关闭文件,并确保文件指针被释放。int fclose(FILE *stream);
在Linux和Unix操作系统中,文件操作可以使用文件描述符或文件指针:
open,read,write,close等系统调用操作文件描述符。FILE *指针类型,通常使用fopen,fread,fwrite,fclose等C标准库函数访问。#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h> // 添加头文件以使用 strlen() 函数 [1]
int main() {
int fd = open("example.txt", O_WRONLY | O_CREAT, 04); // 打开或创建文件 [2]
if (fd == -1) {
perror("Error opening file"); // 错误处理 [3]
return 1;
}
const char *text = "Hello, World!";
write(fd, text, strlen(text)); // 写入文件 [4]
close(fd); // 关闭文件 [5]
return 0;
}
#include <string.h> 用于使用 strlen() 函数计算字符串长度。open() 函数打开 example.txt 文件供写操作;如果文件不存在,将创建该文件。标志 O_WRONLY 表示只写模式,O_CREAT 表示如文件不存在则创建文件,权限 04 即文件所有者可读写,其他用户可读。open() 返回值为 -1,则表示打开文件失败,perror() 用于输出错误信息到标准错误输出并返回非零值终止程序。write() 函数将 text 中的数据写入打开的文件中,使用 strlen(text) 计算待写入数据的长度。close(fd) 释放文件描述符 fd 以关闭 example.txt 文件,确保写入的数据完整并释放系统资源。flock, fcntl)文件锁定是防止多个进程并发访问文件导致数据损坏的重要机制。
flock函数:提供排他锁和共享锁。
#include <sys/file.h>
int flock(int fd, int operation);
fcntl函数:更强大的文件锁定机制,支持记录级锁定。
#include <fcntl.h>
int fcntl(int fd, int cmd, ...);
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/file.h>
// 主程序执行文件锁定操作
int main() {
int fd = open("example.txt", O_WRONLY); // 以只写模式打开文件 [1]
if (fd == -1) { // 检查文件是否成功打开
perror("Error opening file");
return 1;
}
if (flock(fd, LOCK_EX) == -1) { // 进行独占锁定 [2]
perror("Error locking file");
close(fd);
return 1;
}
// 对文件进行操作...
// 这些操作处于锁定状态下执行,此时其他进程无法修改文件
if (flock(fd, LOCK_UN) == -1) { // 解锁文件 [3]
perror("Error unlocking file");
}
close(fd); // 关闭文件 [4]
return 0; // 程序结束
}
open() 函数以 O_WRONLY 标志打开名为 example.txt 的文件,返回一个文件描述符 fd。如果打开失败(返回-1),则输出错误信息并结束程序。flock() 函数尝试对文件进行独占锁定(LOCK_EX),即只允许当前进程访问,其他进程必须等待文件解锁。如果锁定失败,程序输出错误信息,关闭文件后结束。flock() 函数解除文件的锁定(LOCK_UN),允许其他进程访问文件。close(fd) 用于关闭文件描述符 fd,释放系统资源。#include <sys/stat.h>
int mkdir(const char *pathname, mode_t mode);
#include <unistd.h>
int rmdir(const char *pathname);
#include <dirent.h>
DIR *opendir(const char *name);
struct dirent *readdir(DIR *dirp);
int closedir(DIR *dirp);
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h> // 用于目录流操作
#include <sys/stat.h> // 用于文件和文件权限操作
#include <unistd.h> // 用于访问 POSIX 操作系统 API
int main() {
mkdir("new_directory", 0755); // 创建目录 [1]
DIR *dir = opendir("."); // 打开当前目录 [2]
if (dir == NULL) { // 判断是否成功打开 [3]
perror("Error opening directory");
return 1;
}
struct dirent *entry; // 声明目录项结构指针 [4]
while ((entry = readdir(dir)) != NULL) { // 读取目录 [5]
printf("%s\n", entry->d_name); // 打印目录中的文件名 [6]
}
closedir(dir); // 关闭目录流 [7]
rmdir("new_directory"); // 删除目录 [8]
return 0;
}
链接是文件系统中用来建立文件别名的机制。
符号链接:创建指向另一个文件的路径。
#include <unistd.h>
int symlink(const char *target, const char *linkpath);
#include <unistd.h>
int link(const char *oldpath, const char *newpath);
#include <stdio.h>
#include <unistd.h>
int main() {
symlink("example.txt", "example_symlink.txt"); // 创建符号链接 [1]
link("example.txt", "example_hardlink.txt"); // 创建硬链接 [2]
return 0;
}
[1] 创建符号链接:symlink("example.txt", "example_symlink.txt"); 使用 symlink 函数创建一个指向 example.txt 的符号链接,命名为 example_symlink.txt。符号链接类似于快捷方式,存储的是目标文件的路径名。
[2] 创建硬链接:link("example.txt", "example_hardlink.txt"); 使用 link 函数创建一个指向 example.txt 的硬链接,命名为 example_hardlink.txt。硬链接指向与目标文件相同的物理文件数据。
示例说明:这段程序首先创建一个名为 example_symlink.txt 的符号链接,然后创建一个名为 example_hardlink.txt 的硬链接,指向相同的原始文件example.txt。符号链接与硬链接在用途和特性上迥异,符号链接提供更直观的路径指向,而硬链接则实现数据存储的复用效应。
通过以上内容,我们具体了解了文件系统的基本操作。这些知识点在C语言项目开发中尤为重要,可以有效防止数据损坏并提升程序的鲁棒性。
因篇幅问题不能全部显示,请点此查看更多更全内容