Linux系统编程
线程的创建
每一个线程都有一个唯一的线程 ID,ID 类型为 pthread_t,这个 ID 是一个无符号长整形数,如果想要得到当前线程的线程 ID,可以调用如下函数:
pthread_t pthread_self(void); // 返回当前线程的线程ID
在一个进程中调用线程创建函数,就可得到一个子线程,和进程不同,需要给每一个创建出的线程指定一个处理函数,否则这个线程无法工作。
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); // Compile and link with -pthread, 线程库的名字叫pthread, 全名: libpthread.so libptread.a
参数:
thread: 传出参数,是无符号长整形数,线程创建成功,会将线程 ID 写入到这个指针指向的内存中
attr: 线程的属性,一般情况下使用默认属性即可,写 NULL
start_routine: 函数指针,创建出的子线程的处理动作,也就是该函数在子线程中执行。
arg: 作为实参传递到 start_routine 指针指向的函数内部
返回值:线程创建成功返回 0,创建失败返回对应的错误号
启动程序之后,入口main函数,在主线程创建子线程,子线程创建出来后执行任函数callback,从for循环向下执行,执行完后子线程退出。
主线程从main函数一直向下,直到main执行完毕。主线不会执行子线程的任务函数,它是在子线程里处理的。

错误原因是因为编译器链接不到线程库文件(动态库),需要在编译的时候通过参数指定出来,动态库名为 libpthread.so 需要使用的参数为 -l,根据规则掐头去尾最终形态应该写成:-lpthread(参数和参数值中间可以有空格)。正确的编译命令为:

结论:
1.创建一个线程需要调用线程函数,函数执行成功后,得到的是子线程,子线程执行的任务第三个参数执行的任务函数
2.主线程执行完后会释放对应的虚拟地址空间,可以让主线程睡一会。
3. 子进程结束后,它的父进程要回收它的资源,否则就会成为僵尸进程 。
线程退出
在编写多线程程序的时候,如果想要让线程退出,但是不会导致虚拟地址空间的释放(针对于主线程),我们就可以调用线程库中的线程退出函数,只要调用该函数当前线程就马上退出了,并且不会影响到其他线程的正常运行,不管是在子线程或者主线程中都可以使用。
参数:线程退出的时候携带的数据,当前子线程的主线程会得到该数据。如果不需要使用,指定为 NULL。

文件IO之open函数
查看open函数 man 2 open,需要用到以下头文件
 #include <sys/types.h>
       #include <sys/stat.h>
       #include <fcntl.h>
int open(const char *pathname, int flags);
       int open(const char *pathname, int flags, mode_t mode)
       int creat(const char *pathname, mode_t mode);
参数 flags 是通过 O_RDONLY, O_WRONLY 或 O_RDWR (指明文件是以只读 ,只写或读写方式打开的) 与下面的零个或多个可选模式 按位 -or 操作得到的。
mode 只有 当 在 flags 中 使用 O_CREAT 时 才 有效 , 否则被忽略.
creat 相当 于 open 的 参数 flags 等于 O_CREAT|O_WRONLY|O_TRUN
 
打开了当前目录的a.c文件,如果当前目录下没有a.c,就会创建一个a.c,因为加了O_CREAT这个函数,权限为666。


mode-t & ~(umask)
6 6 6 & ~(0 0 2) => 6 6 6 & (7 7 5) = 6 6 4
110&111 = 110 110&101=100
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(int argc, char *argv[]) { //argc:表示的是命令行中参数的个数 //argv:表示的是命令行中的参数 int fd;//保存文件名 fd = open("a.c", O_CREAT | O_RDWR, 0666); if(fd < 0) { printf("open is error!\n"); } printf("fd is %d\n", fd); return 0; }
文件IO之close函数
查看close函数 man 2 close,需要用到以下头文件
#include <unistd.h>
int close(int fd);
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char *argv[]) { //argc:表示的是命令行中参数的个数 //argv:表示的是命令行中的参数 int fd;//保存文件名 fd = open("a.c", O_CREAT | O_RDWR, 0666); if(fd < 0) { printf("open is error!\n"); } printf("fd is %d\n", fd); close(fd); return 0; }
文件IO之read函数
查看read函数 man 2 read,需要用到以下头文件
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
fd是使用open函数打开的返回值
buf: void* 类型的指针,一般传的是地址。
count:要读取的字节数
int main(int argc, char *argv[]) { //argc:表示的是命令行中参数的个数 //argv:表示的是命令行中的参数 int fd;//保存文件名 char buf[32]={0}; ssize_t ret; fd = open("a.c", O_RDWR); if(fd < 0) { printf("open is error!\n"); return -1; } printf("fd is %d\n", fd); ret = read(fd, buf, 32); if(ret<0){ printf("read is error!\n"); return -2; } printf("buf is %s\n", buf); printf("ret is %ld\n", ret); close(fd); return 0; }
运行结果


文件IO之write函数
查看write函数 man 2 write,需要用到以下头文件
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char *argv[]) { int fd;//保存文件名 char buf[32]={0}; ssize_t ret; /***fd = open("a.c", O_RDWR); if(fd < 0) { printf("open is error!\n"); return -1; } printf("fd is %d\n", fd);***/ write(1, "hello\n", 6); close(fd); return 0; }
运行结果:

#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char *argv[]) { int fd;//保存文件名 char buf[32]={0}; ssize_t ret; fd = open("a.c", O_RDWR); if(fd < 0) { printf("open is error!\n"); return -1; } printf("fd is %d\n", fd); write(fd, "hello\n", 6); close(fd); return 0; }
运行结果:

综合练习
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char *argv[]) { //步骤一:判断命令行中的参数 if(argc != 3) { printf("Usage:%s <src file> <obj file>\n", argv[0]); return -1; } //步骤二:定义变量 int fd_src, fd_obj;//保存文件名 char buf[32]={0}; ssize_t ret; //步骤三:打开文件或者文件描述符,用两次open函数
// 1.打开a.c 2.打开b.c fd_src = open(argv[1], O_RDWR); if(fd_src < 0) { printf("open is error!\n"); return -1; } fd_obj = open(argv[2], O_RDWR|O_CREAT, 0666); if(fd_obj < 0) { printf("open is error!\n"); return -1; } //步骤四:读写操作 while((ret = read(fd_src, buf, 32))!=0) { write(fd_obj, buf, ret); }; //步骤五 close(fd_src); close(fd_obj); return 0; }
目录IO之mkdir函数
查看 mkdir 函数 man 2 mkdir ,需要用到以下头文件
#include <sys/stat.h>
       #include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
RETURN VALUE
       mkdir() returns zero on success, or -1 if an error  occurred  (in  which case, errno is set appropriately).
#include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> int main(int argc, char *argv[]) { //argc:表示的是命令行中的参数个数 //argv:表示的是命令行中的参数 int ret;//返回值 if(argc != 2 ) { printf("Usage%s <name file> \n",agrv[0]); return -1; } ret = mkdir(argv[1], 0666); if(ret < 0) { printf("mkdir is error \n"); return -2; } printf("mkdir is OK \n"); return 0; }
运行结果:创建了一个test 目录

目录IO之opendir函数和closedir函数
查看 opendir 函数 man 3 opendir ,需要用到以下头文件
#include <sys/types.h>
       #include <dirent.h>
DIR *opendir(const char *name);
       DIR *fdopendir(int fd);
#include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <dirent.h> int main(int argc, char *argv[]) { //argc:表示的是命令行中的参数个数 //argv:表示的是命令行中的参数 int ret;//返回值 DIR *dp; if(argc != 2 ) { printf("Usage%s <name file> \n",argv[0]); return -1; } dp = opendir(argv[1]); if(dp != NULL){ printf("opendir is OK\n"); } closedir(dp); return 0; }
 
目录IO之readdir函数
查看 readdir 函数 man 3 readdir ,需要用到以下头文件
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);

#include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <dirent.h> int main(int argc, char *argv[]) { //argc:表示的是命令行中的参数个数 //argv:表示的是命令行中的参数 int ret;//返回值 DIR *dp; struct dirent *dir; if(argc != 2 ) { printf("Usage%s <name file> \n",argv[0]); return -1; } dp = opendir(argv[1]); if(dp == NULL){ printf("opendir is error\n"); return -2; } printf("opendir is OK\n"); while(1){ dir = readdir(dp); if(dir != NULL) { printf("file name is %s\n",dir->d_name); }
else break; }
closedir(dp); return 0; }
运行结果:

 
综合练习2
1.打印我们要拷贝的目录下的所有文件名,并拷贝我们需要的文件。
2.通过键盘输入我们要拷贝的文件的路径和文件名等信息。
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <dirent.h> int main(int argc, char *argv[]) { //步骤一:定义变量 int fd_src, fd_obj;//保存文件名 char buf[32]={0}; char file_path[32] = {0}; ssize_t ret; DIR *dp; struct dirent *dir; printf("Please enter the file path:\n"); scanf("%s", file_path); dp = opendir(file_path); if(dp == NULL){ printf("opendir is error!\n"); return -1; } printf("opendir is ok!\n"); while(1)
{ dir = readdir(dp); if(dir != NULL){ printf("file name is %s\n", dir->d_name); } else break; } return 0; }
运行结果:

#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <dirent.h> #include <string.h> int main(int argc, char *argv[]) { //步骤一:定义变量 int fd_src, fd_obj;//保存文件名 char buf[32]={0}; char file_path[32] = {0}; char file_name[32] = {0}; ssize_t ret; DIR *dp; struct dirent *dir; //步骤二:从键盘输入文件路径 printf("Please enter the file path:\n"); scanf("%s", file_path); //步骤三:打开目录,获得目录流指针,然后读取目录打印文件名 dp = opendir(file_path); if(dp == NULL){ printf("opendir is error!\n"); return -1; } printf("opendir is ok!\n"); while(1){ dir = readdir(dp); if(dir != NULL){ printf("file name is %s\n", dir->d_name); } else break; } //步骤四:获得我们要复制的文件的文件名 printf("Please enter the file name:\n"); scanf("%s", file_name); //步骤五:获得文件描述符 fd_src = open(strcat(strcat(file_path, "/"),file_name), O_RDWR); if(fd_src < 0) { printf("open is error!\n"); return -1; } fd_obj = open(file_name, O_RDWR|O_CREAT, 0666); if(fd_obj < 0) { printf("open is error!\n"); return -1; } //步骤六:读写操作 while((ret = read(fd_src, buf, 32))!=0) { write(fd_obj, buf, ret); }; //步骤七:关闭 close(fd_src); close(fd_obj); closedir(dp); return 0; }
运行结果:

库的基本知识
1.什么是库?
库是一种可执行的二进制文件,是编译好的代码。
静态库的制作和使用
1 准备测试程序 vim mylib.c
#include <stdio.h> void mylib(void); void mylib(void) { printf("Happy every day\n"); }
2 生成静态库
第一步:将源文件mylib.c 进行汇编,得到二进制目标文件 mylib.o
gcc -c mylib.c

第二步:将生成的目标文件通过 ar 工具打包生成静态库

静态库的使用
vim test.c
#include <stdio.h> void mylib(void); int main(void) { mylib(); return 0; }

程序:源代码,指令
- 什么是进程?
进程指的是正在运行的程序
- 进程ID
每个进程都有一个唯一的标识符,即进程ID,简称pid
- 进程间通信
管道通信:有名管道,无名管道
信号通信:信号的发生,信号的接受,信号的处理
IPC通信:共享内存,消息队列,信号灯
Socket通信
- 进程的三种基本状态以及转换
线程:线程从属于进程,一个进程可以有多个线程,线程之间共享进程的资源
任务:具体要做的事情
systemd(init):所有进程的父进程 pid = 1
进程控制
查看 fork函数 man 3 fork,需要用到以下头文件
#include <unistd.h>
pid_t fork(void);
返回值:fork函数有三种返回值
1.在父进程中,fork返回新创建的子进程的PID
2.在子进程中,fork返回0
3.如果出现错误,fork返回一个负值
父子进程的执行顺序不一定。
 

exec函数族
换核不换壳
在Linux中使用exec函数族主要有以下两种情况:
1.当进程认为自己不能再为系统核用户做出任何贡献时,就可以调用任何exec函数族让自己重生。
2.如果一个进程想执行另一个程序,那么它就可以调用fork函数新建一个进程,然后调用任何一个exec函数使子进程重生。
查看 exec 函数 man execl,需要用到以下头文件
#include <unistd.h>
extern char **environ;
 int execl(const char *path, const char *arg, ...);
       int execlp(const char *file, const char *arg, ...);
       int execle(const char *path, const char *arg, ..., char * const envp[]);
       int execv(const char *path, char *const argv[]);
       int execvp(const char *file, char *const argv[]);
       int execvpe(const char *file, char *const argv[], char *const envp[]);
ps和kill命令
ps命令:ps命令可以列出参数中当前运行的那些进程。
命令格式:ps[参数]
命令功能:用来显示当前进程的状态
常用参数:aux

PID:PID号
TTY:当前的进程关联我们终端
STAT:进程的状态
TIME:启动时间
COMMAND:进程执行的具体的程序

VSZ:虚拟内存的大小
RSS:进程使用的物理内存的大小
%CPU:进程占用了CPU计算能力的百分比
%MEM:占用内存的百分比
USER:进程是归属的用户是什么

psx:显示所有程序,不以终端机来区分。

 
 
孤儿进程和僵尸进程
孤儿进程:父进程结束以后,子进程还未结束,这个子进程就叫做孤儿进程。
僵尸进程:子进程结束以后,父进程还在运行,但是父进程不去释放进程控制块,这个子进程就叫做僵尸进程
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(void) { pid_t pid; int i = 0; pid = fork(); if(pid < 0){ printf("fork is error \n"); return -1; } //父进程 if(pid > 0){ printf("This is parent,parent pid is %d\n", getpid()); } //子进程 if(pid == 0){ while(1){ printf("This is child,child pid is %d,parent pid is %d\n", getpid(),getppid()); //execl("/bin/ls", "ls", "-al", NULL); //exit(1); } } i++; printf("i is %d\n", i); return 0; }
线程回收函数

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> struct Test { int num; int age; }; void* callback(void *arg) { for(int i=0; i<5; ++i) { printf("子线程: i=%d\n", i); } printf("子线程:%ld\n", pthread_self()); struct Test t; t.num = 100; t.age = 66; pthread_exit(&t); return NULL; } int main() { pthread_t tid; pthread_create(&tid, NULL, callback, NULL); printf("主线程:%ld\n", pthread_self()); void* ptr; pthread_join(tid, &ptr); struct Test* pt = (struct Test*)ptr; printf("num: %d, age=%d\n", pt->num, pt->age); return 0; }
线程分离
互斥锁
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <pthread.h> #define MAX 100 // 全局变量 int number; // 创建一把互斥锁 // 全局变量, 多个线程共享 pthread_mutex_t mutex; // 线程处理函数 void* funcA_num(void* arg) { for(int i=0; i<MAX; ++i) { // 如果线程A加锁成功, 不阻塞 // 如果B加锁成功, 线程A阻塞 pthread_mutex_lock(&mutex); int cur = number; cur++; usleep(10); number = cur; pthread_mutex_unlock(&mutex); printf("Thread A, id = %lu, number = %d\n", pthread_self(), number); } return NULL; } void* funcB_num(void* arg) { for(int i=0; i<MAX; ++i) { // a加锁成功, b线程访问这把锁的时候是锁定的 // 线程B先阻塞, a线程解锁之后阻塞解除 // 线程B加锁成功了 pthread_mutex_lock(&mutex); int cur = number; cur++; number = cur; pthread_mutex_unlock(&mutex); printf("Thread B, id = %lu, number = %d\n", pthread_self(), number); usleep(5); } return NULL; } int main(int argc, const char* argv[]) { pthread_t p1, p2; // 初始化互斥锁 pthread_mutex_init(&mutex, NULL); // 创建两个子线程 pthread_create(&p1, NULL, funcA_num, NULL); pthread_create(&p2, NULL, funcB_num, NULL); // 阻塞,资源回收 pthread_join(p1, NULL); pthread_join(p2, NULL); // 销毁互斥锁 // 线程销毁之后, 再去释放互斥锁 pthread_mutex_destroy(&mutex); return 0; }
生产者和消费者
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> pthread_cond_t cond; pthread_mutex_t mutex; // 链表的节点 struct Node { int number; struct Node* next; }; //链表头节点 struct Node* head = NULL; //生产者 void* producer(void* arg) { while(1) { pthread_mutex_lock(&mutex); //创建新节点 struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); //init node newNode -> number = rand()%1000; newNode -> next = head; head = newNode; printf("生产者,id:%ld, number:%d\n", pthread_self(), newNode->number); pthread_mutex_unlock(&mutex); sleep(rand() % 3); } return NULL; } // 定义条件变量, 控制消费者线程 //消费者 void* consumer(void *arg) { while(1) { pthread_mutex_lock(&mutex); while(head == NULL) { //zuse consumer xiancheng pthread_cond_wait(&cond, &mutex); } struct Node* node = head; printf("消费者,id:%ld, number:%d\n", pthread_self(), node->number); head = head -> next; free(node); pthread_mutex_unlock(&mutex); pthread_cond_broadcast(&cond); sleep(rand() % 3); } return NULL; } int main() { pthread_mutex_init(&mutex, NULL); pthread_cond_init(&cond, NULL); pthread_t t1[5], t2[5]; for(int i=0; i<5; ++i) { pthread_create(&t1[i], NULL, producer, NULL); } for(int i=0; i<5; ++i) { pthread_create(&t2[i], NULL, consumer, NULL); } for(int i=0; i<5; i++) { pthread_join(t1[i], NULL); pthread_join(t2[i], NULL); } pthread_mutex_destroy(&mutex); pthread_mutex_destroy(&cond); return 0; }
运行结果:


文件 IO 是直接调用内核提供的系统调用函数,头文件是 unistd.h,标准 IO 是间接调用系统调用函数,
头文件是 stdio.h,文件 IO 是依赖于 Linux 操作系统的,标准 IO 是不依赖操作系统的,所以在任何的操作系
统下,使用标准 IO,也就是 C 库函数操作文件的方法都是相同的。
wait( )

进程间通信
 
 
共享内存
共享内存,顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
Linux 操作系统的进程通常使用的是虚拟内存,虚拟内存空间是有由物理内存映射而来的。System V 共享内存能够实现让两个或多个进程访问同一段物理内存空间,达到数据交互的效果。
  
线程
串行:一件事、一件事接着做
并发:交替做不同的事;
并行:同时做不同的事。
Socket
TCP 面向连接连接 A和B打电话 精细操作 很多控制
UDP 面向报文 A和B发短信 涉及到大量的数据
1.TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2.TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
3.TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4.每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5. TCP首部开销20字节;UDP的首部开销小,只有8个字节
6. TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
字节序
字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。
LE(小端字节序):将低序字节存储在起始位置。
BE(大端字节序):将高序字节存储在起始位置。
grep "struct sockaddr_in {" * -nir

 
                     
                    
                 
                    
                 
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号