目录
Linux(ubantu)系统编程笔记
1.常用文件操作函数
open(char* path,int flag):
return:为文件描述符,是一个整形数。
flag:是打开模式
write(int fd,const void *buf,size_t count):
return写入的字节数
fd:是文件描述符
buf:是任意类型的指针
count:是要写入的字节数
read(int fd,const void *buf,size_t count):
return读出的字节数
fd:是文件描述符
buf:是任意类型的指针
count:是要读出的字节数
close(int fd):
传入文件描述符即可.
[一些常识]:
1.fopen fwrite fread 这一系列的函数和上述功能相同,但是使用方法会不一样。
从根本的说区别在于:这些函数是c语言标准的API,而上述的函数是linux内核里的函数。
2.main函数的完整写法为:int main(char argc,char **argv)/int main(char argc,char *argv[]),
其中argc是参数总数,argv是指针数组其中从argv[1]开始指向第一个参数.
2.进程相关函数和常识:
fork()与vfork():
它们都是用于创建一个子进程,子进程会和父进程共享从fork函数开始往下所有代码也包括这个函数本身,所以也就引申出了这个函数的一个特点——它会返回两次。
当返回值为0时表示这是父进程执行,否则为子进程。
它们的不同在于,vfork()函数会等待子进程执行结束后在执行父进程,而fork()同时进行,这就引申出了第一个概念:僵尸进程。
wait(int *waitstatus):
该函数会阻塞父进程直到收到子进程结束码为止。
execl家族函数:
execl();execlp();execv();execvp;
它们的功能比较重复,理解execl本身的用法就基本够用了:
execl(char *pathname,const char *arg ....):
此函数一般在子进程中调用,它会调用可执行程序或者文件直接覆盖该进程,也就是说执行该函数后如果执行成功将不会有返回值。
*pathname:可执行程序或文件路径
*arg:执行程序需要传入的参数 比如这个可执行程序传入的是系统的cp指令,那么这里就要传file1的路径,然后传file2的路径。
另外:参数的结尾必须为NULL
execlp():
和上述一样,唯一不同就是p同PATH表示环境变量,第一个参数可以传非绝对路径.
system(const char* command):
这个函数说白了就是对execl函数的一个高级封装,不用传乱七八糟的参数,直接给命令就这能执行,并且用execl函数时执行成功不会返回,也就是说原函数调用此函数后的代码全都不会执行,
但是使用system会执行。
popen(const char* command,char *type):
和system一样,唯一不同就是它在调用可执行程序时会创造一个管道,这样可以获取执行命令后的信息。
*type:r或者w,这决定它的返回值是一个标准的输入流还是输出流。
[一些常识]:
僵尸进程:指没有被父进程收集退出码就已经退出了的子进程。
孤儿进程:子进程未结束但是父进程已经结束的进程,这样的进程最终会被init进程回收,pid为1。
3.进程间通信:
[有名管道和无名管道]:
int piepe(int fd[2]):
无名管道创建函数,管道本质上就是一种文件,但是无名管道所创建的文件不存在于文件管理系统中。
其中 fd[0]用读而打开,fd[1]用于写而打开。另外,无名管道只能用于有血缘关系的进程通信,说白了就是子进程和父进程或者同一父进程下的子进程。
int mkfifo(const char* pathname,mode_t mode):
有名管道,从参数就能看出来是创建一个文件用于通信,这个文件是确实存在于文件系统中的。进程利用该文件调用read和write函数即可通信。
有名管道可以任意进程之间通信。其中,mode在默认情况下设定为阻塞,即当一个进程单方面进行读或写时,当没有对应进程进行写或者读时会阻塞,直到有为止。
注意:打开的文件必须不存在。
[消息队列]:
须知四个函数:
msgget(key_t ket,int flag):
获取消息队列,key是一个索引用于寻找队列。flag相当于模式,当flag=IPC_CREAT时如果找不到则会创建一个队列,当创建成功时return 队列的ID。
msgsnd(int msgid,const void *buf,size_t size,int flag):
发送消息到队列中,其中buf是对应消息,size是大小。成功return消息长度。
msgrcv(int msgid,const void *buf,size_t size,long type,int flag):
接收消息,和写入一样,唯一不同的是type表示查询的顺序,
type:== 0 查询队列中第一个消息
> 0 返回type类型的第一个消息。
msgctl(int msgid,int cmd,struct msqid_ds *buf);
其中:key的值如果用于创建可以用ftok(const char* filename,int id)函数,来创建一个美观的,原理是用当前文件的结点数和一个任意id组合成一个十六进制数。
[共享内存]:
int shmget(key_t key,size_t size,int flag):
创建共享内存
size:内存字节大小,比如写成1024*4就是4M。
return:返回共享内存的标识符即shm_id
void shmat(int shm_id,const void *addr,int flag):
将内存映射进程序占用的cpu内存中来使用,return一个只想共享内存存储地方的一个指针。
参数:
*addr:指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
id:共享内存标识符
int shmdt(void *shmaddr):
断开映射连接
shmaddr:连接的共享内存的起始地址
int shmctl(int id,int cmd,struct shmid_ds *buff):
控制内存函数
id:略
cmd:
IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中
IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内
IPC_RMID:删除这片共享内存
[一些常识]:
1.共享内存的写入写出方式和单片机串口的方式一样,就是使对应内存比如buff = "abcd"。
读就干脆在printf里用%S对应即可。
[信号]:
int kill(pid_t pid, int sig):
pid > 0,该函数会向PID为pid的进程发送信号值为sig的信号。
pid = 0,该函数会向当前进程隶属的进程组下所有的进程发出信号值为sig的信号,包括这个进程自己。
sig是具体信号
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler):
sighandler_t是一个指向函数的指针,要求参数列表只有一个int参数,并且无返回值。
当进程收到信号对应的宏值为signum那么将会调用handler函数,就像QT中的connection。
以上为初级函数,无法发送信号的同时携带信息,高级函数为:
高级接收信号:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
signum:信号值
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);//由于有void*参数,可以携带复杂的数据
sigset_t sa_mask; //工作于信号捕捉函数执行期间屏蔽sa_mask对应的信号
int sa_flags; //用于设定可接收到信号外的信息
void (*sa_restorer)(void);
};
oldact:指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL(基本都让它为NULL)
基本用法就是设定结构体,重点设置(*sa_sigaction)(int, siginfo_t *, void *)和sa_flags = SA_SIGINFO;
高级发送信号:
int sigqueue(pid_t pid, int sig, const union sigval value):
pid:进程号
union sigval value:
typedef union sigval
{
int sival_int;
void *sival_ptr;
}sigval_t;
[一些常识]:
1.atoi函数用于将ascall对应的字符数字转换为int数字。
2.对信号的处理有忽略捕捉和默认动作,默认动作值系统内核自动配备的handler函数,
比如ctrl+c终止程序就是一种默认动作,它可以被更改吗,但是有些信号不可以更改默认动作也没办法忽略。
4.线程:
关于线程的api总共分三大类:
线程: 创建 退出 等待
互斥锁: 创建 销毁 上锁 解锁
条件: 创建 销毁 广播 触发 等待
从线程开始:
线程创建函数:
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg):
*restrict tidp:一个pthread_t类型的指针,一般定义一个 pthread_t thread 传入 &thread即可。
const pthread_attr_t *restrict attr: 线程属性,设为NULL就行。
void *(*start_rtn)(void *):函数指针,对应函数形式必须为:
void *handler(void *param){}
void *restrict arg:从名字上就知道是一个参数,会对应到函数里的param。
线程退出函数:
int pthread_exit(void *rval_ptr):
void *rval_ptr:给&thread即可。
线程等待函数:
int pthread_join(pthread_t thread, void **rval_ptr):
pthread_t thread:要等待的线程thread,调用该函数的线程会等待对应线程结束后在继续执行否则阻塞。
void **rval_ptr:用原文的话说是一个指向指向退出码的指针的指针,总之它最终是退出码。
互斥锁:
创建及销毁互斥锁:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr):
pthread_mutex_t *restrict mutex:写法有点怪,总之就是 pthread_mutex_t mutex 后传入&mutex就行。
const pthread_mutexattr_t *restrict attr:NULL
int pthread_mutex_destroy(pthread_mutex_t mutex);
pthread_mutex_t mutex:&mutex。
互斥锁的上锁和解锁:
互斥锁上锁函数:
int pthread_mutex_lock(pthread_mutex_t mutex):
pthread_mutex_t mutex:&mutex。
互斥锁解锁函数:
int pthread_mutex_unlock(pthread_mutex_t mutex):
pthread_mutex_t mutex:&mutex。
条件:
创建及销毁条件变量:
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr):
pthread_cond_t *restrict cond:pthread_cond_t cound后传入&cound。
const pthread_condattr_t *restrict attr: NULL。
int pthread_cond_destroy(pthread_cond_t cond);
pthread_cond_t cond:&cound。
条件等待:
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
pthread_cond_t *restrict cond:&cound
pthread_mutex_t *restrict mutex: &mutex
说明:该函数会阻塞直到coud的值为真。
条件触发:
int pthread_cond_signal(pthread_cond_t *cond):
pthread_cond_t *cond:&cound
说明:使得cond为真,会让等待函数不再阻塞。
int pthread_cond_broadcast(pthread_cond_t cond):
而pthread_cond_broadcast函数将唤醒等待该条件的所有进程。